KeiSeiKit-1.0/hooks/task-timer.sh
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
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.
2026-05-01 12:09:03 +08:00

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