oh-my-customcode 1.1.43 → 1.1.45

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.
Files changed (28) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
  4. package/templates/.claude/hooks/hooks.json +22 -0
  5. package/templates/.claude/hooks/scripts/fail-axis-cause-advisor.sh +102 -0
  6. package/templates/.claude/hooks/scripts/failure-ledger.sh +75 -0
  7. package/templates/.claude/hooks/scripts/r007-r008-drift-advisor.sh +46 -6
  8. package/templates/.claude/hooks/scripts/session-env-check.sh +33 -5
  9. package/templates/.claude/hooks/scripts/session-reflection.sh +34 -11
  10. package/templates/.claude/hooks/scripts/user-prompt-preprocessor.sh +74 -15
  11. package/templates/.claude/rules/MAY-optimization.md +4 -2
  12. package/templates/.claude/rules/MUST-agent-design.md +5 -1
  13. package/templates/.claude/rules/MUST-agent-teams.md +4 -0
  14. package/templates/.claude/rules/MUST-completion-verification.md +25 -4
  15. package/templates/.claude/rules/MUST-continuous-improvement.md +12 -1
  16. package/templates/.claude/rules/MUST-enforcement-policy.md +7 -5
  17. package/templates/.claude/rules/MUST-orchestrator-coordination.md +34 -0
  18. package/templates/.claude/rules/MUST-parallel-execution.md +2 -0
  19. package/templates/.claude/rules/MUST-permissions.md +3 -1
  20. package/templates/.claude/rules/MUST-safety.md +3 -1
  21. package/templates/.claude/rules/MUST-sync-verification.md +10 -0
  22. package/templates/.claude/rules/SHOULD-ecomode.md +2 -0
  23. package/templates/.claude/rules/SHOULD-hud-statusline.md +1 -1
  24. package/templates/.claude/rules/SHOULD-memory-integration.md +11 -1
  25. package/templates/.claude/rules/SHOULD-verification-ladder.md +22 -0
  26. package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +19 -1
  27. package/templates/manifest.json +1 -1
  28. package/templates/workflows/auto-dev.yaml +19 -1
package/dist/cli/index.js CHANGED
@@ -241,7 +241,7 @@ var init_package = __esm(() => {
241
241
  workspaces: [
242
242
  "packages/*"
243
243
  ],
244
- version: "1.1.43",
244
+ version: "1.1.45",
245
245
  description: "Batteries-included agent harness for Claude Code",
246
246
  type: "module",
247
247
  bin: {
package/dist/index.js CHANGED
@@ -2031,7 +2031,7 @@ var package_default = {
2031
2031
  workspaces: [
2032
2032
  "packages/*"
2033
2033
  ],
2034
- version: "1.1.43",
2034
+ version: "1.1.45",
2035
2035
  description: "Batteries-included agent harness for Claude Code",
2036
2036
  type: "module",
2037
2037
  bin: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "1.1.43",
6
+ "version": "1.1.45",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -227,6 +227,16 @@
227
227
  }
228
228
  ],
229
229
  "description": "Inject session auto-fix findings into first user prompt (#838)"
230
+ },
231
+ {
232
+ "matcher": "*",
233
+ "hooks": [
234
+ {
235
+ "type": "command",
236
+ "command": "bash .claude/hooks/scripts/fail-axis-cause-advisor.sh"
237
+ }
238
+ ],
239
+ "description": "Advisory on cause-free progress nudges when this session has logged tool failures \u2014 delivers via hookSpecificOutput.additionalContext (#1561)"
230
240
  }
231
241
  ],
232
242
  "SubagentStart": [
@@ -621,6 +631,18 @@
621
631
  ],
622
632
  "description": "Print auto-dev token spend summary on session end (Issue #1057, advisory)"
623
633
  }
634
+ ],
635
+ "PostToolUseFailure": [
636
+ {
637
+ "matcher": "*",
638
+ "hooks": [
639
+ {
640
+ "type": "command",
641
+ "command": "bash .claude/hooks/scripts/failure-ledger.sh"
642
+ }
643
+ ],
644
+ "description": "FAIL-axis instrumentation \u2014 append tool failures to the error ledger (JSONL). Never blocks; feeds fail-axis-cause-advisor.sh (#1561)"
645
+ }
624
646
  ]
625
647
  }
626
648
  }
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # fail-axis-cause-advisor.sh — UserPromptSubmit: 원인 없는 재촉 발화 감지 (FAIL 축)
3
+ #
4
+ # 배경:
5
+ # 8주 세션 실측에서 사용자 발화 216턴 중 원인 분석 표현이 0건(error_cause_ratio = 0.000)이었다.
6
+ # 반면 "계속해"(14) / "ㄱㄱ"(5) / "계속 진행해"(5) 등 원인 진술 없는 진행 지시가 24건,
7
+ # 전체 발화의 11%를 차지했다. 도구 실패가 세션당 1.72건 발생하는데도 원인을 묻지 않고
8
+ # 재촉으로 통과시키는 패턴이다.
9
+ #
10
+ # 역할:
11
+ # (1) 짧은 진행 지시이고 (2) 원인 언급이 없으며 (3) 이 세션에 기록된 도구 실패가 있을 때,
12
+ # Claude에게 "진행 전에 사용자에게 원인 가설 한 줄을 되물어라"는 advisory를 전달한다.
13
+ #
14
+ # 왜 사용자가 아니라 Claude에게 전달하는가:
15
+ # hookSpecificOutput.additionalContext는 모델 컨텍스트로만 들어간다(사용자에게 표시되지 않음).
16
+ # 따라서 Claude가 사용자에게 되묻게 만들고, 사용자의 답변이 대화 로그에 사용자 발화로
17
+ # 남게 하는 우회 경로를 택한다. 이렇게 해야 실제 진단 행동과 계측 지표가 함께 개선된다.
18
+ #
19
+ # 왜 차단하지 않는가:
20
+ # decision:"block"을 쓰면 프롬프트 자체가 거부되어 자율 루프(/fsd)가 멈춘다.
21
+ # R021 advisory-first 원칙에 따라 절대 차단하지 않고 exit 0을 유지한다.
22
+ #
23
+ # 의존:
24
+ # failure-ledger.sh(PostToolUseFailure)가 기록한 원장을 발동 조건으로 읽는다.
25
+ # 원장이 없으면 조용히 통과한다 — 훅 단독으로도 안전하게 동작한다.
26
+ #
27
+ # 환경변수 override:
28
+ # OMCUSTOM_FAIL_ADVISOR=off — advisory 완전 비활성화
29
+ # OMCUSTOM_ERROR_LEDGER=<path> — 원장 경로 override
30
+
31
+ set -euo pipefail
32
+
33
+ input=$(cat)
34
+
35
+ # ── Opt-out 체크 ──
36
+ if [ "${OMCUSTOM_FAIL_ADVISOR:-}" = "off" ]; then
37
+ exit 0
38
+ fi
39
+
40
+ if ! command -v jq >/dev/null 2>&1; then
41
+ exit 0
42
+ fi
43
+
44
+ prompt=$(printf '%s' "$input" | jq -r '.prompt // empty' 2>/dev/null) || exit 0
45
+ session=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null) || exit 0
46
+
47
+ if [ -z "$prompt" ] || [ -z "$session" ]; then
48
+ exit 0
49
+ fi
50
+
51
+ # ── 조건 1: 짧은 발화만 대상 (긴 발화는 이미 맥락을 담고 있다고 본다) ──
52
+ # 실측 p75가 27자이므로 40자를 상한으로 둔다.
53
+ if [ "${#prompt}" -gt 40 ]; then
54
+ exit 0
55
+ fi
56
+
57
+ # ── 조건 2: 진행/재촉 패턴인가 ──
58
+ if ! printf '%s' "$prompt" \
59
+ | grep -qiE '(계속|이어서|진행해|재개|다음|ㄱㄱ|고고|가자|continue|keep going|go on|resume|proceed|next)'; then
60
+ exit 0
61
+ fi
62
+
63
+ # ── 조건 3: 이미 원인/이유를 언급했다면 개입하지 않는다 (오탐 방지) ──
64
+ if printf '%s' "$prompt" \
65
+ | grep -qiE '(원인|이유|왜|때문|에러|오류|error|fail|because|cause)'; then
66
+ exit 0
67
+ fi
68
+
69
+ # ── 조건 4: 이 세션에 기록된 도구 실패가 있는가 ──
70
+ LEDGER="${OMCUSTOM_ERROR_LEDGER:-${HOME}/.claude/error-ledger.jsonl}"
71
+ if [ ! -f "$LEDGER" ]; then
72
+ exit 0
73
+ fi
74
+
75
+ # 원장 꼬리만 스캔한다 (전체 파일 스캔 회피).
76
+ # interrupt == true 는 사용자가 직접 중단시킨 것이므로 진단 대상 실패가 아니다 — 제외한다.
77
+ # (제외하지 않으면 사용자가 스스로 끊은 도구까지 "원인을 대라"고 되묻는 오탐이 된다.)
78
+ fail_count=$(tail -n 300 "$LEDGER" 2>/dev/null \
79
+ | jq -r --arg s "$session" 'select(.session == $s and .interrupt != true) | .tool' 2>/dev/null \
80
+ | wc -l | tr -d ' ') || fail_count=0
81
+
82
+ if [ -z "$fail_count" ] || [ "$fail_count" -eq 0 ] 2>/dev/null; then
83
+ exit 0
84
+ fi
85
+
86
+ # 최근 실패 도구 요약 (최대 3종)
87
+ fail_tools=$(tail -n 300 "$LEDGER" 2>/dev/null \
88
+ | jq -r --arg s "$session" 'select(.session == $s and .interrupt != true) | .tool' 2>/dev/null \
89
+ | sort | uniq -c | sort -rn | head -3 \
90
+ | awk '{printf "%s(%s) ", $2, $1}') || fail_tools=""
91
+
92
+ advisory_text=$(printf '[FAIL Advisory] 이 세션에 도구 실패 %s건이 기록되어 있습니다 (%s). 방금 입력은 원인 언급이 없는 진행 지시입니다. 곧바로 재시도하지 말고, 먼저 직전 실패의 원인 가설을 한 줄로 제시한 뒤 사용자에게 "이 진단이 맞는지 / 다른 원인이 짚이는지" 짧게 한 번만 확인하십시오. 사용자가 답하면 그대로 진행합니다. 이 확인은 한 턴을 넘기지 마십시오.' \
93
+ "$fail_count" "${fail_tools:-unknown}")
94
+
95
+ # 사람이 보는 감사 추적용 (exit 0에서 stderr는 모델에 전달되지 않음)
96
+ printf '%s\n' "$advisory_text" >&2
97
+
98
+ # 실제 전달 경로: additionalContext. decision 필드는 절대 포함하지 않는다.
99
+ jq -cn --arg ctx "$advisory_text" \
100
+ '{hookSpecificOutput: {hookEventName: "UserPromptSubmit", additionalContext: $ctx}}'
101
+
102
+ exit 0
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # failure-ledger.sh — PostToolUseFailure 에러 원장 기록 (FAIL 축 계측)
3
+ #
4
+ # 배경:
5
+ # hooks.json은 성공 경로(PreToolUse 12 / PostToolUse 16 / Stop 7)에는 촘촘히 배선되어
6
+ # 있으나 실패 경로에는 훅이 하나도 없었다. 그 결과 도구 실패가 세션당 평균 1.72건
7
+ # 발생함에도 아무 데이터도 남지 않아, R023 검증 래더의 상위 tier(에러 패턴 인식,
8
+ # post-mortem)를 측정할 근거 자체가 없는 상태였다.
9
+ #
10
+ # 역할:
11
+ # 도구 호출 실패 시 한 줄 JSONL을 원장에 append 한다. 이 원장은
12
+ # (1) recovery 성공률 산출, (2) 반복 실패 도구/명령 식별,
13
+ # (3) fail-axis-cause-advisor.sh(UserPromptSubmit)의 발동 조건으로 쓰인다.
14
+ #
15
+ # 설계 원칙:
16
+ # - 순수 append-only. 기존 파일을 읽거나 수정하지 않는다.
17
+ # - 네트워크 호출 없음. 외부 명령은 jq/date만 사용.
18
+ # - 어떤 실패에도 exit 0 — 원장 기록 실패가 본 작업을 막아서는 안 된다 (R021 advisory-first).
19
+ # - 명령/에러 문자열은 절단하여 기록한다 (원장 비대화 방지).
20
+ #
21
+ # 환경변수 override:
22
+ # OMCUSTOM_FAILURE_LEDGER=off — 기록 완전 비활성화
23
+ # OMCUSTOM_ERROR_LEDGER=<path> — 원장 경로 override (기본: ~/.claude/error-ledger.jsonl)
24
+
25
+ set -euo pipefail
26
+
27
+ input=$(cat)
28
+
29
+ # ── Opt-out 체크 ──
30
+ if [ "${OMCUSTOM_FAILURE_LEDGER:-}" = "off" ]; then
31
+ exit 0
32
+ fi
33
+
34
+ # ── jq 의존성 체크 (없으면 조용히 통과) ──
35
+ if ! command -v jq >/dev/null 2>&1; then
36
+ exit 0
37
+ fi
38
+
39
+ LEDGER="${OMCUSTOM_ERROR_LEDGER:-${HOME}/.claude/error-ledger.jsonl}"
40
+
41
+ if ! mkdir -p "$(dirname "$LEDGER")" 2>/dev/null; then
42
+ exit 0
43
+ fi
44
+
45
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")
46
+
47
+ # ── 한 줄 JSONL append ──
48
+ # 에러 필드 위치 (공식 문서 실측, code.claude.com/docs/en/hooks "PostToolUseFailure input"):
49
+ # PostToolUseFailure는 PostToolUse와 달리 tool_response를 보내지 않는다. 에러는
50
+ # **최상위 `error`** 문자열로 오고, 부수적으로 `is_interrupt` / `duration_ms`가 따라온다.
51
+ # 초판이 `.tool_response.error`를 읽어 err가 항상 빈 문자열이 되던 결함을 교정한 것이다
52
+ # (r007-r008-drift-advisor.sh의 `.role` vs `.message.role`과 동일 계열).
53
+ # .tool_error / .tool_response.* fallback은 스키마 변화에 대한 방어로만 남긴다 —
54
+ # tool_response가 문자열인 경우 인덱싱 에러로 레코드가 통째로 유실되므로 type 검사로 감싼다.
55
+ #
56
+ # 단일 라인(<1KB) append 이므로 O_APPEND 원자성에 기대어 병렬 에이전트 환경에서도 안전.
57
+ printf '%s' "$input" \
58
+ | jq -c --arg ts "$ts" --arg cwd "$PWD" '
59
+ {
60
+ ts: $ts,
61
+ session: (.session_id // ""),
62
+ cwd: $cwd,
63
+ tool: (.tool_name // "unknown"),
64
+ target: ((.tool_input.command // .tool_input.file_path // "") | tostring | .[0:160]),
65
+ interrupt: (.is_interrupt == true),
66
+ err: ((.error
67
+ // .tool_error
68
+ // (if (.tool_response | type) == "object"
69
+ then (.tool_response.error // .tool_response.stderr)
70
+ else .tool_response end)
71
+ // "")
72
+ | tostring | gsub("\\s+"; " ") | .[0:320])
73
+ }' >> "$LEDGER" 2>/dev/null || true
74
+
75
+ exit 0
@@ -46,7 +46,45 @@
46
46
  # with `tool_result` content blocks. Only a genuine prompt (string content, or an array
47
47
  # with no tool_result block) ends a turn.
48
48
  # * `thinking` blocks are interleaved with text/tool_use and never carry an R008 prefix;
49
- # they are filtered out before adjacency analysis.
49
+ # they are filtered out before analysis.
50
+ #
51
+ # ── R008 verdict: TURN-LEVEL COUNTING, not block adjacency (#1563 찐빠 #1) ─────────────
52
+ # R008 (`.claude/rules/MUST-tool-identification.md`) says, verbatim:
53
+ # "For parallel calls: list ALL identifications BEFORE the tool calls."
54
+ # The rule therefore requires the announce lines of a parallel BATCH to be grouped ahead of
55
+ # the batch — it does NOT require a text block wedged immediately before every single
56
+ # tool_use. The previous implementation tested block ADJACENCY (`$blocks[i-1]` is a text
57
+ # block matching the prefix), so in a compliant parallel batch `[text, tool_use, tool_use]`
58
+ # every tool_use after the first had a `tool_use` predecessor and was counted as a violation
59
+ # — R009 MANDATES those batches, so the advisor fired against rule-compliant behavior
60
+ # (measured: 212 bytes on a compliant live turn, 348 on a synthetic fixture; both must be 0).
61
+ #
62
+ # The verdict is now a per-turn count comparison:
63
+ # violations = max(0, tool_use_blocks − announce_lines)
64
+ #
65
+ # Announce lines counted (over ALL text blocks of the turn, split into lines):
66
+ # * `[agent][model] → Tool: X` — the Core Rule form; ONE per tool call.
67
+ # * `→ Target:` is NOT counted. It is the COMPANION line of `→ Tool:` (Core Rule prints the
68
+ # pair), so counting it would score 2 per tool and silently mask real omissions.
69
+ # * Spawn notation from R008 §"Parallel Spawn Prefix Rule", which documents parallel Agent
70
+ # calls as a `[agent][model] → Spawning:` header followed by one indented
71
+ # `[N] subagent_type:model → description` line per agent — with NO `→ Tool: Agent` line.
72
+ # The per-agent unit is the numbered line, so those are counted when present; the bare
73
+ # `→ Spawning:` header counts only when no numbered line exists (single-agent spawn, which
74
+ # R008 explicitly exempts from the `[N]` prefix). Excluding this notation would recreate
75
+ # exactly the false positive this fix removes (N Agent tool_use blocks, 0 `Tool:` lines).
76
+ #
77
+ # Tool calls EXCLUDED from the denominator:
78
+ # * `Skill` — R008 §"Tier-3 Interaction Tool Prefix" exempts it verbatim:
79
+ # "Skill | NO separate R008 prefix — identified via R007 `claude → {skill-name}`
80
+ # integrated header instead"
81
+ # A compliant skill invocation therefore emits a `┌─ Agent: claude → homework` header and
82
+ # NO `→ Tool:` line, so counting the Skill tool_use scored `R008 접두사=1` against a turn
83
+ # that follows the rule exactly (verified against the R008 table, #1569). The exclusion
84
+ # lives in the SHARED verdict, so session-reflection.sh carries the identical select or
85
+ # the next copy re-contaminates.
86
+ #
87
+ # R007 detection (first line of the turn's first text block) is unchanged.
50
88
  #
51
89
  # ── Performance ───────────────────────────────────────────────────────────────────────
52
90
  # The previous implementation forked jq once PER LINE inside a `while read` loop (measured
@@ -149,11 +187,13 @@ split("\n")
149
187
  | (if ($ftext | length) == 0 then 0
150
188
  elif ($fline | test("^┌─ Agent:")) or ($fline | test("^\\[.+\\]")) then 0
151
189
  else 1 end) as $r007
152
- | ([ range(0; ($blocks | length))
153
- | select($blocks[.].type? == "tool_use")
154
- | select( (. == 0)
155
- or ($blocks[. - 1].type? != "text")
156
- or (((($blocks[. - 1].text?) // "") | test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?(Tool|Target):")) | not) ) ] | length) as $r008
190
+ | ([ $blocks[] | select(.type? == "text") | (.text? // "") ] | join("\n") | split("\n")) as $lines
191
+ | ([ $lines[] | select(test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?Tool:")) ] | length) as $an_tool
192
+ | ([ $lines[] | select(test("^[[:space:]]*\\[[0-9]+\\][[:space:]].*(→|->|—>)")) ] | length) as $an_spawn_item
193
+ | ([ $lines[] | select(test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?Spawning:")) ] | length) as $an_spawn_hdr
194
+ | ($an_tool + (if $an_spawn_item > 0 then $an_spawn_item else $an_spawn_hdr end)) as $announce
195
+ | ([ $blocks[] | select((.type? == "tool_use") and ((.name? // "") != "Skill")) ] | length) as $ntools
196
+ | (if $ntools > $announce then $ntools - $announce else 0 end) as $r008
157
197
  | [$tuuid, ($r007 | tostring), ($r008 | tostring)] | @tsv
158
198
  end
159
199
  '
@@ -82,8 +82,10 @@ if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/n
82
82
  mkdir -p "$SESSION_STATE_DIR"
83
83
 
84
84
  PROJECT_HASH=$(echo "$(pwd)" | md5 2>/dev/null || echo "$(pwd)" | md5sum 2>/dev/null | cut -c1-8)
85
- # md5 on macOS outputs "MD5 (stdin) = <hash>", extract just the hash
86
- PROJECT_HASH=$(echo "$PROJECT_HASH" | grep -oE '[a-f0-9]{32}' | cut -c1-8)
85
+ # md5 on macOS outputs "MD5 (stdin) = <hash>", extract just the hash.
86
+ # Guarded for the same reason as json_string_field below: on a platform where the fallback
87
+ # already yields an 8-char digest this grep matches nothing and would abort the hook.
88
+ PROJECT_HASH=$(echo "$PROJECT_HASH" | grep -oE '[a-f0-9]{32}' | cut -c1-8 || printf '')
87
89
  STATE_FILE="${SESSION_STATE_DIR}/${PROJECT_HASH}.last-head"
88
90
 
89
91
  CURRENT_HEAD=$(git log -1 --format="%H" 2>/dev/null || echo "")
@@ -126,15 +128,41 @@ OMCUSTOM_UPDATE_STATUS="unknown"
126
128
  INSTALLED_VERSION=""
127
129
  CACHED_LATEST=""
128
130
 
131
+ # Extract a "<key>": "<value>" pair from a JSON file using grep only (no jq dependency).
132
+ # EVERY grep here is guarded with `|| printf ''`: this script runs under `set -euo pipefail`,
133
+ # where an unmatched grep exits 1 and (via pipefail) kills the ENTIRE SessionStart hook —
134
+ # measured as exit 1 with 0 bytes of stdout and 11 failing tests, including the script's own
135
+ # "should always exit with code 0" contract (#1570).
136
+ json_string_field() {
137
+ local file="$1" key="$2"
138
+ grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$file" 2>/dev/null \
139
+ | head -1 \
140
+ | grep -o '"[^"]*"$' \
141
+ | tr -d '"' \
142
+ || printf ''
143
+ }
144
+
129
145
  # Read installed version from .omcustomrc.json
130
146
  if [ -f ".omcustomrc.json" ]; then
131
- INSTALLED_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' .omcustomrc.json 2>/dev/null | head -1 | grep -o '"[^"]*"$' | tr -d '"')
147
+ INSTALLED_VERSION=$(json_string_field ".omcustomrc.json" "version")
132
148
  fi
133
149
 
134
- # Read cached latest version (no network call)
150
+ # Read cached latest version (no network call).
151
+ #
152
+ # Schema note (MEASURED 2026-08-10, #1570): TWO writers produce this exact path with
153
+ # DIFFERENT key names, and BOTH are current — neither is a legacy schema:
154
+ # * .claude/hooks/scripts/omcustom-auto-update.sh → {"version","timestamp","source"}
155
+ # * src/core/self-update.ts writeCache() → {"checkedAt","latestVersion"}
156
+ # The live cache on this machine was the auto-update shape ({source,timestamp,version}), so
157
+ # the hard-coded "latestVersion" lookup matched nothing. Read `latestVersion` first, then fall
158
+ # back to `version`. The `"version"` pattern includes the opening quote, so it cannot
159
+ # accidentally match the tail of `"latestVersion"`.
135
160
  CACHE_FILE="$HOME/.oh-my-customcode/self-update-cache.json"
136
161
  if [ -f "$CACHE_FILE" ]; then
137
- CACHED_LATEST=$(grep -o '"latestVersion"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_FILE" 2>/dev/null | grep -o '"[^"]*"$' | tr -d '"')
162
+ CACHED_LATEST=$(json_string_field "$CACHE_FILE" "latestVersion")
163
+ if [ -z "$CACHED_LATEST" ]; then
164
+ CACHED_LATEST=$(json_string_field "$CACHE_FILE" "version")
165
+ fi
138
166
  fi
139
167
 
140
168
  if [ -n "$INSTALLED_VERSION" ] && [ -n "$CACHED_LATEST" ]; then
@@ -89,11 +89,29 @@ ISO8601="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
89
89
  # 없다. 실제 경로는 `.message.role` / `.message.content`다. 종전 구현은 `.role`을 읽어 항상 빈 값을
90
90
  # 얻었고, 그 결과 assistant 라인이 하나도 매칭되지 않아 이 분석기는 사실상 0계층 탐지였다.
91
91
  #
92
- # 그리고 content 블록은 라인당 1개다 — 한 assistant 턴이 여러 줄에 걸친다. 따라서 라인 단위로
93
- # R008 인접성(직전 블록이 text인지)을 보면 모든 tool_use가 영구 위반으로 집계된다. 턴을 먼저
94
- # 복원한 뒤 블록 인접성을 본다. 턴 경계는 "진짜 user 프롬프트"(문자열 content 또는 tool_result가
95
- # 없는 배열)이며, tool_result user 라인은 경계가 아니다. `isSidechain: true`(서브에이전트 턴)은
96
- # 제외한다. `thinking` 블록은 R008 접두사를 가질 수 없으므로 인접성 판정 전에 제거한다.
92
+ # 그리고 content 블록은 라인당 1개다 — 한 assistant 턴이 여러 줄에 걸친다. 따라서 턴을 먼저
93
+ # 복원한 뒤 판정한다. 턴 경계는 "진짜 user 프롬프트"(문자열 content 또는 tool_result가 없는
94
+ # 배열)이며, tool_result user 라인은 경계가 아니다. `isSidechain: true`(서브에이전트 턴)은
95
+ # 제외한다. `thinking` 블록은 R008 접두사를 가질 수 없으므로 판정 전에 제거한다.
96
+ #
97
+ # R008 판정은 블록 인접성이 아니라 **턴 단위 개수 비교**다 (#1563 찐빠 #1). R008 원문은
98
+ # "For parallel calls: list ALL identifications BEFORE the tool calls." 즉 병렬 배치의 식별을
99
+ # 배치 앞에 모아 나열하라는 규칙이지, 매 tool_use 직전에 text 블록을 끼우라는 요구가 아니다.
100
+ # 인접성 판정은 R009가 MUST로 요구하는 병렬 배치 `[text, tool_use, tool_use]`에서 2번째 이후
101
+ # tool_use를 무조건 위반으로 집계했다. 새 판정: violations = max(0, tool_use 수 − announce 수).
102
+ # announce 인정 범위: `[agent][model] → Tool:` 라인(도구당 1개). `→ Target:`은 `→ Tool:`의
103
+ # 동반 라인이라 중복 계수 금지(도구당 2로 세어 누락을 놓침). R008 §"Parallel Spawn Prefix Rule"이
104
+ # 규정한 spawn 표기(`→ Spawning:` 헤더 + 에이전트당 `[N] type:model → desc` 라인, `→ Tool: Agent`
105
+ # 없음)도 포함 — 에이전트당 단위는 번호 라인이고, 번호 라인이 없을 때만(단일 spawn) 헤더를 1로 센다.
106
+ # 위반 샘플은 announce가 모자란 만큼 턴의 마지막 tool_use들을 보고한다(결손 개수 기준).
107
+ #
108
+ # 분모에서 제외하는 도구: `Skill` (#1569). R008 §"Tier-3 Interaction Tool Prefix" 표는 Skill을
109
+ # 명시 면제한다 — "Skill | NO separate R008 prefix — identified via R007 `claude → {skill-name}`
110
+ # integrated header instead". 즉 준수한 스킬 호출은 `┌─ Agent: claude → homework` 통합 헤더만
111
+ # 남기고 `→ Tool:` 라인을 쓰지 않으므로, Skill tool_use를 세면 규칙을 정확히 지킨 턴에
112
+ # `R008 접두사=1` 오탐이 찍힌다.
113
+ #
114
+ # 이 판정은 r007-r008-drift-advisor.sh와 공유된다 — 한쪽만 고치면 재오염된다.
97
115
  #
98
116
  # 성능: 줄마다 jq를 포크하던 구조를 jq 1회 포크로 교체.
99
117
  JQ_REFLECT='
@@ -120,12 +138,17 @@ split("\n")
120
138
  and ((($fline | test("^┌─ Agent:")) or ($fline | test("^\\[.+\\]"))) | not)
121
139
  then [ {k: "R007", turn: ($ti + 1), s: ($fline[0:120])} ]
122
140
  else [] end )
123
- + [ range(0; ($blocks | length))
124
- | select($blocks[.].type? == "tool_use")
125
- | select( (. == 0)
126
- or ($blocks[. - 1].type? != "text")
127
- or (((($blocks[. - 1].text?) // "") | test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?(Tool|Target):")) | not) )
128
- | {k: "R008", turn: ($ti + 1), s: ($blocks[.].name? // "")} ]
141
+ + ( ([ $blocks[] | select(.type? == "text") | (.text? // "") ] | join("\n") | split("\n")) as $lines
142
+ | ([ $lines[] | select(test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?Tool:")) ] | length) as $an_tool
143
+ | ([ $lines[] | select(test("^[[:space:]]*\\[[0-9]+\\][[:space:]].*(→|->|—>)")) ] | length) as $an_spawn_item
144
+ | ([ $lines[] | select(test("\\[.+\\]\\[.+\\] ?(→|->|—>) ?Spawning:")) ] | length) as $an_spawn_hdr
145
+ | ($an_tool + (if $an_spawn_item > 0 then $an_spawn_item else $an_spawn_hdr end)) as $announce
146
+ | ([ $blocks[] | select((.type? == "tool_use") and ((.name? // "") != "Skill")) ]) as $tus
147
+ | (if ($tus | length) > $announce then ($tus | length) - $announce else 0 end) as $r008n
148
+ | if $r008n > 0
149
+ then [ $tus[(($tus | length) - $r008n):][]
150
+ | {k: "R008", turn: ($ti + 1), s: (.name? // "")} ]
151
+ else [] end )
129
152
  ]
130
153
  | flatten
131
154
  | . as $viol
@@ -1,32 +1,91 @@
1
- #!/bin/bash
2
- # UserPromptSubmit hook — advisory pre-processing of user input
3
- # Provides skill matching hints based on user input patterns
4
- # Advisory only — never blocks user prompt submission
1
+ #!/usr/bin/env bash
2
+ # user-prompt-preprocessor.sh — UserPromptSubmit hook: advisory hints from the user's prompt
3
+ #
4
+ # Advisory-only: ALWAYS exits 0, NEVER blocks prompt submission.
5
+ #
6
+ # ── Two measured defects this file fixes (#1568) ──────────────────────────────────────
7
+ #
8
+ # 1. SELECTOR. The previous implementation read `.user_input`, but the Claude Code
9
+ # UserPromptSubmit payload carries the text in `prompt`. The `[ -z "$user_input" ]` guard
10
+ # on the next line therefore ALWAYS took the early-return branch, so the detection blocks
11
+ # below were unreachable — the hook was a pure pass-through.
12
+ # Live probe (2026-08-10):
13
+ # platform-shaped {"prompt":"끝"} → NO hint (defect)
14
+ # script-shaped {"user_input":"끝"} → "[Hook] Session-end signal detected"
15
+ # `.prompt` is now read first, with `.user_input` retained as a fallback so any
16
+ # script-shaped caller (and the pre-existing test corpus) keeps working.
17
+ #
18
+ # 2. DELIVERY CHANNEL. The hints were written to stderr only. Per the official hook spec,
19
+ # stderr on exit 0 is NEVER fed into the model's context for ANY event — it is visible in
20
+ # transcript debug mode, i.e. to a human, not to Claude. Fixing the selector alone would
21
+ # have left the hook functionally silent. Hints are now delivered through
22
+ # `hookSpecificOutput.additionalContext` (JSON on stdout, exit 0), the same contract used
23
+ # by the sibling UserPromptSubmit advisors r007-r008-drift-advisor.sh and
24
+ # fail-axis-cause-advisor.sh. The stderr line is kept purely as a human audit trail.
25
+ #
26
+ # `hookEventName` MUST echo the ACTUAL firing event — a wrong value invalidates the
27
+ # output — so a missing `hook_event_name` is a hard `exit 0` with no guessed default.
28
+ # A top-level `decision` field is NEVER emitted: `"decision": "block"` would turn this
29
+ # advisory into an enforcement gate, exactly what R021 (advisory-first) forbids.
30
+ #
31
+ # ── Why the stdin pass-through (`echo "$input"`) was removed ──────────────────────────
32
+ # It was never part of the UserPromptSubmit contract. For UserPromptSubmit, plain stdout on
33
+ # exit 0 is injected into the model's context, so echoing the payload back would inject the
34
+ # raw hook JSON as context noise. The repo's own convention agrees: of the four scripts wired
35
+ # to UserPromptSubmit in .claude/hooks/hooks.json, the three that were written or repaired
36
+ # against the measured spec — r007-r008-drift-advisor.sh, session-autofix-prompt.sh,
37
+ # fail-axis-cause-advisor.sh — all exit 0 WITHOUT echoing stdin. This file was the only
38
+ # hold-out. Pass-through echo is a Stop-hook idiom (see session-reflection.sh, which chains),
39
+ # not a UserPromptSubmit one.
5
40
 
41
+ set -euo pipefail
42
+
43
+ # ── stdin 읽기 ──
6
44
  input=$(cat)
7
- user_input=$(echo "$input" | jq -r '.user_input // ""' 2>/dev/null)
45
+
46
+ # ── jq 의존성 체크 (graceful degrade) ──
47
+ if ! command -v jq >/dev/null 2>&1; then
48
+ exit 0
49
+ fi
50
+
51
+ # ── 입력 필드 추출 ──
52
+ # `.prompt`(플랫폼 실제 필드)를 우선 읽고 `.user_input`(스크립트형 호출자)로 폴백한다.
53
+ # prompt 본문은 탭/개행을 포함할 수 있으므로 @tsv 로 묶지 않고 개별 추출한다.
54
+ user_input=$(printf '%s' "$input" | jq -r '(.prompt // .user_input // "")' 2>/dev/null) || exit 0
55
+ hook_event_name=$(printf '%s' "$input" | jq -r '(.hook_event_name // "")' 2>/dev/null) || exit 0
8
56
 
9
57
  if [ -z "$user_input" ]; then
10
- echo "$input"
11
58
  exit 0
12
59
  fi
13
60
 
14
- # Detect common patterns and provide advisory hints
61
+ # ── 패턴 탐지 ──
15
62
  hints=""
16
63
 
17
64
  # Korean session-end signals
18
- if echo "$user_input" | grep -qiE '(끝|종료|마무리|done|wrap up|end session)'; then
19
- hints="${hints}[Hook] Session-end signal detected — R011 memory saves will be triggered\n"
65
+ if printf '%s' "$user_input" | grep -qiE '(끝|종료|마무리|done|wrap up|end session)'; then
66
+ hints="${hints}[Hook] Session-end signal detected — R011 memory saves will be triggered"$'\n'
20
67
  fi
21
68
 
22
69
  # Workflow invocation
23
- if echo "$user_input" | grep -qE '^/'; then
24
- hints="${hints}[Hook] Slash command detected\n"
70
+ if printf '%s\n' "$user_input" | grep -qE '^/'; then
71
+ hints="${hints}[Hook] Slash command detected"$'\n'
25
72
  fi
26
73
 
27
- # Output hints to stderr (advisory)
28
- if [ -n "$hints" ]; then
29
- printf "%b" "$hints" >&2
74
+ if [ -z "$hints" ]; then
75
+ exit 0
30
76
  fi
31
77
 
32
- echo "$input"
78
+ # 사람이 보는 감사 추적용 (exit 0에서는 모델에 전달되지 않음 — 위 주석 2번 참고)
79
+ printf '%s' "$hints" >&2
80
+
81
+ # hook_event_name 이 없으면 hookSpecificOutput.hookEventName 을 정확히 채울 수 없다.
82
+ # 잘못된 기본값은 출력을 무효화하므로 추측하지 않는다.
83
+ if [ -z "$hook_event_name" ]; then
84
+ exit 0
85
+ fi
86
+
87
+ # 실제 전달 경로: additionalContext. decision 필드는 절대 포함하지 않는다 (R021).
88
+ jq -cn --arg event "$hook_event_name" --arg ctx "$hints" \
89
+ '{hookSpecificOutput: {hookEventName: $event, additionalContext: $ctx}}'
90
+
91
+ exit 0
@@ -32,12 +32,14 @@
32
32
  > **v2.1.206+**: `/doctor`에 checked-in CLAUDE.md에서 코드베이스로부터 파생 가능한 내용을 잘라내도록 제안하는 체크가 추가되었습니다 — R005 "Context Optimization via HTML Comments"의 컨텍스트 절감 원칙과 정합(모델 불필요 메타데이터 축소).
33
33
  -->
34
34
 
35
- > **v2.1.208+**: Fixed several tool-reliability bugs: env vars like `CLAUDE_CODE_MAX_OUTPUT_TOKENS` silently used only the mantissa of scientific-notation values (`1e6` became `1`); Edit now succeeds on a file modified after being read, as long as the target text still matches uniquely; Read no longer misreports empty files as "shorter than offset"; Grep no longer silently returns "No files found" for invalid regex, no longer under-reports paginated count-mode totals; and Glob no longer crashes on a null byte in pattern/path/cwd.
35
+ <!-- RETIRED (은퇴 릴리즈 v1.1.44, 보존 기준 v2.1.212 미만): > **v2.1.208+**: Fixed several tool-reliability bugs: env vars like `CLAUDE_CODE_MAX_OUTPUT_TOKENS` silently used only the mantissa of scientific-notation values (`1e6` became `1`); Edit now succeeds on a file modified after being read, as long as the target text still matches uniquely; Read no longer misreports empty files as "shorter than offset"; Grep no longer silently returns "No files found" for invalid regex, no longer under-reports paginated count-mode totals; and Glob no longer crashes on a null byte in pattern/path/cwd. -->
36
36
 
37
- > **v2.1.210+**: Bash/PowerShell 명령이 timeout으로 auto-background될 때의 메시지가 개선되어 모델이 hang과 명시적 background 요청을 구분할 수 있으며, auto-background된 명령 내 `cd`는 적용되지 않고 tool result가 working directory 불변을 명시합니다 — auto-background 이후 cwd 의존 후속 명령은 절대 경로로 수행합니다. 또한 Grep content mode가 결과 끝을 지난 페이지네이션에서 "No matches found"를 반환하던 문제가 수정되었습니다(v2.1.208 Grep 페이지네이션 수정의 연장) — 구버전에서 이 응답은 "패턴 미존재"가 아니라 "페이지 끝"일 수 있습니다.
37
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.210+**: Bash/PowerShell 명령이 timeout으로 auto-background될 때의 메시지가 개선되어 모델이 hang과 명시적 background 요청을 구분할 수 있으며, auto-background된 명령 내 `cd`는 적용되지 않고 tool result가 working directory 불변을 명시합니다 — auto-background 이후 cwd 의존 후속 명령은 절대 경로로 수행합니다. 또한 Grep content mode가 결과 끝을 지난 페이지네이션에서 "No matches found"를 반환하던 문제가 수정되었습니다(v2.1.208 Grep 페이지네이션 수정의 연장) — 구버전에서 이 응답은 "패턴 미존재"가 아니라 "페이지 끝"일 수 있습니다. -->
38
38
 
39
39
  > **v2.1.212+**: MCP 도구 호출이 2분(기본값, `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`로 임계값 조정·비활성) 초과 시 자동으로 백그라운드로 이동해 세션이 계속 사용 가능해집니다 — 위 v2.1.210 Bash/PowerShell auto-background의 MCP 도구 확장. 느린 MCP 호출(ontology-rag `rebuild_ontology`, code-review-graph 인덱싱 등)을 hang으로 오판하지 말고, 2분 초과 시 백그라운드 전환을 전제로 후속 작업을 진행합니다.
40
40
 
41
+ > **v2.1.224+**: mid-turn에 연결된 MCP 도구가 **이름 고지 없이** tool search로 deferred되던 결함이 수정되었습니다. 구버전에서는 세션 도중 붙은 MCP 서버의 도구가 이름조차 노출되지 않아 "그런 도구 없음"으로 오판할 수 있었으므로, 위 tool-availability 주의(`command -v` 사전 확인과 동류)를 MCP 도구에도 적용합니다 — 도구 부재 결론 전에 `ToolSearch`로 실측합니다.
42
+
41
43
  ### Capability-Aware Tool Scheduling
42
44
 
43
45
  When dispatching parallel tool calls, consider per-tool capabilities to optimize scheduling:
@@ -64,6 +64,10 @@ Skill/rule text instructing "spawn with `model: opus`" refers to this tier — a
64
64
 
65
65
  > **v2.1.222+**: **org-restricted 환경에서** `model: opus` 계열 subagent/teammate의 family alias가 parent model로 떨어지던 문제가 수정되어, 이제 해당 family 내에서 org가 허용한 **최신 모델로 step-down**합니다. 이는 Tier 1의 "CC resolves these, not this project" 원칙을 강화하는 사례입니다 — Tier-1 alias 해석에는 **org 제한이라는 추가 변수**가 있어 프로젝트가 pin할 수 없으므로, 특정 모델을 확정하려면 frontmatter에 **Tier-2 full ID**를 씁니다. Agent 도구 spawn 파라미터(Tier 3)는 full ID를 받지 않으므로 이 경로에서는 alias 해석이 org 설정에 좌우됩니다. (본 저장소의 org 제한 여부는 미실측 — 위 조건절이 적용 범위입니다.)
66
66
 
67
+ > **v2.1.223+**: workflow agent · forked skill · slash command · 재개된 background agent가 **요청한 subagent 모델이 제한되어 parent model로 실행될 때 경고가 표시**됩니다. 위 v2.1.222 org step-down 노트의 직접 연장선으로, 이전에는 이 강등이 **무음**이었습니다 — 즉 "`model: opus`로 스폰했다"는 기록이 실제 실행 모델의 증거가 아니었습니다. 특정 모델을 확정하려면 frontmatter Tier-2 full ID를 쓰고, 실행 모델은 경고 표시 유무로 확인합니다(R020 "attempt ≠ outcome"의 모델 선택 각도).
68
+
69
+ > **v2.1.223+**: `CLAUDE_CODE_DISABLE_1M_CONTEXT`가 **native 1M 창을 가진 모든 Claude 모델**을 auto-compaction으로 200K에 유지하도록 확대되었습니다(이전에는 고정 모델 목록). 미인식 model ID도 가정 컨텍스트 창 내로 유지되며 `CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT=1`로 복원할 수 있습니다. 위 Tier-2 표의 `claude-sonnet-5`/`claude-opus-5`(native 1M)와 `[1m]` 접미사는 이 env가 설정된 환경에서 **실효 200K로 동작**하므로, 1M 전제의 대용량 컨텍스트 위임 전에 env 설정 여부를 확인합니다(cross-ref R013 context budget).
70
+
67
71
  > **Claude Fable 5 (access via CC v2.1.170+)**: Mythos-class model, GA on the Claude API and positioned as a tier above Opus — its capabilities exceed any previously GA model. CC v2.1.170 is the client version that adds access (the model's GA is an API/platform property, not a CC-release milestone). Available via frontmatter full ID `claude-fable-5` (Tier 2) or Agent tool `model: fable` (Tier 3) — NOT via a Tier-1 frontmatter alias. Reserve for the most complex reasoning where its capability premium is warranted; `sonnet` remains the default for general tasks and `opus` for architecture (cost/latency awareness, R005). CC v2.1.170 also fixes session transcripts not saving (and not appearing in `--resume`) when launched from a VS Code integrated terminal or any shell inheriting Claude Code env vars — relevant to transcript-dependent skills (`homework`, `episodic-memory`). Closes #1352.
68
72
 
69
73
  <!-- ARCHIVED CC version notes (historical):
@@ -484,7 +488,7 @@ Key optional fields: `scope`, `context`, `version`, `effort`, `model`, `agent`,
484
488
  > **v2.1.199+**: 스택된 slash-skill 호출(`/skill-a /skill-b do XYZ`)이 이제 leading skill을 최대 5개까지 모두 로드합니다(이전에는 첫 번째만 로드). oh-my-customcode의 라우팅 스킬 체이닝(예: `/omcustom:fsd`가 여러 스킬을 연쇄 호출하는 패턴)에서 다중 스킬 스택 호출 시 컨텍스트 손실이 줄어듭니다. 또한 subagent 조회 중 `/model`·`/fast`를 입력하면 lead의 model picker가 열리며 notice가 표시됩니다.
485
489
  -->
486
490
 
487
- > **v2.1.210+**: 스킬/커맨드 본문에서 인자 없이 호출된(unmatched) `$1`/`$2` positional placeholder가 조용히 제거되던(silently stripped) 동작이 수정되어 이제 리터럴 `$1`로 verbatim 보존됩니다 — 인자 부재 시 `$1`이 확장된 프롬프트에 그대로 남아 지시가 깨지므로, silent stripping에 옵션-인자 처리를 의존하지 말고 인자 부재 케이스를 명시 처리(default text / `$ARGUMENTS` guard / `argument-hint`)해야 합니다. (위 v2.1.163+ `\$1` escape는 항상 리터럴 `$` 출력용 별개 메커니즘으로 이번 변경 대상이 아니며, 이번 수정은 치환 의도의 bare `$1`이 unmatched일 때만 적용됩니다.)
491
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.210+**: 스킬/커맨드 본문에서 인자 없이 호출된(unmatched) `$1`/`$2` positional placeholder가 조용히 제거되던(silently stripped) 동작이 수정되어 이제 리터럴 `$1`로 verbatim 보존됩니다 — 인자 부재 시 `$1`이 확장된 프롬프트에 그대로 남아 지시가 깨지므로, silent stripping에 옵션-인자 처리를 의존하지 말고 인자 부재 케이스를 명시 처리(default text / `$ARGUMENTS` guard / `argument-hint`)해야 합니다. (위 v2.1.163+ `\$1` escape는 항상 리터럴 `$` 출력용 별개 메커니즘으로 이번 변경 대상이 아니며, 이번 수정은 치환 의도의 bare `$1`이 unmatched일 때만 적용됩니다.) -->
488
492
 
489
493
  > **v2.1.222+**: 스킬 frontmatter의 `disable-model-invocation: true`(모델이 스스로 그 스킬을 호출하지 못하게 막고 사용자/파이프라인의 명시적 호출만 허용하는 필드)가 설정된 스킬을 모델이 호출하려 할 때의 refusal 문구가 개선되어, 모델에게 **워크플로우를 스스로 복제하지 말고 사용자에게 실행을 요청하라**고 지시합니다. 무인 루프(`/fsd` 등)가 이런 스킬을 모델 호출 경로에 두면 실행 대신 refusal이 반환되므로, 해당 스킬은 **사용자/파이프라인 명시 호출**로 설계합니다.
490
494
 
@@ -40,6 +40,8 @@ Available when `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or TeamCreate/SendMessag
40
40
 
41
41
  These are distinct mechanisms. Agent Teams `SendMessage` requires `TeamCreate` and operates within a single Claude Code session. claude-peers-mcp `send_message` operates across separate Claude Code processes via a localhost broker.
42
42
 
43
+ > **v2.1.224/225+**: CC 네이티브 `SendMessage`가 **cross-session으로 확장**되었습니다(다른 머신 포함, macOS/Linux) — `ListAgents`로 대상을 열거하고 `crossSessionInbound` / `dialogExpiry` 설정으로 수신·만료를 제어합니다. 위 표의 "Cross-session = claude-peers-mcp 전용" 구분은 이제 **유일한 수단이 아니며**, 브로커 없이 네이티브 경로를 쓸 수 있습니다. 다만 위 Cross-Session Relay Authority Hardening(v2.1.166)의 권한 비전파 원칙은 네이티브 경로에도 동일하게 적용됩니다 — cross-session 메시지는 조율 신호이지 승인 채널이 아닙니다. (225) cross-session 메시지가 headless 세션·기동 중에 **고지도 만료도 없이 대기**하던 결함이 수정되었으므로, 구버전에서 "응답 없음"은 미수신이 아니라 무기한 대기였을 수 있습니다.
44
+
43
45
  ### Cross-Session Relay Authority Hardening (CC v2.1.166+)
44
46
 
45
47
  <!-- ARCHIVED CC version note (historical):
@@ -381,6 +383,8 @@ Agent Teams member completion MUST be verified by deterministic ground-truth —
381
383
 
382
384
  Cross-reference: R020 ("actual outcome ≠ attempt" — verifying that a command ran is not the same as verifying it succeeded).
383
385
 
386
+ > **v2.1.224+**: `SendMessage`가 **teammate inbox 쓰기에 실패해도 "Message sent"로 보고**하던 결함이 수정되어, 이제 실패가 오류로 보고됩니다. 위 표의 "SendMessage report = Low reliability"가 **전송 자체에도** 해당했다는 실증입니다 — 구버전에서는 "Message sent"가 수신은커녕 기록 성공조차 보장하지 않았습니다. 수정 후에도 전송 성공은 **수신자가 작업을 수행했다는 증거가 아니므로**, 위 표의 결정론적 ground-truth 확인은 그대로 유지합니다.
387
+
384
388
  > **v2.1.222+**: `SendMessage`가 긴 summary를 문자 수 제한으로 거부하던 동작이 **절단(truncate)**으로 변경되어 전송이 실패하지 않습니다. 전송 실패가 사라진 대신 **조용한 절단**이라는 새 실패 모드가 생겼으므로, 위 표의 "SendMessage report = Low reliability" 원칙이 오히려 강화됩니다. 긴 보고가 필요하면 SendMessage 본문 대신 아티팩트 파일 경로 전달(R006 Artifact Channel Protocol)로 대체합니다.
385
389
 
386
390
  <!-- ARCHIVED CC version note (historical):
@@ -73,9 +73,9 @@ Never accept "pre-existing" without direct base-branch evidence. A false "pre-ex
73
73
 
74
74
  ### Verification-Delegation Non-Termination (검증 위임 판정 종료 보장)
75
75
 
76
- 구조 검증(mgr-sauron R017)·판정·품질 게이트를 서브에이전트에 위임할 때, 위임 프롬프트에 **"최종 PASS/FAIL 판정 없이 turn을 종료하지 말라"**를 명시한다 — 단 이 clause는 **보조 수단**일 뿐 1차 방어선이 아니다. clause를 명시해도 mid-step 종료가 **누적 11회** 재발했다(v1.1.13/14/17/18/19 … v1.1.30, 아래 Origin 참조). **1차 방어선은 오케스트레이터의 직접 ground-truth 실측**이다.
76
+ 구조 검증(mgr-sauron R017)·판정·품질 게이트를 서브에이전트에 위임할 때, 위임 프롬프트에 **"최종 PASS/FAIL 판정 없이 turn을 종료하지 말라"**를 명시한다 — 단 이 clause는 **보조 수단**일 뿐 1차 방어선이 아니다. clause를 명시해도 mid-step 종료가 **누적 14회** 재발했다(v1.1.13/14/17/18/19 … v1.1.44, 아래 Origin 참조). **예방의 1차 방어선은 위임 경계 분할**(아래 「위임 경계를 Phase 개수로 설계」)이고, **사후 1차 방어선은 오케스트레이터의 직접 ground-truth 실측**이다.
77
77
 
78
- mid-step 종료는 예상 가능한 정상 실패 모드로 취급한다 — 발생 시 즉시 ground-truth를 실측해 실제 진행 상태를 확인한다. **증상만으로 결과를 넘겨짚지 않는다**: 같은 "...중" 한 줄 종료라도 실측 결과는 다를 수 있다(예: "merging now" 후 종료 → 실측 시 PR 이미 MERGED, resume 불필요 / "CI 실행 중" 후 종료 → 실측 시 PR OPEN 미머지, resume 필요). 미완이면 SendMessage로 resume하되, 오케스트레이터가 실측한 값(예: "CI 전부 통과, mergeStateStatus=CLEAN")을 resume 메시지에 동봉해 에이전트가 재폴링 후 재종료하는 루프를 끊는다.
78
+ mid-step 종료는 예상 가능한 정상 실패 모드로 취급한다 — 발생 시 즉시 ground-truth를 실측해 실제 진행 상태를 확인한다. **증상만으로 결과를 넘겨짚지 않는다**: 같은 "...중" 한 줄 종료라도 실측 결과는 **세 방향 모두** 관측됐다 — (a) 보고=완료("merging now" → 실측 시 PR 이미 MERGED, resume 불필요), (b) 보고=미완료("CI 실행 중" → 실측 시 PR OPEN 미머지, resume 필요), (c) **실제가 보고보다 앞섬**("커밋 1 완료, 커밋 2 스테이징으로 이어갑니다" → 실측 시 3개 커밋 전부 완료). 세 방향이 모두 나온 이상 증상 기반 진행도 추론은 **원리적으로 불가능**하며, 실측만이 유일한 판정 수단이다. 미완이면 SendMessage로 resume하되, 오케스트레이터가 실측한 값(예: "CI 전부 통과, mergeStateStatus=CLEAN")을 resume 메시지에 동봉해 에이전트가 재폴링 후 재종료하는 루프를 끊는다.
79
79
 
80
80
  | Anti-pattern | Required |
81
81
  |--------------|----------|
@@ -83,7 +83,17 @@ mid-step 종료는 예상 가능한 정상 실패 모드로 취급한다 — 발
83
83
  | mid-step 종료 증상(예: "merging now")으로 결과를 넘겨짚음 | 매번 `gh pr view`/`gh run list` 등으로 실측 후 완료/미완료 판정 |
84
84
  | resume 시 빈 재촉만 전달 | 실측값을 resume 메시지에 동봉해 재폴링 루프 차단 |
85
85
 
86
- Origin: #1443 (Session 126 회고 찐빠 #1) — v1.1.3 R017 검증에서 mgr-sauron이 source-hash 대조 중 판정 없이 종료 → resume 후 PASS. v1.1.4에서 "판정 반드시 출력" 명시로 1회 완료(대조 실증). **5회 재발 확인(#1492, Session 132)**: v1.1.13/14/17(clause 명시에도 재발) → v1.1.18(완료조건 6항목+종료금지 명시에도 "merging now" 한 줄 남기고 종료, 실측 결과 이미 완료) → v1.1.19(위임 프롬프트에 "4회 무시됨"까지 명시했으나 "CI 실행 중" 한 줄 남기고 종료, 실측 결과 미완료). Session 132에서 2회 모두 오케스트레이터 직접 실측으로 복구 — clause 강화가 아니라 실측 습관화가 유일하게 실증된 방어선. **누적 11회 확인(#1518 찐빠 #2, Session 136)**: v1.1.30 릴리즈 세션에서도 "완료 조건 5항목 실측 + 판정 없이 종료 금지" 명시에도 mgr-gitnerd가 "폴링 완료 통지를 기다리겠습니다" 한 줄만 남기고 종료 → 오케스트레이터 직접 실측으로 복구(lockfile push 완료 / CI pending / PR OPEN); 이번엔 "대기 중" 증상이 실제 미완료였고 Session 132의 "머지 중" 증상은 실제 완료였다는 대비로 증상→결과 추론 금지가 재확인됨.
86
+ #### 위임 경계를 Phase 개수로 설계 (예방 1차 방어선)
87
+
88
+ 다중 Phase 작업을 한 에이전트에 위임하면 **Phase 경계가 곧 종료 유혹 지점**이 된다 — 완료 조건 번호 명시와 종료 금지 clause를 넣어도 동일하다. 위임 단위는 **단일 목표 1개**로 자르고, Phase가 2개 이상이면 분할해 순차 발주한다.
89
+
90
+ | Anti-pattern | Required |
91
+ |--------------|----------|
92
+ | 다중 Phase 작업(3-Phase 검증, 3-커밋 시퀀스)을 한 에이전트에 위임하고 clause로 종료를 막으려 함 | 위임 단위를 단일 목표 1개로 분할해 순차 발주 — 경계 분할이 clause 강화보다 실효적 |
93
+
94
+ 대조 실증(#1574, v1.1.44 세션): 단일 목표 위임(PR 생성 / 머지 / 브랜치 정리 / 버전 범프) **4건 전원 완주**, 다중 Phase 위임(mgr-sauron 3-Phase, mgr-gitnerd 3-커밋) **2건 모두 mid-step 종료**. 같은 세션에서 릴리즈 단계를 push+범프 / PR 생성 / 머지로 3분할한 것이 이 설계의 적용례다.
95
+
96
+ Origin: #1443 (Session 126 회고 찐빠 #1) — v1.1.3 R017 검증에서 mgr-sauron이 source-hash 대조 중 판정 없이 종료 → resume 후 PASS. v1.1.4에서 "판정 반드시 출력" 명시로 1회 완료(대조 실증). **5회 재발 확인(#1492, Session 132)**: v1.1.13/14/17(clause 명시에도 재발) → v1.1.18(완료조건 6항목+종료금지 명시에도 "merging now" 한 줄 남기고 종료, 실측 결과 이미 완료) → v1.1.19(위임 프롬프트에 "4회 무시됨"까지 명시했으나 "CI 실행 중" 한 줄 남기고 종료, 실측 결과 미완료). Session 132에서 2회 모두 오케스트레이터 직접 실측으로 복구 — clause 강화가 아니라 실측 습관화가 유일하게 실증된 방어선. **누적 11회 확인(#1518 찐빠 #2, Session 136)**: v1.1.30 릴리즈 세션에서도 "완료 조건 5항목 실측 + 판정 없이 종료 금지" 명시에도 mgr-gitnerd가 "폴링 완료 통지를 기다리겠습니다" 한 줄만 남기고 종료 → 오케스트레이터 직접 실측으로 복구(lockfile push 완료 / CI pending / PR OPEN); 이번엔 "대기 중" 증상이 실제 미완료였고 Session 132의 "머지 중" 증상은 실제 완료였다는 대비로 증상→결과 추론 금지가 재확인됨. **누적 14회 + 3방향째 확인(#1574, v1.1.44 세션)**: mgr-sauron 3-Phase / mgr-gitnerd 3-커밋 위임 2건이 Phase 경계에서 종료했고(위 「위임 경계를 Phase 개수로 설계」의 대조 실증), 그중 mgr-gitnerd는 "커밋 2로 이어가겠다"고 보고했으나 실측 시 3개 커밋이 이미 전부 완료 — 실제가 보고보다 앞서는 세 번째 방향.
87
97
 
88
98
  Cross-reference: R018 (Member Completion Verification), `feedback_release_delegation_phasing`, `feedback_orchestrator_direct_verify` (release delegation phasing을 verification 위임에도 확장).
89
99
 
@@ -93,7 +103,7 @@ Cross-reference: R018 (Member Completion Verification), `feedback_release_delega
93
103
  > **v2.1.200+**: rate limit으로 어떤 텍스트 출력도 내기 전에 잘린 subagent가 이전에는 빈 결과(empty result)를 반환하던 것을 clean failure로 반환하도록 수정되었습니다 — v2.1.199 partial-work 반환에 이어, 출력 이전 rate-limit 차단 시 조용한 빈 결과 대신 명시적 실패를 parent에 보고합니다. 플랫폼이 false-success/silent-empty 자가보고를 추가로 줄였으나, "actual outcome ≠ attempt" ground-truth 검증 원칙(R020 Core Rule)은 여전히 유지됩니다 — subagent 보고를 그대로 신뢰하지 말고 git status/grep/validation script로 재확인합니다.
94
104
  -->
95
105
 
96
- > **v2.1.211+**: CC의 background agent 결과 보고가 개선되어, Claude가 아직 실행 중인 agent의 상태를 그대로 보고하고 **결과를 지어내지 않고 실제 완료를 기다립니다**(previously fabricated results). v2.1.199/200(false-success·silent-empty 자가보고 감소)에 이은 플랫폼 개선으로 오케스트레이터의 fabricated-completion 리스크를 추가로 낮추지만, "actual outcome ≠ attempt" ground-truth 검증 원칙(Core Rule)은 여전히 유지됩니다 — subagent/background agent 보고를 그대로 신뢰하지 말고 `git status`/`grep`/validation script로 재확인합니다.
106
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.211+**: CC의 background agent 결과 보고가 개선되어, Claude가 아직 실행 중인 agent의 상태를 그대로 보고하고 **결과를 지어내지 않고 실제 완료를 기다립니다**(previously fabricated results). v2.1.199/200(false-success·silent-empty 자가보고 감소)에 이은 플랫폼 개선으로 오케스트레이터의 fabricated-completion 리스크를 추가로 낮추지만, "actual outcome ≠ attempt" ground-truth 검증 원칙(Core Rule)은 여전히 유지됩니다 — subagent/background agent 보고를 그대로 신뢰하지 말고 `git status`/`grep`/validation script로 재확인합니다. -->
97
107
 
98
108
  ## Common False Completion Patterns — 8 anti-patterns including "Command executed" without exit code check, "Waiting for manual publish" when CI auto-publishes, "UI changes done" without browser render. See full table via Read tool.
99
109
 
@@ -261,6 +271,17 @@ Origin: #1266 ④.
261
271
 
262
272
  이는 Read-Before-Characterize의 **자기 적용** 각도다 — 진단 대상이 외부 로그가 아니라 자기 자신의 transcript일 때에도 "읽기 전 특성화 금지"가 동일하게 적용된다.
263
273
 
274
+ #### 자율 루프 세션의 턴 경계 정의 (계수 전 확정 필수)
275
+
276
+ 위 파싱 레시피는 **"사용자 프롬프트 = 턴 경계"**를 암묵 전제한다. `/fsd` 같은 자율 루프는 사용자 프롬프트가 거의 없어(실측: 사용자 프롬프트 4개 대 assistant 응답 30여 회) 이 전제로는 경계 재구성이 실패하고, 계수 자체가 성립하지 않는다. 자율 루프 transcript를 셀 때는 대안 경계 정의(예: **`tool_result` 직후 첫 text 블록을 응답 시작으로 간주**)를 먼저 확정하고, 정의를 확정하기 전에는 **위반 횟수를 단정하지 않는다**.
277
+
278
+ | Anti-pattern | Required |
279
+ |--------------|----------|
280
+ | 자율 루프 transcript를 사용자 프롬프트 경계로 파싱해 위반 N회로 단정 | 대안 경계 정의를 먼저 확정; 확정 전에는 횟수 단정 금지 |
281
+ | 경계 재구성 실패를 "위반 없음"으로 해석 | 경계 무관 지표로 대체 보고 — `┌─ Agent:` 헤더 총량, tool_use 대 announce 라인 비율 |
282
+
283
+ Origin: #1574 (v1.1.44 세션 — 자율 루프에서 R007 헤더 누락 계수를 시도했으나 사용자 프롬프트 4개로 턴 경계 재구성 불가). Cross-ref: R005(계수/매칭 방법 확인 — 도구 기본 동작 미확인 시 결과 오해석).
284
+
264
285
  ### Proxy Signal vs Canonical Ground-Truth (#1336 ①②)
265
286
 
266
287
  > Origin: #1336 ①② — transcription was alarmed as "stopped" because `.txt` files looked stale, but the canonical DB had transcripts current to 06-09 21:30 (.txt is not the whisper collector's output — it emits only to the DB). Separately, SMS was over-diagnosed as "fully blocked" from one empty OneDrive XML path + a single 401, while the DB held 17 SMS rows ingested via the app path.
@@ -90,7 +90,7 @@ R016의 승격 루프(위반 지적 → 규칙 조항 추가)는 코퍼스의 **
90
90
 
91
91
  1. **발동 추적**: `/homework` 회고·위반 지적 시 인용된 규칙 ID/조항을 feedback memory에 기록한다 (가벼운 추적 — 완전 자동화는 불요).
92
92
  2. **후보 선정**: 마이너 2릴리즈 무발동 + 행동 지시 가치 소멸 조항을 은퇴 후보로 선정한다.
93
- 3. **HTML-comment화**: 조항을 `<!-- RETIRED (은퇴 릴리즈 vX.Y.Z, N릴리즈 무발동): 원문 -->` 로 감싸 auto-injection에서 제외한다. Read 도구로 열람 가능하므로 무손실이다 (R005 Context Optimization via HTML Comments).
93
+ 3. **HTML-comment화**: 조항을 `<!-- RETIRED (은퇴 릴리즈 vX.Y.Z, <사유>): 원문 -->` 로 감싸 auto-injection에서 제외한다. `<사유>`는 위 「은퇴 대상」 두 범주에 대응한다 — 장기 무발동은 `N릴리즈 무발동`, 수정 완료된 플랫폼 버그 서사는 `보존 기준 v2.1.NNN 미만`. Read 도구로 열람 가능하므로 무손실이다 (R005 Context Optimization via HTML Comments).
94
94
  4. **부활**: 동일 패턴이 재발하면 uncomment하여 즉시 복원한다 — 승격 루프와 대칭이다.
95
95
 
96
96
  ### 버전노트 보존정책
@@ -99,6 +99,17 @@ R016의 승격 루프(위반 지적 → 규칙 조항 추가)는 코퍼스의 **
99
99
  - 그 이하 버전노트는 HTML-comment화(무손실 중간 단계) 하거나 `guides/claude-code/15-version-compatibility.md`로 이관한다.
100
100
  - `claude-native` 스킬이 생성하는 버전 추적 이슈를 규칙에 반영할 때, 최신만 visible로 두고 구버전은 즉시 은닉한다.
101
101
 
102
+ #### 보존 기준 변경 = 전 룰 파일 스윕 (같은 릴리즈 내 필수)
103
+
104
+ 보존 기준선을 상향하면 **같은 릴리즈에서 23개 룰 파일 전수를 스윕**해 기준 미만 노트를 HTML-comment화한다. 기준만 올리고 적용을 다음 릴리즈로 이월하면 코퍼스가 기준과 불일치한 상태로 남고, 그 불일치는 다음 회고에서 "잔존 N건" 부채로 재발견될 때까지 보이지 않는다. 스윕 범위는 `.claude/rules/**`와 `templates/.claude/rules/**` 양쪽이며, 잔존 여부는 **HTML 주석 안/밖을 구분해** 실측한다 — 단순 `grep`은 이미 은퇴한 주석 내부 노트까지 세어 판정을 왜곡한다.
105
+
106
+ | Anti-pattern | Required |
107
+ |--------------|----------|
108
+ | 보존 기준선만 상향하고 기존 노트 스윕을 다음 릴리즈로 이월 | 기준 상향과 전 룰 파일 스윕을 같은 릴리즈에서 완료 |
109
+ | `grep -c` 히트 수로 잔존 판정 | 주석 안/밖을 구분해 **visible 잔존**만 계수 |
110
+
111
+ Origin: #1563 찐빠 #4 — R016이 보존 기준을 v2.1.212로 규정했으나 R001/R005/R012에 visible v2.1.208 노트 3건이 잔존해 v1.1.44에서 뒤늦게 은퇴. Cross-reference: R005(HTML-comment 컨텍스트 최적화), R017(Count Sync — 전수 grep + 의미 판별).
112
+
102
113
  ### Cross-References
103
114
 
104
115
  R005(HTML-comment 컨텍스트 최적화), R023(Deprecated-Platform-Feature Staleness Check — 폐기 참조를 결정론적으로 탐지하여 은퇴 후보를 조기 발굴), Origin #1473.
@@ -14,10 +14,12 @@ oh-my-customcode uses an **advisory-first enforcement model**. Most rules are en
14
14
  | Soft Block | Stop hook prompt | R011 session-end saves | Auto-performs then approves |
15
15
  | Conversation Block | PostToolUse hook + `continueOnBlock` (CC v2.1.139+), exit 2 | stuck-detector, context-budget-advisor, cost-cap-advisor | Feeds rejection reason into conversation; Claude continues with awareness |
16
16
  | Advisory | PostToolUse hooks | R007, R008, R009, R010, R018 | Warns via stderr, never blocks |
17
- | Advisory (proactive) | UserPromptSubmit + SubagentStop hooks | R007, R008 (`r007-r008-drift-advisor.sh` — #1229 UserPromptSubmit, #1545 SubagentStop) | Reads last assistant turn; emits advisory if header/prefix absent. SubagentStop wiring (#1545) closes the no-user-input autonomous-loop gap (`/fsd`). Complements retroactive Stop-hook (`session-reflection.sh`, #1190). **현재 미발화 — 아래 각주 참조.** |
17
+ | Advisory (proactive) | UserPromptSubmit + SubagentStop + PostToolUse hooks | R007, R008 (`r007-r008-drift-advisor.sh` — #1229 UserPromptSubmit, #1545 SubagentStop, #1553 PostToolUse) | Reads last assistant turn; emits advisory if header/prefix absent. SubagentStop wiring (#1545) closes the no-user-input autonomous-loop gap (`/fsd`); PostToolUse (#1553) covers the orchestrator-only stretch before the first subagent spawn. Complements retroactive Stop-hook (`session-reflection.sh`, #1190). **v1.1.43부터 실제 발화 — 아래 각주 참조.** |
18
+ | Advisory (telemetry) | PostToolUseFailure hook | — (계측 전용, 규칙 강제 없음) | `failure-ledger.sh` (#1561, v1.1.44) — 도구 실패를 JSONL 원장에 append. stdout/stderr 무출력이라 모델에 도달하지 않으며 절대 차단하지 않음 |
19
+ | Advisory (proactive) | UserPromptSubmit hook | R020 (원인 진단) | `fail-axis-cause-advisor.sh` (#1561, v1.1.44) — 원장에 실패 기록이 있는데 원인 진술 없는 재촉 프롬프트가 오면 `hookSpecificOutput.additionalContext`로 "원인 가설 되묻기" advisory 전달. 원장 부재 시 조용히 통과 |
18
20
  | Prompt-based | CLAUDE.md + rules/ + PostCompact | All MUST rules | Behavioral guidance in context |
19
21
 
20
- > **Advisory (proactive/retroactive) 실태 정정 (실측)**: `hookSpecificOutput.additionalContext` **전달 경로 자체는 #1547(v1.1.40)에서 구현**됐으나, 그 앞단 **파서 셀렉터 결함**으로 advisory가 **한 번도 발화한 적이 없다** — `jq -r '.role'`로 읽으나 트랜스크립트 최상위에 `role` 키가 없어(실제는 `.message.role`) `last_assistant`가 항상 비고 즉시 `exit 0`으로 종료된다. 실측: 트랜스크립트 771개 전수에서 `"additionalContext":` 출현 0건, 라이브 프로브 stdout/stderr 각 0바이트. **proactive(`r007-r008-drift-advisor.sh`)와 retroactive(`session-reflection.sh`, 동일 결함) 두 계층 모두 미발화**였다. v1.1.43에서 파서 복구 + `PostToolUse` 배선으로 수정 중(Refs #1553).
22
+ > **Advisory (proactive/retroactive) 발화 결함과 해소 (실측)**: `hookSpecificOutput.additionalContext` **전달 경로 자체는 #1547(v1.1.40)에서 구현**됐으나, 그 앞단 **파서 셀렉터 결함**으로 advisory가 **v1.1.42까지 한 번도 발화하지 못했다** — `jq -r '.role'`로 읽었으나 트랜스크립트 최상위에 `role` 키가 없어(실제는 `.message.role`) `last_assistant`가 항상 비고 즉시 `exit 0`으로 종료됐다. 당시 실측: 트랜스크립트 771개 전수에서 `"additionalContext":` 출현 0건, 라이브 프로브 stdout/stderr 각 0바이트. **proactive(`r007-r008-drift-advisor.sh`)와 retroactive(`session-reflection.sh`, 동일 결함) 두 계층 모두 미발화**였다. **v1.1.43에서 양 계층 파서 복구 + `PostToolUse` 배선을 완료했고, 라이브 프로브로 최초 발화를 확인했다(#1553).** 후속으로 v1.1.44에서 R008 판정을 블록 인접 비교 → 턴 단위 개수 비교로 전환(#1563), v1.1.45에서 Skill 도구 면제를 추가했다(#1569).
21
23
  >
22
24
  > 교훈: **배선 확인 ≠ 전달 확인 ≠ 발화 확인** — R020 "actual outcome ≠ attempt"의 훅 도메인 재현 사례.
23
25
 
@@ -27,7 +29,7 @@ oh-my-customcode uses an **advisory-first enforcement model**. Most rules are en
27
29
  > **v2.1.199+**: SessionStart/Setup/SubagentStart hook이 exit code 2로 종료할 때 stderr를 조용히 숨기던 문제가 수정되어 이제 오류가 표시됩니다. Hard Block/Advisory 계층(위 Enforcement Tiers 표)의 훅 실패 관측성을 강화합니다 — cf. v2.1.163 `additionalContext` 구조화 피드백.
28
30
  -->
29
31
 
30
- > **v2.1.210+**: hook callback timeout이 모델에 user rejection으로 오보고되어 unattended 세션이 정지 대기하던 문제가 수정되었습니다. R021 advisory 훅(PostToolUse/UserPromptSubmit/Stop 등)이 매 턴 발화하고 /fsd 등 장기 무인 루프가 이에 의존하므로, hook timeout이 더 이상 phantom rejection으로 무인 세션을 중단시키지 않습니다 — cf. v2.1.199 훅 실패 관측성.
32
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.210+**: hook callback timeout이 모델에 user rejection으로 오보고되어 unattended 세션이 정지 대기하던 문제가 수정되었습니다. R021 advisory 훅(PostToolUse/UserPromptSubmit/Stop 등)이 매 턴 발화하고 /fsd 등 장기 무인 루프가 이에 의존하므로, hook timeout이 더 이상 phantom rejection으로 무인 세션을 중단시키지 않습니다 — cf. v2.1.199 훅 실패 관측성. -->
31
33
 
32
34
  > **v2.1.211/212/214+**: 훅의 enforcement 결정이 auto/unattended 모드에서 안정적으로 존중되도록 세 건이 수정되었습니다 — (211) auto mode가 unsandboxed Bash에 대한 PreToolUse 훅의 `ask` 결정을 덮어쓰던 문제가 수정되어 훅 `ask`가 최소 prompt로 floor되고, (212) `continue:false` 훅의 halt가 도구 실패·중간 완료 시 누락되던 문제 및 훅 인프라 오류가 user rejection으로 오보고되던 문제가 수정되었으며, (214) 훅 stdout JSON이 스키마 검증에 실패할 때 exit code 2가 문서대로 차단하지 못하던 문제가 수정되었습니다. R021 Enforcement Tiers(Hard Block=exit 2, Conversation Block=continueOnBlock exit 2, Advisory)가 훅의 block/ask 결정 존중에 의존하므로, 세 수정 모두 hard-block·advisory 훅(stage-blocker, rule-deletion-guard, stuck-detector 등)의 강제 신뢰성을 강화합니다 — v2.1.210 훅 timeout phantom-rejection 수정의 연장선.
33
35
 
@@ -40,7 +42,7 @@ oh-my-customcode uses an **advisory-first enforcement model**. Most rules are en
40
42
  3. **Composability**: External skills and internal rules can coexist without deadlocks
41
43
  4. **PostCompact reinforcement**: R007/R008/R009/R010/R018 are re-injected after context compaction
42
44
 
43
- ## Hard Enforcement Candidates — R010 git-delegation-guard (conditional), R007/R008 advisory **implemented** (#1229 UserPromptSubmit, proactive) + **#1545 SubagentStop** (closes autonomous-loop gap) + retroactive Stop-hook (#1190); `additionalContext` 전달 경로는 #1547(v1.1.40)에서 구현됐으나 파서 셀렉터 결함으로 **양 계층 모두 미발화** — v1.1.43에서 수정 중(Refs #1553); hard-block variant still candidate if advisory insufficient (#1096). Promoted: rule-deletion-guard.sh (2026-04-08). See details via Read tool.
45
+ ## Hard Enforcement Candidates — R010 git-delegation-guard (conditional), R007/R008 advisory **implemented & firing** (#1229 UserPromptSubmit, proactive) + **#1545 SubagentStop** (closes autonomous-loop gap) + **#1553 PostToolUse** + retroactive Stop-hook (#1190); `additionalContext` 전달 경로는 #1547(v1.1.40)에서 구현됐으나 파서 셀렉터 결함으로 v1.1.42까지 **양 계층 모두 미발화**였고, **v1.1.43에서 수정 완료·발화 확인**(#1553); hard-block variant still candidate if advisory insufficient (#1096). Promoted: rule-deletion-guard.sh (2026-04-08). See details via Read tool.
44
46
 
45
47
  <!-- DETAIL: Hard Enforcement Candidates (Future)
46
48
  If advisory enforcement proves insufficient for specific rules, these are candidates for promotion to hard-block:
@@ -48,7 +50,7 @@ If advisory enforcement proves insufficient for specific rules, these are candid
48
50
  | Rule | Candidate Hook | Status | Condition for Promotion |
49
51
  |------|---------------|--------|------------------------|
50
52
  | R010 | git-delegation-guard.sh | Candidate | If orchestrator-direct-write violations exceed 3/session |
51
- | R007/R008 | `r007-r008-drift-advisor.sh` (UserPromptSubmit #1229 + SubagentStop #1545) | **Advisory implemented** — proactive pre-response check now wired to both UserPromptSubmit and SubagentStop; the SubagentStop leg (#1545) closes the no-user-input autonomous-loop gap (`/fsd` etc.). Retroactive: `session-reflection.sh` (Stop, #1190). Two-layer drift detection: proactive (#1229/#1545) + retroactive (#1190). **Delivery path implemented but never fired (측정 정정)** — #1547 (v1.1.40) switched delivery from stderr (never model-visible on exit 0) to `hookSpecificOutput.additionalContext` on JSON stdout, non-blocking (no top-level `decision`/`continue`/`stopReason`). However both scripts short-circuit BEFORE emitting: `jq -r '.role'` reads a key absent at transcript top level (it is `.message.role`), so `last_assistant` is always empty and the script exits 0 silently. Measured: 0 `"additionalContext":` occurrences across 771 transcripts; live probe emits 0 bytes on both streams. Parser fix + `PostToolUse` wiring in v1.1.43 (Refs #1553). | Promote to hard-block if advisory proves insufficient (#1096) |
53
+ | R007/R008 | `r007-r008-drift-advisor.sh` (UserPromptSubmit #1229 + SubagentStop #1545 + PostToolUse #1553) | **Advisory implemented and firing** — proactive pre-response check wired to three trigger points; the SubagentStop leg (#1545) closes the no-user-input autonomous-loop gap (`/fsd` etc.), and the PostToolUse leg (#1553) covers the orchestrator-only stretch before the first subagent spawn. Retroactive: `session-reflection.sh` (Stop, #1190). Two-layer drift detection: proactive (#1229/#1545/#1553) + retroactive (#1190). **Historical defect (v1.1.40–v1.1.42): delivery path implemented but never fired** — #1547 (v1.1.40) switched delivery from stderr (never model-visible on exit 0) to `hookSpecificOutput.additionalContext` on JSON stdout, non-blocking (no top-level `decision`/`continue`/`stopReason`), but both scripts short-circuited BEFORE emitting: `jq -r '.role'` read a key absent at transcript top level (it is `.message.role`), so `last_assistant` was always empty and the script exited 0 silently. Measured then: 0 `"additionalContext":` occurrences across 771 transcripts; live probe emitted 0 bytes on both streams. **Resolved in v1.1.43** — parser fix on both layers + `PostToolUse` wiring, first fire confirmed by live probe (#1553). Follow-ups: v1.1.44 turn-level R008 prefix counting (#1563), v1.1.45 Skill-tool exemption (#1569). | Promote to hard-block if advisory proves insufficient (#1096) |
52
54
 
53
55
  Promotion requires: (1) measured violation rate data, (2) user approval, (3) rollback plan.
54
56
 
@@ -258,6 +258,8 @@ When a subagent trips the safety classifier (R001/R002) **2 times**, the orchest
258
258
  | 2nd trip | STOP the agent — do NOT re-run. Redesign: decompose by domain (R009) and re-delegate narrower units |
259
259
  | 3+ trips | Hard anti-pattern — indicates lost control; abort and report to user |
260
260
 
261
+ > **v2.1.225+**: auto mode가 **자기 권한 검사에 대한 safety-filter refusal**을 consecutive-block 한도에 계상하던 결함이 수정되었습니다 — 동작은 여전히 거부되나 모델에는 재시도 대신 진행하라고 지시됩니다. 이 표의 trip 계수는 **서브에이전트가 실제 작업에서 유발한 classifier trip**만을 대상으로 하며, 플랫폼 내부 권한 검사에서 발생한 refusal은 계수 대상이 아닙니다 — 구버전에서 이 둘이 섞여 계상되었으므로, 과거 세션의 trip 횟수를 근거로 STOP 판정을 소급하지 않습니다.
262
+
261
263
  ### Pre-Decomposition Mandate
262
264
 
263
265
  Broad single-task scopes (e.g. "migrate + backfill") MUST be pre-decomposed by domain before delegation, so an agent cannot silently expand from its named task into adjacent privileged domains (secret rotation, tunnel creation, infra deletion, dashboard changes). See R009 (pre-decomposition) and R018 (domain-split).
@@ -299,6 +301,16 @@ Cross-reference: the Subagent Scope-Creep STOP Protocol (reactive halt after tri
299
301
 
300
302
  > Origin: #1556 — 승인 인용을 포함한 위임은 mgr-gitnerd가 2회 거부했고, 동일 에이전트에 허용/금지 작업만 열거한 위임은 거부 없이 완주했다(2026-08-05 v1.1.43 세션, 대조 실증). Cross-ref: R015 (User Directive Persistence — `settings.json` allow 규칙이 실제 prompt 억제 수단이라는 동일 결론의 선례), R002 (permission tiers).
301
303
 
304
+ ### Delegation Prompt Command Examples — 실측 확인 또는 예시 명시
305
+
306
+ 위임 프롬프트에 구체적 명령·플래그를 적을 때는 **실측으로 확인한 것만 적거나**, 확인하지 않았다면 "예시이며 실제 플래그는 확인 후 사용"을 명시한다. 미확인 플래그를 확정형으로 적으면 서브에이전트가 실행 중 `unknown flag`를 만나 복구 왕복을 소비하고, 복구에 실패하면 잘못된 대체 경로를 택한다. 확인 수단은 `--help` 또는 `command -v` 한 줄이면 충분하다.
307
+
308
+ | Anti-pattern | Required |
309
+ |--------------|----------|
310
+ | 미확인 플래그를 확정형으로 위임 프롬프트에 기재 (`gh issue edit <N> --assignee @me`) | 실행 전 `--help`로 실측 후 기재, 또는 "예시 — 실제 플래그는 확인 후 사용" 명시 |
311
+
312
+ Origin: #1563 찐빠 #3 — `gh issue edit --assignee`가 gh 2.86.0에 없는 플래그였고(정답 `--add-assignee`) 에이전트가 실행 중 자체 복구했다. Cross-reference: R005(도구 플래그·기본 동작 실측 함정 사례집), 아래 Agent Capability Pre-Check(위임 전 존재성 확인의 도구·경로 각도).
313
+
302
314
  ### Parallel Delegation — Sibling-Agent Disclosure
303
315
 
304
316
  2개 이상의 서브에이전트를 같은 메시지에서 병렬 스폰할 때, 각 위임 프롬프트는 **형제 에이전트의 존재와 각자의 담당 범위**를 고지해야 한다. 서브에이전트는 격리된 컨텍스트에서 실행되어 형제를 인지할 수 없으므로, 고지가 없으면 `git status` 같은 **저장소 전역 공유 뷰**의 출력을 자기 변경분으로 오독하거나 경합 원인을 "외부 세션/프로세스"로 오귀속한다.
@@ -311,6 +323,16 @@ Cross-reference: the Subagent Scope-Creep STOP Protocol (reactive halt after tri
311
323
 
312
324
  > Origin: #1518 (찐빠 #3 — 미고지 git 에이전트가 형제를 "외부 프로세스"로 오귀속; 같은 세션에서 고지한 4개 구현 에이전트는 전원 정확히 구분 보고 — 대조 실증). Cross-ref: R009 (병렬 실행 조건).
313
325
 
326
+ #### 고지는 귀속 후보를 늘릴 뿐 증거 등급을 올리지 않는다
327
+
328
+ 형제 고지를 받았더라도 **정황 귀속(형제 탓)은 여전히 오답을 낸다** — 오히려 고지가 그럴듯한 오귀속 대상을 제공한다. 공유 뷰의 이상 징후는 형제 고지 여부와 무관하게 **개입 실험**(캐시 제거·복원, `bash -x` 추적, 변경 되돌려 재현)으로 귀속해야 한다.
329
+
330
+ | Anti-pattern | Required |
331
+ |--------------|----------|
332
+ | 고지받은 형제의 담당 범위와 겹친다는 정황만으로 실패 원인을 형제에 귀속 | 개입 실험(제거→재현 / 복원→소멸)으로 인과를 확정한 뒤 귀속 |
333
+
334
+ > Origin: #1574 (v1.1.44 세션 대조 실증 — 동일 고지를 받은 3개 병렬 에이전트 중 [1]은 `bun test` 11 fail을 "형제가 그 파일 편집 중"으로 정황 귀속해 오답, [2]/[3]은 개입 실험으로 정확히 귀속). Cross-ref: R020 (Read-Before-Characterize — 정황으로 특성화 금지).
335
+
314
336
  ## Universal bypassPermissions
315
337
 
316
338
  > **This section is the canonical single source for the bypassPermissions requirement.** R002 (MUST-permissions.md) and R006 (MUST-agent-design.md) reference this section rather than repeating it.
@@ -395,6 +417,8 @@ Before spawning any agent:
395
417
 
396
418
  > **v2.1.212+**: CC가 Task(=Agent) 도구의 `mode` 파라미터를 deprecated(이제 무시)했습니다 — subagent는 기본적으로 **부모(오케스트레이터) 세션의 permission mode를 상속**합니다. 따라서 이 섹션이 요구하는 per-call `mode: "bypassPermissions"`는 v2.1.212+에서 no-op이며, 무인 위임이 프롬프트 없이 돌게 하는 통제점은 per-call 파라미터가 아니라 **부모 세션의 permission mode**입니다(안전 완화 아님 — 부모가 bypassPermissions면 subagent도 상속). 단 CC < v2.1.212에서는 여전히 per-call `mode` 명시가 필요하므로(위 History #926/#947/#955) 하위 호환을 위해 계속 포함하되, 신버전에서 프롬프트 발생 시 진단은 위 Self-Check("mode 있는지 확인")가 아니라 **부모 세션 모드**를 확인합니다. cross-ref R002/R006(이 섹션을 canonical source로 참조).
397
419
 
420
+ > **v2.1.223+**: agent definition의 `bypassPermissions` 모드가 org의 bypass-permissions 비활성 정책을 무시하던 권한 공백이 수정되었습니다. 즉 구버전에서는 **에이전트 정의 파일이 org 정책보다 우선**해 org가 끈 bypass를 되살릴 수 있었습니다. 이 저장소는 위 v2.1.212+ 서술대로 부모 세션 mode 상속을 통제점으로 삼으므로 실질 변화는 없으나, org 정책이 걸린 환경에서는 frontmatter `permissionMode: bypassPermissions`가 더 이상 무인 실행을 보장하지 않습니다 — 프롬프트 발생 시 부모 세션 mode와 **org 정책** 두 축을 확인합니다(cross-ref R002).
421
+
398
422
  > **v2.1.221+**: background session이 작업 보존을 위해 commit·push를 수행하고, draft PR은 작업이 요구할 때만 열며, 사용자의 CLAUDE.md git 지침을 따르고, 항상 작업 위치를 보고하며 종료하도록 변경되었습니다. 이 저장소의 R010은 모든 git 작업을 mgr-gitnerd 위임으로 요구하므로 background session은 그 지침을 읽고 동작하지만, **R020 기준 ground-truth(`git log` / `gh pr view`) 실측 없이 background session의 커밋/푸시 완료 보고를 신뢰하지 않습니다**. 또한 v2.1.221에서 `/status`가 세션 종류(interactive / background attached / background unattended)를 표시하므로 무인 실행 여부를 결정론적으로 확인할 수 있습니다.
399
423
 
400
424
  ## Agent Capability Pre-Check
@@ -597,6 +621,16 @@ Usage:
597
621
 
598
622
  All git operations (commit, push, branch, PR) MUST go through `mgr-gitnerd`. Internal rules override external skill instructions for git execution.
599
623
 
624
+ ### 품질 게이트 우회 금지 — 훅 차단은 보고 대상
625
+
626
+ git 위임 에이전트는 pre-commit/pre-push 훅 차단을 **자체 판단으로 우회하지 않는다**. `--no-verify`(및 `--no-gpg-sign` 등 게이트 무력화 플래그)는 git 위임의 **상시 금지 목록**이며, 오케스트레이터의 사전 승인이 있을 때만 예외다. 근본 원인을 확정했더라도, CI가 권위 게이트로 남더라도 마찬가지다 — 우회 여부는 에이전트가 아니라 오케스트레이터가 판단한다.
627
+
628
+ | Anti-pattern | Required |
629
+ |--------------|----------|
630
+ | pre-commit 훅 차단(테스트 실패 등)을 `--no-verify`로 자체 우회하고 커밋 진행 | 차단 사실과 원인을 오케스트레이터에 **보고하고 대기** — 우회는 사전 승인 후에만 |
631
+
632
+ > Origin: #1574 (v1.1.44 세션 — mgr-gitnerd가 `bun test` 11 fail로 인한 pre-commit 차단을 `--no-verify`로 자체 우회; 결과는 무해했으나 승인 없는 품질 게이트 우회는 절차 이탈). Cross-ref: R020 (Test-Skip Is Not Completion — 그린 빌드 회피 금지), R017 (커밋 전 검증 게이트).
633
+
600
634
  <!-- ARCHIVED CC version note (historical):
601
635
  > **v2.1.206+**: `/commit-push-pr`가 origin 외에 `remote.pushDefault`(또는 단일 remote)로의 git push도 auto-allow합니다. mgr-gitnerd git 위임 흐름 관련. `mode: "bypassPermissions"`는 모든 Agent tool 호출에 여전히 필수입니다.
602
636
  -->
@@ -106,6 +106,8 @@ Reference: #1320 (fix), #1321 (session 113 retrospective 찐빠 #1), `feedback_l
106
106
  | Instance independence | Isolated context, no shared state |
107
107
  | Large tasks (>3 min) | MUST split into parallel sub-tasks |
108
108
 
109
+ > **v2.1.224+**: **세션당 200 subagent spawn cap이 제거**되어 장기 세션이 신규 에이전트를 거부하지 않습니다(동시성 제한과 depth 제한은 유지). 위 표의 "Max instances 5 concurrent"는 **동시성** 제한이므로 그대로 유효합니다 — 제거된 것은 세션 누적 총량 cap입니다. `/fsd` 등 장기 무인 루프에서 후반 반복의 스폰 실패를 더 이상 누적 cap으로 진단하지 않습니다.
110
+
109
111
  > **Fable 5 long-lived subagent reuse (Origin: #1435)**: Fable 5는 long-lived subagent 재사용(단일 subagent가 여러 단계를 이어서 수행)에 강함 — 현행 R009 병렬 실행 원칙과 상충하지 않으며, Fable 5 실행 시 short-lived 병렬 다수 대신 long-lived 재사용도 유효한 선택지. 상세는 `guides/claude-code/16-fable5-prompting.md`.
110
112
 
111
113
  ## Adaptive Parallel Splitting
@@ -71,7 +71,7 @@ Use a `"*"` deny rule in `settings.json` to enforce a deny-by-default posture, t
71
71
  > **v2.1.207+**: Auto mode가 Bedrock/Vertex/Foundry에서 `CLAUDE_CODE_ENABLE_AUTO_MODE` opt-in 없이 사용 가능해졌습니다(설정 `disableAutoMode`로 비활성화 가능). 또한 `-p`/SDK 비대화 실행의 remote managed settings가 consent 다이얼로그 없이 동의로 기록되던 문제가 수정되었습니다. Tier-3/4 권한 흐름 관련.
72
72
  -->
73
73
 
74
- > **v2.1.210+**: `Write(path)`/`NotebookEdit(path)`/`Glob(path)` 형태의 permission rule은 시작 시 경고를 발생시킵니다 — 파일 쓰기 rule은 `Edit(path)`, 읽기 rule은 `Read(path)` matcher로 작성합니다. 위 Tier 표의 Write/NotebookEdit/Glob은 도구명일 뿐 path-scoped rule matcher가 아닙니다(위 v2.1.166 unknown-tool startup warning 연장선).
74
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.210+**: `Write(path)`/`NotebookEdit(path)`/`Glob(path)` 형태의 permission rule은 시작 시 경고를 발생시킵니다 — 파일 쓰기 rule은 `Edit(path)`, 읽기 rule은 `Read(path)` matcher로 작성합니다. 위 Tier 표의 Write/NotebookEdit/Glob은 도구명일 뿐 path-scoped rule matcher가 아닙니다(위 v2.1.166 unknown-tool startup warning 연장선). -->
75
75
 
76
76
  > **v2.1.214+**: 단일 세그먼트 `dir/**` allow rule(예: `Edit(src/**)`)이 트리 어디에나 있는 중첩 `dir/`까지 auto-approve하던 버그가 수정되어 이제 `<cwd>/dir`에만 매칭됩니다(hook `if:` 조건도 동일 — 임의 깊이 매칭이 필요하면 `**/dir/**`로 작성). **`deny`/`ask` permission rule은 any-depth 매칭을 유지**(allow만 `<cwd>`로 좁아짐). settings.json 스코프 설계 시 이 비대칭(allow 좁게 / deny·ask 넓게)을 전제로 삼습니다. 위 v2.1.210 `Edit(path)`/`Read(path)` matcher 권고의 연장선.
77
77
 
@@ -80,6 +80,8 @@ Use a `"*"` deny rule in `settings.json` to enforce a deny-by-default posture, t
80
80
  > 2. **(v2.1.221) auto mode 병렬 권한 검사 최적화** — 병렬 tool call의 권한 검사가 cache-efficient해지고 캐시된 대화 prefix 재사용으로 비용이 감소했습니다(R009 병렬 배치의 부담 완화). 검사 대기 중 모드를 전환하면 stale 결과를 적용하지 않고 재프롬프트합니다.
81
81
  > 3. **(v2.1.222) Remote Control auto-start 스코프 축소** — repo-local 설정(`.claude/settings.json` / `.claude/settings.local.json`)으로는 **켤 수 없고**(끄는 것은 가능), 활성화는 user scope `/config`에서만 가능합니다.
82
82
 
83
+ > **v2.1.223/225+**: 두 건이 Tier-4 Bash 권한 검사와 세션 인증에 영향을 줍니다. (223) 조작된 명령이 **자기 일부를 권한 검사에서 숨기던** 결함이 수정되었습니다 — v2.1.221 zsh `[[ ]]` 우회 수정의 연장선이며, 검사 대상 문자열과 실행 문자열이 다를 수 있었다는 뜻입니다(표시 측 결함은 R001). (225) 일시적 401이 장수명 `CLAUDE_CODE_OAUTH_TOKEN`을 저장된 단수명 토큰으로 교체해 headless 세션을 재시작 전까지 망가뜨리던 결함이 수정되었습니다 — 구버전 무인 실행에서 401 이후의 연쇄 인증 실패는 토큰 설정 오류가 아니라 이 교체 버그일 수 있으므로 진단 시 구분합니다. agent definition의 `bypassPermissions`가 org 정책을 무시하던 공백(223)은 R010 "Universal bypassPermissions"가 canonical.
84
+
83
85
  ## Agent Tool Permission Mode
84
86
 
85
87
  > Canonical source: R010 (MUST-orchestrator-coordination.md) "Universal bypassPermissions" owns the full requirement, rationale, self-check, and version history. Core rule: always pass `mode: "bypassPermissions"` explicitly on every Agent tool call — the Agent tool's default `mode` (`acceptEdits`) overrides agent frontmatter `permissionMode` and causes prompts during unattended execution. Skills that spawn agents MUST include this in their Agent tool call instructions. See R010 for details.
@@ -29,7 +29,9 @@ The following git commands have caused working tree loss in past sessions (#1146
29
29
  > **v2.1.183+**: Auto mode now BLOCKS destructive git commands at the platform level — `git reset --hard`, `git checkout -- .`, `git clean -fd`, and `git stash drop` are blocked when you did not ask to discard local work; `git commit --amend` is blocked when the commit was not made by the agent this session; and `terraform destroy` / `pulumi destroy` / `cdk destroy` are blocked unless you asked for the specific stack. This is the PLATFORM-level complement to this section's (advisory) per-invocation approval requirement and the Pre-Delegation Blast-Radius Enumeration below: the model still enumerates discard targets and requests approval (model-level), and CC now also hard-blocks the destructive command itself in auto mode (platform-level) — defense-in-depth. The advisory approval requirement remains because the platform block gates the COMMAND, not the blast-radius enumeration the user needs for an informed decision.
30
30
  -->
31
31
 
32
- > **v2.1.208+**: Catastrophic removals (e.g. `rm -rf ~`) wrapped in `$(…)`/backticks/`<(…)` now trigger the same prompt as the plain form in `--dangerously-skip-permissions` and auto mode — closes a subshell-obfuscation gap in the v2.1.183 platform-level destructive-command block above.
32
+ <!-- RETIRED (은퇴 릴리즈 v1.1.44, 보존 기준 v2.1.212 미만): > **v2.1.208+**: Catastrophic removals (e.g. `rm -rf ~`) wrapped in `$(…)`/backticks/`<(…)` now trigger the same prompt as the plain form in `--dangerously-skip-permissions` and auto mode — closes a subshell-obfuscation gap in the v2.1.183 platform-level destructive-command block above. -->
33
+
34
+ > **v2.1.223/224+**: 승인 다이얼로그·샌드박스 경계의 표시 무결성 결함 두 건이 수정되었습니다. (223) 탭·비가시 유니코드로 패딩한 명령이 승인 다이얼로그에서 자기 일부를 숨길 수 있던 결함 — 사용자가 **본 것과 승인한 것이 달랐다**는 뜻이므로, 위 Pre-Delegation Blast-Radius Enumeration(모델이 파괴 대상을 별도로 열거)이 플랫폼 다이얼로그로 대체될 수 없음을 재확인시킵니다. (224) sandbox filesystem deny 항목의 후행 슬래시(`denyRead: "~/.aws/"`)가 조용히 우회 가능하던 결함, 그리고 sandbox 위반 상세가 Bash 도구 결과에 전혀 나타나지 않던 결함 — 후자는 위반이 **관측 불가**했다는 의미이므로, 구버전에서 "sandbox 위반 없음"은 위반 부재의 증거가 아닙니다. credential deny 규칙 작성 시 후행 슬래시를 제거합니다(cross-ref R002).
33
35
 
34
36
  > **v2.1.221/222+**: v2.1.222에서 worktree-isolated 세션과 그 subagent가 main checkout에 대해 파괴적 git 명령을 실행할 수 있던 문제가 수정되어, isolation이 모든 세션 타입의 file edit과 Bash에 적용됩니다. v2.1.221에서는 `/fork` 세션이 원본 세션 checkout이 아니라 자체 worktree를 생성하도록 변경되었습니다. **완화 아님**: 위 Destructive Git Commands 표의 per-invocation 승인 요구와 아래 Pre-Delegation Blast-Radius Enumeration은 그대로 유지됩니다. 플랫폼 isolation은 격리 경계를 강화할 뿐, 사용자가 판단하는 데 필요한 blast-radius 열거를 대체하지 않습니다(v2.1.183/208 플랫폼 블록과 동일한 defense-in-depth 관계).
35
37
 
@@ -166,6 +166,16 @@ Origin: #1492 (Session 132) — cc-release-monitor 워크플로우 삭제(#1454,
166
166
 
167
167
  Origin: #1457 (Session 128 회고 찐빠 #1) — 오케스트레이터가 stale 메모리(npm 1.1.6→target v1.1.7 추정)로 implement를 위임 → v1.1.7이 이미 배포된 closed milestone임을 에이전트가 STOP으로 감지 → v1.1.8 재위임 왕복 1회. 기존 `feedback_session_memory_git_stale`(브랜치 분기 전 pull)의 릴리즈-버전-선정 각도 확장. Cross-ref: R020 (Diagnostic Hypothesis Verification — 영구 변경/위임 전 전제 실측 확정).
168
168
 
169
+ ### 일반화 — 메모리 TODO 를 위임 전제로 쓸 때 (Origin: #1574)
170
+
171
+ 위 게이트는 **버전**에 대한 규정이지만 원리는 세션 메모리 항목 전반에 적용된다. `MEMORY.md`의 "선재 항목 / Next Session TODO"는 **직전 세션 종료 시점의 스냅샷**이므로 이후 해소·변경됐을 수 있다. 이를 위임 브리핑(예: mgr-sauron 검증 스코프)의 전제로 넘기기 전, 각 항목을 실측으로 재확인하거나 **측정 시점을 함께 표기**해 전달한다.
172
+
173
+ | Anti-pattern | Required |
174
+ |--------------|----------|
175
+ | 메모리 TODO를 현재 상태로 간주해 위임 브리핑에 전제로 기재 | 위임 전 항목별 실측 재확인, 또는 "vX.Y.Z 시점 스냅샷 — 직접 확인하라"를 명시 |
176
+
177
+ Origin: #1574 (v1.1.44 세션 — mgr-sauron 브리핑의 "선재 항목" 4건 중 3건이 부정확: 이미 해소된 항목, 의도적 차이를 결함으로 오인, 규모 과대). **완화 요인**: 프롬프트에 "그대로 믿지 말고 직접 확인하라"를 명시해 3건 전부 에이전트가 정정 — #1443의 "실측값 기준으로 동기화하라" 방어선과 동일 효과. Cross-ref: R011(메모리 신뢰도·Temporal Decay), R020(Diagnostic Hypothesis Verification).
178
+
169
179
  ## Post-Gate Scope-Expansion Re-Run (Origin: #1433 #2)
170
180
 
171
181
  R017 게이트(mgr-sauron) 통과 선언 후 신규 결함 발견 등으로 스코프가 확장되면(추가 파일 편집), 커밋 전 게이트를 **최종 상태에서 재실행**한다. 게이트 통과 시점 이후의 변경은 형식적으로 미검증이므로, 확장분 미검증 커밋은 R017이 최종 산출물을 커버하지 못하게 만든다.
@@ -86,6 +86,8 @@ Active removal of irrelevant retrieved content from agent context. Complements o
86
86
 
87
87
  ## Context Budget Management — Task-type-aware thresholds (research 40%, implementation 50%, review 60%, management 70%, general 80%). See full spec via Read tool.
88
88
 
89
+ > **v2.1.223+**: `CLAUDE_CODE_DISABLE_1M_CONTEXT`가 native 1M 창을 가진 **모든** Claude 모델을 auto-compaction으로 200K에 유지하도록 확대되었고(이전에는 고정 모델 목록), 미인식 model ID도 가정 컨텍스트 창 내로 유지됩니다(`CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT=1`로 복원). 위 임계값은 **창 대비 백분율**이므로 절대 토큰량은 이 env 설정에 따라 5배까지 달라집니다 — 1M 전제로 예산을 잡기 전 env 설정 여부를 확인합니다(cross-ref R006).
90
+
89
91
  <!-- DETAIL: Context Budget Management
90
92
 
91
93
  Task-type-aware context thresholds that trigger ecomode earlier for context-heavy operations.
@@ -37,7 +37,7 @@ Format: `─── [Spawn] {subagent_type}:{model} | {description} ───`
37
37
  > **v2.1.202+**: workflow-spawned agent 텔레메트리에 `workflow.run_id`/`workflow.name` OTel 속성이 추가되어 workflow run 활동을 OTel 데이터로 재구성할 수 있습니다. R012 관측성 확장(monitoring-setup 스킬).
38
38
  -->
39
39
 
40
- > **v2.1.208+**: Fixed `/release-notes` "Show all" injecting the entire changelog into the model's context (cross-ref R013 context budget). Fixed the context window (and auto-compact indicator) briefly resetting to 200k after CLI auto-update, causing a false "100% context used" on resumed long-context sessions — relevant to the CTX% statusline segment below. Completed background agents now stay listed in `/tasks` until cleanup instead of vanishing on completion — extends the v2.1.198 background-notification observability above.
40
+ <!-- RETIRED (은퇴 릴리즈 v1.1.44, 보존 기준 v2.1.212 미만): > **v2.1.208+**: Fixed `/release-notes` "Show all" injecting the entire changelog into the model's context (cross-ref R013 context budget). Fixed the context window (and auto-compact indicator) briefly resetting to 200k after CLI auto-update, causing a false "100% context used" on resumed long-context sessions — relevant to the CTX% statusline segment below. Completed background agents now stay listed in `/tasks` until cleanup instead of vanishing on completion — extends the v2.1.198 background-notification observability above. -->
41
41
 
42
42
  <!-- DETAIL: HUD Events full spec
43
43
  ### When to Display: Multi-step tasks, parallel execution, long-running operations. Skip for single brief operations.
@@ -373,6 +373,16 @@ Session-end saves lose context: by the time the session ends, multiple discoveri
373
373
  Related records from session v0.87.2~v0.88.0 (issue #869). The originating memory files were later consolidated/removed; no live equivalents remain as of this writing.
374
374
  -->
375
375
 
376
+ ## Procedure-Summary Scope Tagging
377
+
378
+ 절차·순서를 메모리에 압축 요약할 때는 **적용 스코프를 함께 표기**한다. 압축은 문맥 경계를 가장 먼저 버리므로, 하위 단계 내부의 순서가 파이프라인 전체 순서로 읽히는 오독이 발생한다. 스코프 표기는 괄호 한 마디면 충분하다 — "(릴리즈 단계 내부 순서)", "(구현 커밋에는 미적용)"처럼 **무엇에 적용되지 않는지**까지 적으면 오독 여지가 사라진다.
379
+
380
+ | Anti-pattern | Required |
381
+ |--------------|----------|
382
+ | `release 브랜치 선생성 → 버전범프 → PR` (스코프 미표기 → 전체 파이프라인 순서로 오독) | `릴리즈 단계 내부 순서: release 브랜치 선생성 → 버전범프 → PR (구현 커밋은 develop 직행)` |
383
+
384
+ Origin: #1563 찐빠 #5 — 위 요약이 릴리즈 단계 내부 순서인데 전체 파이프라인 순서로 오독되었다. Cross-reference: R013(Compact Output — 압축이 버리는 것을 인지), 위 Mid-Session Immediate Save(트리거 문맥 보존).
385
+
376
386
  ## Safety-Related Feedback Memory Framing
377
387
 
378
388
  > Origin: #1307 찐빠 #2 (Medium) — a sys-memory-keeper delegation prompt framed a learning as "오탐으로 판단하고 진행한다" (conclude it's a false positive and proceed), tripping the memory-poisoning safety classifier and requiring a rewrite.
@@ -472,4 +482,4 @@ References: #1226 (item 3), #1227.
472
482
  - Memory write failure is **non-blocking**: MUST NOT prevent session from ending
473
483
  - If sys-memory-keeper fails to write MEMORY.md: log warning, confirm to user anyway
474
484
 
475
- > **v2.1.210+**: MEMORY.md 인덱스가 read limit을 초과하게 만드는 memory write는 이제 silent truncation 대신 명시적 오류를 반환합니다. write 실패는 여전히 non-blocking이지만, 오류 수신 시 log-warning으로 끝내지 말고 예산 초과 처리(Attention-Weight Tiering — Cold 항목 archive 이동)로 축소 후 재시도합니다 — 이전의 silent truncation을 가정하고 oversize write를 던지면 업데이트가 반영되지 않습니다.
485
+ <!-- RETIRED (은퇴 릴리즈 v1.1.45, 보존 기준 v2.1.212 미만): > **v2.1.210+**: MEMORY.md 인덱스가 read limit을 초과하게 만드는 memory write는 이제 silent truncation 대신 명시적 오류를 반환합니다. write 실패는 여전히 non-blocking이지만, 오류 수신 시 log-warning으로 끝내지 말고 예산 초과 처리(Attention-Weight Tiering — Cold 항목 archive 이동)로 축소 후 재시도합니다 — 이전의 silent truncation을 가정하고 oversize write를 던지면 업데이트가 반영되지 않습니다. -->
@@ -103,6 +103,26 @@ staleness/audit 검증은 model ID·placeholder·TBD뿐 아니라 **폐기된
103
103
 
104
104
  Origin: #1455 #1 (Session 127 회고 찐빠 #1) — cc-release-monitor PR #1449 머지 후 workflow_dispatch 실검증에서 issue_body의 `<details>`·릴리즈 요약에 12칸 리터럴 들여쓰기 발견 → PR #1451 재작업. 첫 위임이 문법 검증만 지시하고 샘플 값 출력 조립 검증을 누락. `textwrap.dedent` + 멀티라인 변수 함정이 문법 검증만으로는 미노출. R020(문법 통과 ≠ 출력 정상)과 정합.
105
105
 
106
+ ## Delegated Verification Floor — CI 잡 목록에서 도출 (Origin: #1574)
107
+
108
+ 위임 프롬프트의 검증 항목은 "변경 파일의 영향 범위"만으로 정하면 부족하다. **하한선은 CI가 실제로 돌리는 잡 전체**다 — 워크플로 YAML의 잡 목록을 읽어 대응하는 로컬 명령(`lint` / `test` / `validate-docs` / sync 검사)을 열거하고, 그중 로컬 실행 가능한 것을 위임 완료 조건에 포함한다. 로컬에서 통과시키지 않은 CI 잡은 병합 시점에 halt로 돌아와 수정 에이전트 추가 발주를 강제한다.
109
+
110
+ | Anti-pattern | Required |
111
+ |--------------|----------|
112
+ | "변경분 영향 범위"만 보고 검증 항목을 정해 위임 → CI 전용 잡(lint 등) 누락 | 워크플로 잡 목록을 하한선으로 삼아 로컬 대응 명령을 완료 조건에 열거 |
113
+
114
+ Origin: #1574 (v1.1.44 세션 — 병렬 위임 3건 모두 `bun run lint`를 누락해 verify-build halt, 수정 에이전트 1회 추가 발주). 기존 `feedback_delegation_verify_scope_by_impact`("영향 범위 기준")의 하한선을 명문화한 것이다. Cross-reference: R020(완료 검증 — 선언 전 실제 게이트 통과 확인), R017(커밋 전 검증 게이트).
115
+
116
+ ## Conditional-Output Verification — Positive/Negative Pair Mandate (Origin: #1563 #2)
117
+
118
+ 조건부로만 출력하는 대상(advisory 훅, 가드, 경고 emitter)의 동작을 검증하도록 위임할 때, 완료 기준은 **"출력이 나와야 하는 입력"과 "나오면 안 되는 입력"을 짝으로** 지정해야 한다. "stdout ≠ 0바이트" 같은 단일 프록시는 검증이 아니다 — 침묵이 정답인 입력에서도 통과를 요구하게 되어 기준 자체가 틀리고, 반대로 오탐(준수 턴에서 발화)을 통과시킨다.
119
+
120
+ | Anti-pattern | Required |
121
+ |--------------|----------|
122
+ | "지정 입력에서 stdout ≠ 0바이트"를 단일 완료 기준으로 위임 | 양성 케이스(발화해야 함)와 음성 케이스(침묵해야 함)를 짝으로 명시 |
123
+
124
+ Origin: #1563 찐빠 #2 — R007/R008 advisor 발화 검증에 단일 "0바이트 아님" 프록시를 제시했으나, advisor는 준수 턴에서 침묵하는 것이 정상 동작이라 기준이 성립하지 않았다. Cross-reference: R020(Proxy Signal vs Canonical Ground-Truth — 프록시로 상태를 특성화하지 말 것), 아래 Detection Guard Delegation Standard(positive-match vs negative-context 구분의 가드 설계 각도).
125
+
106
126
  ## Detection Guard Delegation Standard (Origin: #1438 #3)
107
127
 
108
128
  Tier-1 shift-left 검출 가드(예: deprecated-pattern grep 가드)의 설계·수정을 서브에이전트에 위임할 때, 위임 프롬프트는 **positive-match(genuine defect mandate — `MUST`/`MANDATORY` 인접 문맥)와 negative-context(deprecation note — "no longer"/"deprecated"/"불필요"/"폐기됨" 설명 문구)를 구분**하도록 명시해야 한다. 이를 누락하면 올바르게 수정된 파일의 폐기-설명 문구까지 과잉매칭하여 자기모순 BLOCK을 유발한다.
@@ -146,6 +166,8 @@ Before invoking a Workflow script, deterministically verify:
146
166
  | 프롬프트 문자열 내 셸 변수 `${...}`(`$?`, `${PIPESTATUS[0]}`, `$(...)` 등)가 `\${...}`로 이스케이프되어 있는지 사전 grep 확인 | JS 템플릿 리터럴 안의 이스케이프 안 된 셸 `${...}`를 JS가 JS 표현식으로 평가 → 런타임 `ReferenceError`(예: `PIPESTATUS is not defined`). `node --check`는 문법만 검사하여 이 런타임 오류를 못 잡으므로 별도 결정론 grep 검사가 필요함 |
147
167
  | Workflow `args`를 사용하는 스크립트가 `typeof args === 'string' ? JSON.parse(args) : args` 방어를 거친 뒤 필드에 접근하는지 확인 | 하니스가 객체 args를 문자열로 인코딩해 전달하면 `args.<field>`가 undefined가 되어 스크립트가 즉시 런타임 실패(0 agents 실행). `node --check`는 문법만 검사하므로 위 셸 `${...}` 이스케이프 항목과 동일한 런타임 계열을 잡지 못함 |
148
168
 
169
+ > **v2.1.223+**: workflow script가 동적 `import()`로 workflow 샌드박스 **밖의 코드를 실행**할 수 있던 결함이 수정되었습니다. 위 표의 체크는 프롬프트 조립·문법·런타임 계열을 다루지만 **샌드박스 탈출은 다루지 않았고**, 구버전에서는 `node --check` 통과 + 프롬프트 정상 조립 상태에서도 스크립트가 경계 밖 코드를 끌어올 수 있었습니다. 외부에서 받은 workflow script를 실행하기 전 동적 `import()` 사용 여부를 grep으로 확인합니다(Tier-1 결정론 검사).
170
+
149
171
  #### Common Violation (#1271)
150
172
  Session 106 follow-up to #1266 ③: a Workflow authoring error recurred — the guardrail fact-sheet was concatenated onto the agent's RETURN VALUE instead of the prompt string, and a placeholder/assembly slip went uncaught because no pre-run sanity check existed. This check is the deterministic Tier-1 guard that catches such slips before the expensive run.
151
173
 
@@ -116,12 +116,30 @@ steps:
116
116
  4. Cap at 7 issues; if priority issues < 7, stop at that priority (don't mix tiers)
117
117
  5. Minimum 1 issue; if 0 eligible, halt with "no eligible issues for auto-dev run"
118
118
 
119
+ ## Step 3 — Approval-required path pre-check (R010)
120
+
121
+ For each scoped issue, extract target file/directory paths from its title+body (explicit
122
+ paths, backtick-quoted paths, or clearly named targets).
123
+
124
+ Check against `.claude/rules/MUST-orchestrator-coordination.md` "Protected Paths":
125
+ - `.claude/hooks/**` → EXCLUDED from mgr-creator routing; requires EXPLICIT USER APPROVAL
126
+ (security-critical). If any scoped issue targets this path, request approval for the
127
+ FULL scoped set NOW, before proceeding — do not let a later step (e.g. compression-mode-
128
+ eval) discover it mid-run after scope is already committed (#1574 찐빠 #5).
129
+ - `.claude/agents/*.md`, `.claude/skills/*/SKILL.md`, `guides/*/` (new dirs) → route to
130
+ mgr-creator at implement time (delegation requirement, not a HALT).
131
+
132
+ If approval was already granted THIS session for the same category+target, do not
133
+ re-request (R015 directive persistence).
134
+
135
+ Output approval_required_paths (list, empty if none) as pipeline state.
136
+
119
137
  Assign scoped issues to milestone.
120
138
  Output markdown release manifest:
121
139
  | order | # | title | prerequisite | effort | labels |
122
140
 
123
141
  Persist manifest as pipeline state for subsequent steps.
124
- description: "Milestone state pre-check, label filter, pick 3-7 issues as v{X.Y.Z} scope"
142
+ description: "Milestone state pre-check, label filter, pick 3-7 issues as v{X.Y.Z} scope, R010 approval-required path pre-check"
125
143
  depends_on: pre-triage
126
144
 
127
145
  - name: compression-mode-eval
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.43",
2
+ "version": "1.1.45",
3
3
  "lastUpdated": "2026-07-14T00:00:00.000Z",
4
4
  "omcustomMinClaudeCode": "2.1.121",
5
5
  "omcustomMinClaudeCodeReason": "Sensitive-path direct Write/Edit on .claude/** under bypassPermissions (R010 deprecation, #1101)",
@@ -116,12 +116,30 @@ steps:
116
116
  4. Cap at 7 issues; if priority issues < 7, stop at that priority (don't mix tiers)
117
117
  5. Minimum 1 issue; if 0 eligible, halt with "no eligible issues for auto-dev run"
118
118
 
119
+ ## Step 3 — Approval-required path pre-check (R010)
120
+
121
+ For each scoped issue, extract target file/directory paths from its title+body (explicit
122
+ paths, backtick-quoted paths, or clearly named targets).
123
+
124
+ Check against `.claude/rules/MUST-orchestrator-coordination.md` "Protected Paths":
125
+ - `.claude/hooks/**` → EXCLUDED from mgr-creator routing; requires EXPLICIT USER APPROVAL
126
+ (security-critical). If any scoped issue targets this path, request approval for the
127
+ FULL scoped set NOW, before proceeding — do not let a later step (e.g. compression-mode-
128
+ eval) discover it mid-run after scope is already committed (#1574 찐빠 #5).
129
+ - `.claude/agents/*.md`, `.claude/skills/*/SKILL.md`, `guides/*/` (new dirs) → route to
130
+ mgr-creator at implement time (delegation requirement, not a HALT).
131
+
132
+ If approval was already granted THIS session for the same category+target, do not
133
+ re-request (R015 directive persistence).
134
+
135
+ Output approval_required_paths (list, empty if none) as pipeline state.
136
+
119
137
  Assign scoped issues to milestone.
120
138
  Output markdown release manifest:
121
139
  | order | # | title | prerequisite | effort | labels |
122
140
 
123
141
  Persist manifest as pipeline state for subsequent steps.
124
- description: "Milestone state pre-check, label filter, pick 3-7 issues as v{X.Y.Z} scope"
142
+ description: "Milestone state pre-check, label filter, pick 3-7 issues as v{X.Y.Z} scope, R010 approval-required path pre-check"
125
143
  depends_on: pre-triage
126
144
 
127
145
  - name: compression-mode-eval