Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
79 lines
3.3 KiB
Bash
Executable file
79 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# RULE 0.18 — task/session time tracker. Appends durations to JSONL
|
|
# journals so future estimates have real data to cite.
|
|
#
|
|
# Three modes selected by hook_event_name from JSON stdin:
|
|
# - "Stop" → write session-end record to sessions.jsonl
|
|
# - "PreToolUse" → record agent-spawn start (Agent tool only)
|
|
# - "PostToolUse" → record agent-spawn end + duration
|
|
#
|
|
# Modern Claude Code passes hook info via JSON stdin:
|
|
# {"hook_event_name":"...","tool_name":"...","tool_input":{...},
|
|
# "tool_use_id":"...","session_id":"..."}
|
|
# Older env-var protocol (CLAUDE_HOOK_EVENT) is kept as fallback.
|
|
|
|
set -uo pipefail
|
|
|
|
JOURNAL_DIR="$HOME/.claude/memory/time-metrics"
|
|
mkdir -p "$JOURNAL_DIR"
|
|
|
|
INPUT="$(cat 2>/dev/null || true)"
|
|
EVENT="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null)"
|
|
[[ -z "$EVENT" ]] && EVENT="${CLAUDE_HOOK_EVENT:-unknown}"
|
|
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)"
|
|
[[ -z "$SESSION_ID" ]] && SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
|
|
NOW_EPOCH="$(date +%s)"
|
|
NOW_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
|
|
case "$EVENT" in
|
|
Stop)
|
|
START_FILE="$JOURNAL_DIR/.session-${SESSION_ID}.start"
|
|
if [[ -f "$START_FILE" ]]; then
|
|
START="$(cat "$START_FILE")"
|
|
DURATION=$((NOW_EPOCH - START))
|
|
printf '{"kind":"session","id":"%s","start_epoch":%s,"end_epoch":%s,"duration_s":%s,"ts":"%s"}\n' \
|
|
"$SESSION_ID" "$START" "$NOW_EPOCH" "$DURATION" "$NOW_ISO" \
|
|
>> "$JOURNAL_DIR/sessions.jsonl"
|
|
rm -f "$START_FILE"
|
|
fi
|
|
;;
|
|
|
|
PreToolUse)
|
|
TOOL_NAME="$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
if [[ "$TOOL_NAME" = "Agent" ]]; then
|
|
START_FILE="$JOURNAL_DIR/.session-${SESSION_ID}.start"
|
|
[[ -f "$START_FILE" ]] || echo "$NOW_EPOCH" > "$START_FILE"
|
|
|
|
AGENT_ID="$(printf '%s' "$INPUT" | jq -r '.tool_use_id // empty' 2>/dev/null)"
|
|
DESC="$(printf '%s' "$INPUT" | jq -r '.tool_input.description // empty' 2>/dev/null)"
|
|
AGENT_TYPE="$(printf '%s' "$INPUT" | jq -r '.tool_input.subagent_type // "fork"' 2>/dev/null)"
|
|
if [[ -n "$AGENT_ID" ]]; then
|
|
TASK_START="$JOURNAL_DIR/.task-${AGENT_ID}.start"
|
|
printf '{"id":"%s","desc":"%s","type":"%s","start_epoch":%s}' \
|
|
"$AGENT_ID" "$DESC" "$AGENT_TYPE" "$NOW_EPOCH" > "$TASK_START"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
PostToolUse)
|
|
TOOL_NAME="$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
if [[ "$TOOL_NAME" = "Agent" ]]; then
|
|
AGENT_ID="$(printf '%s' "$INPUT" | jq -r '.tool_use_id // empty' 2>/dev/null)"
|
|
TASK_START="$JOURNAL_DIR/.task-${AGENT_ID}.start"
|
|
if [[ -f "$TASK_START" ]]; then
|
|
START_RAW="$(cat "$TASK_START")"
|
|
START_EPOCH="$(echo "$START_RAW" | jq -r '.start_epoch')"
|
|
DESC="$(echo "$START_RAW" | jq -r '.desc')"
|
|
AGENT_TYPE="$(echo "$START_RAW" | jq -r '.type')"
|
|
DURATION=$((NOW_EPOCH - START_EPOCH))
|
|
printf '{"kind":"task","id":"%s","desc":"%s","type":"%s","start_epoch":%s,"end_epoch":%s,"duration_s":%s,"ts":"%s"}\n' \
|
|
"$AGENT_ID" "$DESC" "$AGENT_TYPE" "$START_EPOCH" "$NOW_EPOCH" "$DURATION" "$NOW_ISO" \
|
|
>> "$JOURNAL_DIR/tasks.jsonl"
|
|
rm -f "$TASK_START"
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Always exit 0 — this hook is observability, never blocks.
|
|
exit 0
|