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 CHANGED
@@ -235,7 +235,7 @@ Key rules: R010 (orchestrator never writes files), R009 (parallel execution mand
235
235
 
236
236
  ---
237
237
 
238
- ### Guides (31)
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/ # 31 reference documents
295
+ └── guides/ # 32 reference documents
296
296
  ```
297
297
 
298
298
  ---
package/dist/cli/index.js CHANGED
@@ -9325,7 +9325,7 @@ var init_package = __esm(() => {
9325
9325
  workspaces: [
9326
9326
  "packages/*"
9327
9327
  ],
9328
- version: "0.77.0",
9328
+ version: "0.78.1",
9329
9329
  description: "Batteries-included agent harness for Claude Code",
9330
9330
  type: "module",
9331
9331
  bin: {
package/dist/index.js CHANGED
@@ -1820,7 +1820,7 @@ var package_default = {
1820
1820
  workspaces: [
1821
1821
  "packages/*"
1822
1822
  ],
1823
- version: "0.77.0",
1823
+ version: "0.78.1",
1824
1824
  description: "Batteries-included agent harness for Claude Code",
1825
1825
  type: "module",
1826
1826
  bin: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.77.0",
6
+ "version": "0.78.1",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -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 when a subagent starts (complements PreToolUse Agent matcher)"
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": "count_file=\"/tmp/.claude-loop-count-$PPID\"; if [ -f \"$count_file\" ]; then last_mod=$(stat -c%Y \"$count_file\" 2>/dev/null || stat -f%m \"$count_file\" 2>/dev/null || echo 0); now=$(date +%s); if [ $((now - last_mod)) -gt 60 ]; then echo 0 > \"$count_file\"; fi; fi; count=$(cat \"$count_file\" 2>/dev/null || echo 0); count=$((count + 1)); echo \"$count\" > \"$count_file\"; if [ \"$count\" -ge 4 ]; then echo '[AutoContinue] SAFETY: auto-continue limit (3) reached. Pausing.' >&2; fi; cat"
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
- '{timestamp: $ts, agent_type: $agent, model: $model, outcome: $outcome, pattern_used: $pattern, skill: $skill, description: $desc, error_summary: $err}')
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
 
@@ -156,7 +156,7 @@ project/
156
156
  | +-- rules/ # 전역 규칙 (R000-R021)
157
157
  | +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
158
158
  | +-- contexts/ # 컨텍스트 파일 (ecomode)
159
- +-- guides/ # 레퍼런스 문서 (31 토픽)
159
+ +-- guides/ # 레퍼런스 문서 (32 토픽)
160
160
  ```
161
161
 
162
162
  ## 오케스트레이션
@@ -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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.77.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": 31
27
+ "files": 32
28
28
  },
29
29
  {
30
30
  "name": "hooks",