oh-my-customcode 0.77.0 → 0.78.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +10 -2
- package/templates/.claude/hooks/scripts/agent-start-recorder.sh +40 -0
- package/templates/.claude/hooks/scripts/auto-continue-guard.sh +33 -0
- package/templates/.claude/hooks/scripts/stall-detection-advisor.sh +112 -0
- package/templates/.claude/hooks/scripts/task-outcome-recorder.sh +15 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/guides/hook-data-flow/README.md +135 -0
- package/templates/manifest.json +2 -2
package/README.md
CHANGED
|
@@ -235,7 +235,7 @@ Key rules: R010 (orchestrator never writes files), R009 (parallel execution mand
|
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
238
|
-
### Guides (
|
|
238
|
+
### Guides (32)
|
|
239
239
|
|
|
240
240
|
Reference documentation covering best practices, architecture decisions, and integration patterns. Located in `guides/` at project root, covering topics from agent design to CI/CD to observability.
|
|
241
241
|
|
|
@@ -292,7 +292,7 @@ your-project/
|
|
|
292
292
|
│ ├── specs/ # Extracted canonical specs
|
|
293
293
|
│ ├── contexts/ # 4 shared context files
|
|
294
294
|
│ └── ontology/ # Knowledge graph for RAG
|
|
295
|
-
└── guides/ #
|
|
295
|
+
└── guides/ # 32 reference documents
|
|
296
296
|
```
|
|
297
297
|
|
|
298
298
|
---
|
package/dist/cli/index.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -166,9 +166,13 @@
|
|
|
166
166
|
{
|
|
167
167
|
"type": "command",
|
|
168
168
|
"command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(echo \"$input\" | jq -r '.agent_type // \"unknown\"')\nmodel=$(echo \"$input\" | jq -r '.model // \"inherit\"')\ndesc=$(echo \"$input\" | jq -r '.description // \"\"' | head -c 40)\necho \"─── [SubagentStart] ${agent_type}:${model} | ${desc} ───\" >&2\necho \"$input\""
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"type": "command",
|
|
172
|
+
"command": "bash .claude/hooks/scripts/agent-start-recorder.sh"
|
|
169
173
|
}
|
|
170
174
|
],
|
|
171
|
-
"description": "HUD display
|
|
175
|
+
"description": "HUD display + agent start time recording for stall detection (R009)"
|
|
172
176
|
}
|
|
173
177
|
],
|
|
174
178
|
"SubagentStop": [
|
|
@@ -181,7 +185,11 @@
|
|
|
181
185
|
},
|
|
182
186
|
{
|
|
183
187
|
"type": "command",
|
|
184
|
-
"command": "
|
|
188
|
+
"command": "bash .claude/hooks/scripts/stall-detection-advisor.sh"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"type": "command",
|
|
192
|
+
"command": "bash .claude/hooks/scripts/auto-continue-guard.sh"
|
|
185
193
|
},
|
|
186
194
|
{
|
|
187
195
|
"type": "prompt",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Agent Start Recorder
|
|
5
|
+
# Trigger: SubagentStart
|
|
6
|
+
# Purpose: Record agent spawn time for stall detection duration calculations
|
|
7
|
+
# Protocol: stdin JSON -> record start time -> stdout pass-through, exit 0 always (R021 advisory)
|
|
8
|
+
|
|
9
|
+
command -v jq >/dev/null 2>&1 || { cat; exit 0; }
|
|
10
|
+
|
|
11
|
+
input=$(cat)
|
|
12
|
+
|
|
13
|
+
agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
|
|
14
|
+
model=$(echo "$input" | jq -r '.model // "inherit"')
|
|
15
|
+
description=$(echo "$input" | jq -r '.description // ""' | head -c 80)
|
|
16
|
+
|
|
17
|
+
AGENT_START_FILE="/tmp/.claude-agent-starts-${PPID}"
|
|
18
|
+
|
|
19
|
+
timestamp=$(date +%s)
|
|
20
|
+
|
|
21
|
+
entry=$(jq -cn \
|
|
22
|
+
--arg ts "$timestamp" \
|
|
23
|
+
--arg agent "$agent_type" \
|
|
24
|
+
--arg model "$model" \
|
|
25
|
+
--arg desc "$description" \
|
|
26
|
+
'{start_epoch: $ts, agent_type: $agent, model: $model, description: $desc}')
|
|
27
|
+
|
|
28
|
+
echo "$entry" >> "$AGENT_START_FILE"
|
|
29
|
+
|
|
30
|
+
# Ring buffer: 50 max
|
|
31
|
+
if [ -f "$AGENT_START_FILE" ]; then
|
|
32
|
+
line_count=$(wc -l < "$AGENT_START_FILE" | tr -d ' ')
|
|
33
|
+
if [ "$line_count" -gt 50 ]; then
|
|
34
|
+
tail -50 "$AGENT_START_FILE" > "${AGENT_START_FILE}.tmp"
|
|
35
|
+
mv "${AGENT_START_FILE}.tmp" "$AGENT_START_FILE"
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "$input"
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Auto-Continue Guard
|
|
5
|
+
# Trigger: SubagentStop
|
|
6
|
+
# Purpose: Count consecutive subagent completions and warn when auto-continue limit reached
|
|
7
|
+
# Protocol: stdin JSON -> count check -> stdout pass-through, exit 0 always (R021)
|
|
8
|
+
|
|
9
|
+
input=$(cat)
|
|
10
|
+
|
|
11
|
+
count_file="/tmp/.claude-loop-count-${PPID}"
|
|
12
|
+
|
|
13
|
+
# Reset counter if stale (>60s since last update)
|
|
14
|
+
if [ -f "$count_file" ]; then
|
|
15
|
+
last_mod=$(stat -c%Y "$count_file" 2>/dev/null || stat -f%m "$count_file" 2>/dev/null || echo 0)
|
|
16
|
+
now=$(date +%s)
|
|
17
|
+
if [ $((now - last_mod)) -gt 60 ]; then
|
|
18
|
+
echo 0 > "$count_file"
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Increment counter
|
|
23
|
+
count=$(cat "$count_file" 2>/dev/null || echo 0)
|
|
24
|
+
count=$((count + 1))
|
|
25
|
+
echo "$count" > "$count_file"
|
|
26
|
+
|
|
27
|
+
# Warn if limit reached
|
|
28
|
+
if [ "$count" -ge 4 ]; then
|
|
29
|
+
echo '[AutoContinue] SAFETY: auto-continue limit (3) reached. Pausing.' >&2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "$input"
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Stall Detection Advisor
|
|
5
|
+
# Trigger: SubagentStop
|
|
6
|
+
# Purpose: Detect stalled parallel agents and advise adaptive splitting (R009)
|
|
7
|
+
# Protocol: stdin JSON -> analyze durations -> stderr advisory, stdout pass-through, exit 0 always (R021)
|
|
8
|
+
|
|
9
|
+
# ORDERING: This hook MUST run AFTER task-outcome-recorder.sh in hooks.json SubagentStop array.
|
|
10
|
+
# Reason: This script removes consumed entries from AGENT_START_FILE; task-outcome-recorder reads them first.
|
|
11
|
+
|
|
12
|
+
command -v jq >/dev/null 2>&1 || { cat; exit 0; }
|
|
13
|
+
|
|
14
|
+
input=$(cat)
|
|
15
|
+
|
|
16
|
+
AGENT_START_FILE="/tmp/.claude-agent-starts-${PPID}"
|
|
17
|
+
DURATION_FILE="/tmp/.claude-agent-durations-${PPID}"
|
|
18
|
+
|
|
19
|
+
# Skip if no start records exist
|
|
20
|
+
[ -f "$AGENT_START_FILE" ] || { echo "$input"; exit 0; }
|
|
21
|
+
|
|
22
|
+
# --- 1. Extract completed agent info ---
|
|
23
|
+
agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
|
|
24
|
+
model=$(echo "$input" | jq -r '.model // "inherit"')
|
|
25
|
+
description=$(echo "$input" | jq -r '.description // ""' | head -c 80)
|
|
26
|
+
|
|
27
|
+
# --- 2. Calculate duration from start record ---
|
|
28
|
+
start_epoch=$(grep -F "\"agent_type\":\"${agent_type}\"" "$AGENT_START_FILE" 2>/dev/null | tail -1 | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
|
|
29
|
+
|
|
30
|
+
if [ "$start_epoch" = "0" ] || [ "$start_epoch" = "null" ]; then
|
|
31
|
+
echo "$input"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
now_epoch=$(date +%s)
|
|
36
|
+
duration_seconds=$((now_epoch - start_epoch))
|
|
37
|
+
|
|
38
|
+
# Guard against negative duration (NTP adjustment, clock skew)
|
|
39
|
+
if [ "$duration_seconds" -lt 0 ]; then duration_seconds=0; fi
|
|
40
|
+
|
|
41
|
+
# --- 3. Stall detection (BEFORE recording this agent's duration, so self is excluded from average) ---
|
|
42
|
+
# Need at least 1 completed peer to calculate average
|
|
43
|
+
if [ -f "$DURATION_FILE" ]; then
|
|
44
|
+
completed_count=$(wc -l < "$DURATION_FILE" | tr -d ' ')
|
|
45
|
+
else
|
|
46
|
+
completed_count=0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ "$completed_count" -ge 1 ]; then
|
|
50
|
+
# Calculate average duration of completed agents (null-safe)
|
|
51
|
+
avg_duration=$(jq -s '[.[].duration_seconds | numbers] | if length == 0 then 0 else add / length | floor end' "$DURATION_FILE" 2>/dev/null || echo "0")
|
|
52
|
+
|
|
53
|
+
if [ "$avg_duration" -gt 0 ]; then
|
|
54
|
+
stall_threshold=$((avg_duration * 2))
|
|
55
|
+
|
|
56
|
+
# Check for still-running agents (in start file but not in duration file)
|
|
57
|
+
if [ -f "$AGENT_START_FILE" ] && [ -s "$AGENT_START_FILE" ]; then
|
|
58
|
+
while IFS= read -r line; do
|
|
59
|
+
running_agent=$(echo "$line" | jq -r '.agent_type // ""' 2>/dev/null || true)
|
|
60
|
+
running_start=$(echo "$line" | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
|
|
61
|
+
running_desc=$(echo "$line" | jq -r '.description // ""' 2>/dev/null || true)
|
|
62
|
+
running_model=$(echo "$line" | jq -r '.model // "inherit"' 2>/dev/null || true)
|
|
63
|
+
|
|
64
|
+
if [ "$running_start" = "0" ] || [ "$running_start" = "null" ]; then continue; fi
|
|
65
|
+
|
|
66
|
+
elapsed=$((now_epoch - running_start))
|
|
67
|
+
|
|
68
|
+
if [ "$elapsed" -gt "$stall_threshold" ]; then
|
|
69
|
+
# --- Emit advisory (stderr) ---
|
|
70
|
+
echo "" >&2
|
|
71
|
+
echo "─── [Stall Detection Advisory] ───────────────────────────" >&2
|
|
72
|
+
echo " Stalled: ${running_agent}:${running_model} (${elapsed}s elapsed, 2x avg ${avg_duration}s)" >&2
|
|
73
|
+
echo " Description: ${running_desc}" >&2
|
|
74
|
+
echo " ⚡ Consider spawning independent pending tasks immediately" >&2
|
|
75
|
+
echo " R009 Adaptive Parallel Splitting applies" >&2
|
|
76
|
+
echo "──────────────────────────────────────────────────────────" >&2
|
|
77
|
+
echo "" >&2
|
|
78
|
+
fi
|
|
79
|
+
done < "$AGENT_START_FILE"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# --- 4. Record duration (AFTER stall detection so self is excluded from average) ---
|
|
85
|
+
duration_entry=$(jq -cn \
|
|
86
|
+
--arg agent "$agent_type" \
|
|
87
|
+
--arg model "$model" \
|
|
88
|
+
--arg desc "$description" \
|
|
89
|
+
--arg dur "$duration_seconds" \
|
|
90
|
+
--arg ts "$now_epoch" \
|
|
91
|
+
'{agent_type: $agent, model: $model, description: $desc, duration_seconds: ($dur | tonumber), timestamp: $ts}')
|
|
92
|
+
|
|
93
|
+
echo "$duration_entry" >> "$DURATION_FILE"
|
|
94
|
+
|
|
95
|
+
# Remove only first consumed start entry (preserve siblings for parallel same-type agents)
|
|
96
|
+
if [ -f "$AGENT_START_FILE" ]; then
|
|
97
|
+
awk -v pat="\"agent_type\":\"${agent_type}\"" 'found || $0 !~ pat { print; next } { found=1 }' "$AGENT_START_FILE" > "${AGENT_START_FILE}.tmp" 2>/dev/null || true
|
|
98
|
+
mv "${AGENT_START_FILE}.tmp" "$AGENT_START_FILE" 2>/dev/null || true
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Ring buffer: 50 max
|
|
102
|
+
if [ -f "$DURATION_FILE" ]; then
|
|
103
|
+
line_count=$(wc -l < "$DURATION_FILE" | tr -d ' ')
|
|
104
|
+
if [ "$line_count" -gt 50 ]; then
|
|
105
|
+
tail -50 "$DURATION_FILE" > "${DURATION_FILE}.tmp"
|
|
106
|
+
mv "${DURATION_FILE}.tmp" "$DURATION_FILE"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Pass through
|
|
111
|
+
echo "$input"
|
|
112
|
+
exit 0
|
|
@@ -67,6 +67,19 @@ else
|
|
|
67
67
|
fi
|
|
68
68
|
fi
|
|
69
69
|
|
|
70
|
+
# Duration calculation from start recorder
|
|
71
|
+
# ORDERING: This script MUST run BEFORE stall-detection-advisor.sh in hooks.json SubagentStop array.
|
|
72
|
+
# Reason: stall-detection-advisor removes consumed entries from AGENT_START_FILE after reading.
|
|
73
|
+
AGENT_START_FILE="/tmp/.claude-agent-starts-${PPID}"
|
|
74
|
+
duration_seconds=0
|
|
75
|
+
if [ -f "$AGENT_START_FILE" ]; then
|
|
76
|
+
start_epoch=$(grep -F "\"agent_type\":\"${agent_type}\"" "$AGENT_START_FILE" 2>/dev/null | tail -1 | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
|
|
77
|
+
if [ "$start_epoch" != "0" ] && [ "$start_epoch" != "null" ]; then
|
|
78
|
+
now_epoch=$(date +%s)
|
|
79
|
+
duration_seconds=$((now_epoch - start_epoch))
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
70
83
|
# Append JSON line entry
|
|
71
84
|
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
72
85
|
entry=$(jq -n \
|
|
@@ -78,7 +91,8 @@ entry=$(jq -n \
|
|
|
78
91
|
--arg skill "$skill_name" \
|
|
79
92
|
--arg desc "$description" \
|
|
80
93
|
--arg err "$error_summary" \
|
|
81
|
-
|
|
94
|
+
--arg dur "$duration_seconds" \
|
|
95
|
+
'{timestamp: $ts, agent_type: $agent, model: $model, outcome: $outcome, pattern_used: $pattern, skill: $skill, description: $desc, error_summary: $err, duration_seconds: ($dur | tonumber)}')
|
|
82
96
|
|
|
83
97
|
echo "$entry" >> "$OUTCOME_FILE"
|
|
84
98
|
|
package/templates/CLAUDE.md
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Hook Data Flow: Stall Detection Pipeline
|
|
2
|
+
|
|
3
|
+
Added in v0.78.0. Documents the three-script pipeline that detects stalled parallel agents and emits R009 Adaptive Parallel Splitting advisories.
|
|
4
|
+
|
|
5
|
+
Related rule: `.claude/rules/MUST-parallel-execution.md` (R009 Adaptive Parallel Splitting section)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
When multiple agents run in parallel, one agent may take significantly longer than its peers. The stall detection pipeline identifies this condition at the moment any agent completes and advises the orchestrator to spawn independent pending tasks immediately — without cancelling the stalled agent.
|
|
12
|
+
|
|
13
|
+
The pipeline spans two hook events and three scripts:
|
|
14
|
+
|
|
15
|
+
| Event | Script | Role |
|
|
16
|
+
|-------|--------|------|
|
|
17
|
+
| SubagentStart | `agent-start-recorder.sh` | Record spawn timestamp |
|
|
18
|
+
| SubagentStop (1st) | `task-outcome-recorder.sh` | Read start time, record outcome with duration |
|
|
19
|
+
| SubagentStop (2nd) | `stall-detection-advisor.sh` | Read start times, compare durations, emit advisory, consume start entry |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Data Flow
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
SubagentStart event
|
|
27
|
+
└─ agent-start-recorder.sh
|
|
28
|
+
reads: stdin JSON (agent_type, model, description)
|
|
29
|
+
writes: /tmp/.claude-agent-starts-$PPID (appends 1 JSON line)
|
|
30
|
+
|
|
31
|
+
SubagentStop event [hooks execute in array order — ordering is critical]
|
|
32
|
+
│
|
|
33
|
+
├─ [1] task-outcome-recorder.sh
|
|
34
|
+
│ reads: stdin JSON (agent_type, model, outcome)
|
|
35
|
+
│ reads: /tmp/.claude-agent-starts-$PPID (duration calc — entry still present)
|
|
36
|
+
│ writes: /tmp/.claude-task-outcomes-$PPID (appends 1 JSON line with duration_seconds)
|
|
37
|
+
│ writes: stderr (on failure only)
|
|
38
|
+
│
|
|
39
|
+
└─ [2] stall-detection-advisor.sh
|
|
40
|
+
reads: stdin JSON (agent_type, model, description)
|
|
41
|
+
reads: /tmp/.claude-agent-starts-$PPID (finds matching start entry for duration)
|
|
42
|
+
reads: /tmp/.claude-agent-durations-$PPID (peer durations for average calculation)
|
|
43
|
+
writes: /tmp/.claude-agent-durations-$PPID (appends completed agent's duration)
|
|
44
|
+
writes: /tmp/.claude-agent-starts-$PPID (removes consumed start entry)
|
|
45
|
+
writes: stderr (advisory block if stall detected — R021 advisory-only)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Stall Detection Logic
|
|
49
|
+
|
|
50
|
+
At SubagentStop, after at least one peer has already completed:
|
|
51
|
+
|
|
52
|
+
1. Calculate `avg_duration` from all entries in `.claude-agent-durations-$PPID`
|
|
53
|
+
2. Set `stall_threshold = avg_duration * 2`
|
|
54
|
+
3. Scan `.claude-agent-starts-$PPID` for agents not yet in the duration file (still running)
|
|
55
|
+
4. For each still-running agent where `elapsed > stall_threshold`, emit advisory to stderr
|
|
56
|
+
|
|
57
|
+
The current agent's duration is recorded *after* stall detection so it does not inflate the average for its own check.
|
|
58
|
+
|
|
59
|
+
### Advisory Output Format
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
─── [Stall Detection Advisory] ───────────────────────────
|
|
63
|
+
Stalled: {agent_type}:{model} ({elapsed}s elapsed, 2x avg {avg_duration}s)
|
|
64
|
+
Description: {description}
|
|
65
|
+
⚡ Consider spawning independent pending tasks immediately
|
|
66
|
+
R009 Adaptive Parallel Splitting applies
|
|
67
|
+
──────────────────────────────────────────────────────────
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Shared Files
|
|
73
|
+
|
|
74
|
+
| File | Writer | Readers | Lifecycle |
|
|
75
|
+
|------|--------|---------|-----------|
|
|
76
|
+
| `/tmp/.claude-agent-starts-$PPID` | `agent-start-recorder.sh` (append) | `task-outcome-recorder.sh` (read), `stall-detection-advisor.sh` (read + remove entry) | Session-scoped via PPID; ring buffer 50 entries; entry removed after `stall-detection-advisor` consumes it |
|
|
77
|
+
| `/tmp/.claude-task-outcomes-$PPID` | `task-outcome-recorder.sh` (append) | `feedback-collector.sh`, `eval-core-batch-save.sh` (at Stop) | Session-scoped via PPID; ring buffer 50 entries |
|
|
78
|
+
| `/tmp/.claude-agent-durations-$PPID` | `stall-detection-advisor.sh` (append) | `stall-detection-advisor.sh` (read for average calculation) | Session-scoped via PPID; ring buffer 50 entries |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Execution Order Requirements
|
|
83
|
+
|
|
84
|
+
The SubagentStop hook array in `hooks.json` defines a strict ordering:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"SubagentStop": [
|
|
88
|
+
{ "command": "bash .claude/hooks/scripts/task-outcome-recorder.sh" },
|
|
89
|
+
{ "command": "bash .claude/hooks/scripts/stall-detection-advisor.sh" },
|
|
90
|
+
...
|
|
91
|
+
]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**task-outcome-recorder MUST run before stall-detection-advisor.**
|
|
95
|
+
|
|
96
|
+
Reason: `stall-detection-advisor.sh` removes the matching start entry from `.claude-agent-starts-$PPID` after reading it (to prevent re-matching on the next SubagentStop). If the order were reversed, `task-outcome-recorder.sh` would find no start entry for the agent and would always record `duration_seconds=0`.
|
|
97
|
+
|
|
98
|
+
If the order is swapped:
|
|
99
|
+
- `task-outcome-recorder` records `duration_seconds=0` for all agents
|
|
100
|
+
- Model escalation decisions based on duration become unreliable
|
|
101
|
+
- No other visible error — silent data corruption
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Temp File Lifecycle
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Session start (PPID assigned)
|
|
109
|
+
│
|
|
110
|
+
├─ First SubagentStart → .claude-agent-starts-$PPID created
|
|
111
|
+
│
|
|
112
|
+
├─ First SubagentStop → .claude-task-outcomes-$PPID created
|
|
113
|
+
│ .claude-agent-durations-$PPID created
|
|
114
|
+
│
|
|
115
|
+
├─ Each SubagentStop → start entry consumed (removed by stall-detection-advisor)
|
|
116
|
+
│ duration entry appended
|
|
117
|
+
│ outcome entry appended
|
|
118
|
+
│
|
|
119
|
+
└─ Session end (PPID released)
|
|
120
|
+
Files remain in /tmp — OS cleans up on reboot
|
|
121
|
+
Ring buffers cap each file at 50 lines to bound growth
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
PPID (parent process ID) is used rather than PID (`$$`) to scope files to the Claude Code session rather than to individual script invocations. All three scripts use `${PPID}` consistently.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Design Principles
|
|
129
|
+
|
|
130
|
+
- **Advisory-only (R021):** All three scripts exit 0 unconditionally. A missing `jq` binary causes silent pass-through, not a blocked hook.
|
|
131
|
+
- **PPID scoping:** Isolates temp files per Claude Code session. Multiple concurrent sessions do not interfere.
|
|
132
|
+
- **Ring buffers:** Each temp file is capped at 50 lines via `tail -50` after each append. Prevents unbounded growth in long sessions with many agents.
|
|
133
|
+
- **grep -F for pattern matching:** Fixed-string matching in `agent-start-recorder` and `task-outcome-recorder` avoids regex injection from agent type names.
|
|
134
|
+
- **Self-exclusion from average:** `stall-detection-advisor` reads the duration file *before* appending its own entry, so the completing agent is never compared against itself.
|
|
135
|
+
- **Sibling preservation:** When removing a start entry, `awk` removes only the first matching line — preserving sibling entries when multiple agents of the same type run in parallel.
|
package/templates/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.78.1",
|
|
3
3
|
"lastUpdated": "2026-03-24T00:00:00.000Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"name": "guides",
|
|
25
25
|
"path": "guides",
|
|
26
26
|
"description": "Reference documentation",
|
|
27
|
-
"files":
|
|
27
|
+
"files": 32
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"name": "hooks",
|