oh-my-customcode 1.1.59 → 1.1.61

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/dist/cli/index.js CHANGED
@@ -243,7 +243,7 @@ var init_package = __esm(() => {
243
243
  workspaces: [
244
244
  "packages/*"
245
245
  ],
246
- version: "1.1.59",
246
+ version: "1.1.61",
247
247
  description: "Batteries-included agent harness for Claude Code",
248
248
  type: "module",
249
249
  bin: {
package/dist/index.js CHANGED
@@ -2539,7 +2539,7 @@ var package_default = {
2539
2539
  workspaces: [
2540
2540
  "packages/*"
2541
2541
  ],
2542
- version: "1.1.59",
2542
+ version: "1.1.61",
2543
2543
  description: "Batteries-included agent harness for Claude Code",
2544
2544
  type: "module",
2545
2545
  bin: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "1.1.59",
6
+ "version": "1.1.61",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -38,6 +38,18 @@ is_readonly_single_command() {
38
38
  return 0
39
39
  fi
40
40
 
41
+ # A NEWLINE is a statement separator, so this function — which classifies a
42
+ # SINGLE statement — must never see one: "read -ra" below stops at the first
43
+ # newline, so "git status\nrm -rf build" used to be judged on "git status"
44
+ # alone and came back read-only (#1647 M-5). Callers split on newlines
45
+ # (Step 6); anything still multi-line here is ambiguous => write.
46
+ case "$cmd" in
47
+ *$'\n'*)
48
+ echo "false"
49
+ return 0
50
+ ;;
51
+ esac
52
+
41
53
  local parts=()
42
54
  read -ra parts <<< "$cmd"
43
55
  local w1="${parts[0]:-}"
@@ -48,8 +60,28 @@ is_readonly_single_command() {
48
60
  # A bare assignment has no side effect outside the shell; when followed by a
49
61
  # command, classify that command instead. Command substitutions inside the
50
62
  # RHS were already validated and replaced by is_readonly_bash_command (#1641).
63
+ #
64
+ # EXCEPTION (#1647 L-1): a few variables change WHICH program runs, or how it
65
+ # is loaded, so the trailing command's NAME no longer describes what the
66
+ # command does — "LD_PRELOAD=./evil.so ls" is not "ls", and "PATH=/tmp ls"
67
+ # runs an arbitrary /tmp/ls. Assignments to those names are writes whether or
68
+ # not a command follows them; an ordinary "FOO=1 ls" still defers to "ls".
69
+ #
70
+ # The git entry is the GLOB "GIT_*", not the three names GIT_DIR /
71
+ # GIT_WORK_TREE / GIT_INDEX_FILE it replaced: git has many more variables
72
+ # that run an arbitrary program (GIT_EXTERNAL_DIFF, GIT_PAGER, GIT_EDITOR,
73
+ # GIT_SSH, GIT_*_COMMAND, ...) or relocate the repository, and enumerating
74
+ # them one at a time keeps losing that race. GLOBIGNORE (changes which files
75
+ # a glob expands to) and BASH_XTRACEFD (sends trace output to an arbitrary
76
+ # descriptor, i.e. a file) are writes for the same reason.
51
77
  case "$w1" in
52
78
  [A-Za-z_]*=*)
79
+ case "${w1%%=*}" in
80
+ PATH|LD_*|DYLD_*|BASH_ENV|ENV|IFS|PROMPT_COMMAND|SHELLOPTS|BASHOPTS|GLOBIGNORE|BASH_XTRACEFD|GIT_*)
81
+ echo "false"
82
+ return 0
83
+ ;;
84
+ esac
53
85
  if [ "${#parts[@]}" -le 1 ]; then
54
86
  echo "true"
55
87
  return 0
@@ -62,13 +94,40 @@ is_readonly_single_command() {
62
94
  esac
63
95
 
64
96
  case "$w1" in
65
- ls|cat|head|tail|grep|rg|wc|jq|md5|md5sum|type|command|which|echo|printf|pwd|date)
97
+ ls|cat|head|tail|grep|rg|wc|jq|md5|md5sum|type|which|echo|printf|pwd|date)
66
98
  echo "true"
67
99
  return 0
68
100
  ;;
101
+ command)
102
+ # "command" used to sit in the whitelist above, which made
103
+ # "command rm -rf x" read-only (#1647 L-5) — it is a PREFIX around an
104
+ # arbitrary program, not a program. Only the lookup forms ("command -v",
105
+ # "command -V") are read-only. "builtin" and "exec" are deliberately
106
+ # absent from the whitelist for the same reason: they fall through to the
107
+ # "*)" write default below.
108
+ case "$w2" in
109
+ -v|-V)
110
+ echo "true"
111
+ ;;
112
+ *)
113
+ echo "false"
114
+ ;;
115
+ esac
116
+ return 0
117
+ ;;
69
118
  find)
119
+ # Writing actions (#1647 L-5): -delete/-exec/-execdir remove or run
120
+ # things, -ok/-okdir are the interactive forms of -exec, and
121
+ # -fprint/-fprint0/-fprintf/-fls all CREATE a file. Each pattern is
122
+ # wrapped in "*" on BOTH sides, so it matches the fragment ANYWHERE in
123
+ # the command, not just as an option prefix: that is how the "dir"/"0"/
124
+ # "f" variants ("-execdir", "-fprint0", "-okdir") are covered by a single
125
+ # entry, and it also means an operand that merely CONTAINS the fragment
126
+ # (e.g. "find . -name '*-exec*'") is classified as a write. That
127
+ # over-match is the safe direction — a write verdict only ever tightens
128
+ # blocking.
70
129
  case "$cmd" in
71
- *-delete*|*-exec*|*-fprintf*)
130
+ *-delete*|*-exec*|*-fprint*|*-fls*|*-ok*)
72
131
  echo "false"
73
132
  ;;
74
133
  *)
@@ -133,6 +192,61 @@ is_readonly_single_command() {
133
192
  esac
134
193
  }
135
194
 
195
+ # Remove heredoc BODIES ("<<DELIM", "<<-'DELIM'") from a command, keeping the
196
+ # header line. A heredoc body is DATA, not statements: without this, the
197
+ # newline splitting in Step 6 (#1647 M-5) would classify every data line as a
198
+ # command and turn "cat <<'EOF' ... EOF" — read-only before #1647 — into a
199
+ # write. Herestrings ("<<<") are single-line and are NOT heredocs. An
200
+ # UNTERMINATED heredoc is unparseable, so a sentinel word is emitted instead
201
+ # and the caller classifies the command as a write. The same sentinel route is
202
+ # taken when the "<<" turns out to sit INSIDE a quoted string (see below).
203
+ _strip_heredoc_bodies() {
204
+ local text="$1"
205
+ local out="" line tok delim="" in_body=0
206
+ local before_op dq sq
207
+ while IFS= read -r line; do
208
+ if [ "$in_body" -eq 1 ]; then
209
+ # "<<-" allows leading tabs before the terminator; accept leading
210
+ # whitespace for either form.
211
+ tok="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
212
+ if [ "$tok" = "$delim" ]; then
213
+ in_body=0
214
+ fi
215
+ continue
216
+ fi
217
+ out="${out}${line}"$'\n'
218
+ # A DELIMITER FORGERY guard: the extraction below has no idea whether the
219
+ # "<<" it finds is shell syntax or literal text inside a quoted string, so
220
+ # `echo "a << EOF"` followed by `rm -rf x` and a line reading `EOF` used to
221
+ # hand the rm to the heredoc BODY — where it was dropped as data — and the
222
+ # command came back read-only. Quote state is not tracked here, so rather
223
+ # than guess, count the quotes preceding the "<<": an ODD number of " or '
224
+ # means the "<<" is quoted, the command is unparseable by this function,
225
+ # and the sentinel makes the CALLER classify the whole multi-line command
226
+ # as a write. "${line%<<*}" strips the shortest matching suffix, i.e. it
227
+ # yields the text before the LAST "<<" — the same occurrence the greedy
228
+ # extraction regex below settles on.
229
+ if [ "$line" != "${line%<<*}" ]; then
230
+ before_op="${line%<<*}"
231
+ dq="$(printf '%s' "$before_op" | tr -cd '"' | wc -c | tr -d '[:space:]')"
232
+ sq="$(printf '%s' "$before_op" | tr -cd "'" | wc -c | tr -d '[:space:]')"
233
+ if [ $((dq % 2)) -eq 1 ] || [ $((sq % 2)) -eq 1 ]; then
234
+ printf '%s' "${out}__QUOTED_HEREDOC_OPENER__"$'\n'
235
+ return 0
236
+ fi
237
+ fi
238
+ delim="$(printf '%s' "$line" \
239
+ | sed -n -E "s/^.*[^<]<<-?[[:space:]]*[\"']?([A-Za-z_][A-Za-z0-9_]*)[\"']?.*\$/\\1/p")"
240
+ if [ -n "$delim" ]; then
241
+ in_body=1
242
+ fi
243
+ done <<< "$text"
244
+ if [ "$in_body" -eq 1 ]; then
245
+ out="${out}__UNTERMINATED_HEREDOC__"$'\n'
246
+ fi
247
+ printf '%s' "$out"
248
+ }
249
+
136
250
  # Maximum recursion depth for nested command substitution / loop bodies.
137
251
  _RO_MAX_DEPTH=5
138
252
  # Maximum command length that is parsed at all (see Step 0 below).
@@ -235,9 +349,14 @@ is_readonly_bash_command() {
235
349
  # --- Step 3: strip harmless redirections BEFORE the ">" write check.
236
350
  # "2>&1", ">/dev/null", "2>/dev/null", "1>/dev/null", "&>/dev/null" produce no
237
351
  # observable side effect. Any OTHER ">" (a real file write) remains a write.
352
+ # ">&N" is stripped ONLY for N in {1,2} (stdout/stderr). An arbitrary
353
+ # descriptor writes wherever that fd was opened, so "cat x >&3" stays a write
354
+ # (#1647 L-2). The trailing "[^0-9]" / "$" guard stops ">&10" from being read
355
+ # as ">&1" plus a stray "0"; the two expressions differ only in whether the
356
+ # redirection is followed by another character or ends the command.
238
357
  local stripped
239
358
  stripped="$(printf '%s' "$cmd" \
240
- | sed -E 's/[0-9]?>&[0-9]/ /g; s#[0-9]?&?>[[:space:]]*/dev/null# #g')"
359
+ | sed -E 's/[0-9]?>&[12]([^0-9])/ \1/g; s/[0-9]?>&[12]$/ /; s#[0-9]?&?>[[:space:]]*/dev/null# #g')"
241
360
  case "$stripped" in
242
361
  *'>'*)
243
362
  echo "false"
@@ -296,10 +415,38 @@ is_readonly_bash_command() {
296
415
  ;;
297
416
  esac
298
417
 
299
- # --- Step 6: compound command — split on "&&", ";", or a single "|" and
300
- # require EVERY segment to be read-only (#1629). An empty segment (e.g. a
301
- # trailing separator) is ambiguous => write.
302
- if [[ "$cmd" == *'&&'* || "$cmd" == *';'* || "$cmd" == *'|'* ]]; then
418
+ # --- Step 6: compound command — split on "&&", ";", a single "|", or a
419
+ # NEWLINE and require EVERY segment to be read-only (#1629, #1647 M-5).
420
+ # A newline separates statements exactly like ";"; before #1647 it was not a
421
+ # separator at all, so a multi-line command fell through to
422
+ # is_readonly_single_command, whose "read -ra" only ever saw the FIRST line
423
+ # ("git status\nrm -rf build" => read-only).
424
+ #
425
+ # Order matters: the ">" (Step 3) and "||" (Step 4) checks above still ran
426
+ # against the FULL text including any heredoc body, so those verdicts are
427
+ # unchanged. Heredoc bodies are dropped only here, right before the split.
428
+ # CR / CRLF are newlines too.
429
+ cmd="${cmd//$'\r\n'/$'\n'}"
430
+ cmd="${cmd//$'\r'/$'\n'}"
431
+ if [[ "$cmd" == *$'\n'* ]]; then
432
+ # A heredoc body needs a following line, so only a MULTI-LINE command can
433
+ # have one — checking "<<" on a single-line command would misread a literal
434
+ # "<<" (e.g. `echo "a << b"`) as an opener.
435
+ case "$cmd" in
436
+ *'<<'*)
437
+ cmd="$(_strip_heredoc_bodies "$cmd")"
438
+ ;;
439
+ esac
440
+ # A BLANK line is not a statement, so drop blank lines before splitting;
441
+ # otherwise they would look like an empty — i.e. ambiguous — segment. A
442
+ # genuinely empty segment from a trailing ";" or "&&" still means write.
443
+ cmd="$(printf '%s' "$cmd" | sed -e '/^[[:space:]]*$/d')"
444
+ if [ -z "$cmd" ]; then
445
+ echo "false"
446
+ return 0
447
+ fi
448
+ fi
449
+ if [[ "$cmd" == *'&&'* || "$cmd" == *';'* || "$cmd" == *'|'* || "$cmd" == *$'\n'* ]]; then
303
450
  local normalized="$cmd"
304
451
  normalized="${normalized//&&/$'\n'}"
305
452
  normalized="${normalized//;/$'\n'}"
@@ -360,11 +507,25 @@ else
360
507
  file_path_full=$(printf '%s' "$input" | jq -r '.tool_input.file_path // ""')
361
508
  # file_path is display/guard only (basename in the messages); target_key is
362
509
  # the value matched against history, so only it carries the length tag.
363
- file_path=$(printf '%s' "$file_path_full" | head -c "$TARGET_KEY_CAP")
510
+ # "|| true": same broken-pipe class as output_preview below (#1647 M-6) —
511
+ # an over-cap file_path would make head close the pipe and take the hook
512
+ # down through pipefail. The captured bytes are unaffected.
513
+ file_path=$(printf '%s' "$file_path_full" | head -c "$TARGET_KEY_CAP" || true)
364
514
  target_key=$(truncate_key "$file_path_full")
365
515
  fi
366
516
  is_error=$(printf '%s' "$input" | jq -r '.tool_output.is_error // false')
367
- output_preview=$(printf '%s' "$input" | jq -r '.tool_output.output // ""' | head -c 200)
517
+ # "head -c 200" closes the pipe after 200 bytes. Once tool_output.output is
518
+ # larger than the pipe buffer (~64 KB), jq keeps writing, dies of SIGPIPE
519
+ # (exit 141), and "set -o pipefail" + "set -e" aborted the ENTIRE hook before
520
+ # the history entry was ever appended — a 300 KB tool output silently erased
521
+ # the record it was supposed to create (#1647 M-6). Two independent guards:
522
+ # (a) slice inside jq so nothing large is piped at all, and (b) "|| true" so a
523
+ # broken pipe can never again take the hook down. The byte cap is still
524
+ # enforced by head (jq slices by codepoint, head by byte, and 200 codepoints
525
+ # are always >= 200 bytes, so the resulting bytes are unchanged).
526
+ output_preview=$(printf '%s' "$input" \
527
+ | jq -r '(.tool_output.output // "" | tostring)[0:200]' \
528
+ | head -c 200 || true)
368
529
  raw_command=$(printf '%s' "$input" | jq -r '.tool_input.command // ""')
369
530
 
370
531
  # Distinguish "the same file edited 3 times with DIFFERENT content" (a normal
@@ -117,6 +117,9 @@ Reference issue: #1096.
117
117
  | Anti-pattern | Required |
118
118
  |--------------|----------|
119
119
  | 도구 결과 수신 후 응답 텍스트 없이 turn 종료 ("(no content)") | 최소 1줄 진행 상태 + R007 헤더 출력 후 종료/계속 |
120
+ | 백그라운드 에이전트 완료 통지·task-notification 등 **system-reminder 통지 수신 후** 도구만 호출하고 텍스트 없이 turn 종료 | 통지 처리 턴에도 헤더 + 상태 1줄 출력 후 도구 호출 |
121
+
122
+ v1.1.58 세션 트랜스크립트 실측 — 응답 22건 중 3건이 텍스트 블록 없이 tool_use만 있었고 전부 통지 수신 직후 턴이었다(#1646).
120
123
 
121
124
  <!-- Reference issues: #1188 item #2, #1198 item #2, #1409. -->
122
125
 
@@ -271,10 +271,14 @@ This is the interrupt-intent extension of Read-Before-Characterize ("actual inte
271
271
  | 에러 원인 추정 | 첫 가설로 워크플로우/설정 영구 수정 | 가설을 좁은 범위에서 검증 후 변경 |
272
272
  | CI/publish 실패 | 추정 기반 우회 커밋 머지 | 에러 메시지/로그로 실제 원인 확정 |
273
273
  | 권한/토큰 오류 | 플래그/옵션 변경으로 우회 시도 | 권한 범위·토큰 종류 직접 확인 |
274
+ | 트랜스크립트·로그 통계로 원인 추정 | 통계적 상관(예: "첫 레코드 thinking 13/21")만으로 코드 경로의 인과를 "확정"해 이슈 코멘트·메모리에 기록 | 해당 코드 경로를 읽고 판정 로직을 1:1 재현(jq/스크립트)해 인과를 확인한 뒤 기록; 그 전에는 R011 `[hypothesis]` 태그로만 저장 |
274
275
 
275
276
  ### Common Violation (#1217 item #4)
276
277
  npm publish E403을 `--provenance` attestation 충돌로 오진단 → release workflow에서 `--provenance` 제거 커밋 머지 → 2차 시도 동일 실패 → 실제 원인은 NPM_TOKEN 권한(Automation token 필요). 잘못된 추정으로 릴리즈 워크플로우를 영구 변경.
277
278
 
279
+ ### Common Violation (#1652 #1) — 통계적 상관 ≠ 코드 인과
280
+ v1.1.59 세션에서 #1643(advisor "R007 헤더=0" 오탐) 원인을 트랜스크립트 통계만으로 "advisor가 thinking 전용 첫 레코드를 병합하지 못함"이라 이슈 코멘트·feedback 메모리에 확정 기록 → triage가 현행 코드(이미 병합·thinking 제외)와의 불일치를 지적 → 진단 에이전트가 jq로 판정 로직을 1:1 재현해 반박 — 실제 원인은 레이블 문구 모호성(값은 위반 건수인데 "헤더=0"으로 읽힘). 같은 세션 2회째: `gh pr merge --delete-branch` 로컬 부수효과를 reflog 3건으로 "확정" 저장했으나 4번째 머지(PR #1651)에서 재현되지 않아 "원인 미확정"으로 정정. 두 건 모두 상관을 인과로 승격한 Read-Before-Characterize 자기 위반이며, R011 즉시 저장과 결합해 틀린 전제가 영속화될 뻔했다. Cross-ref: R011 「Mid-Session Immediate Save」 `[hypothesis]` 행, R010 「저장소 상태 기재도 같은 규율」, 아래 「Self-Violation Counting Is Also Diagnosis」.
281
+
278
282
  ### Self-Check (영구 변경 전)
279
283
  1. 가설을 뒷받침하는 직접 증거(로그/에러 코드/문서)가 있는가?
280
284
  2. 비파괴적 방법으로 가설을 검증할 수 있는가?
@@ -338,6 +338,22 @@ Origin: #1563 찐빠 #3 — `gh issue edit --assignee`가 gh 2.86.0에 없는
338
338
 
339
339
  Origin: #1584 #5 (v1.1.45 세션 — 커밋 위임서에 `develop @ 96ef8f85`로 적었으나 실측은 `f6d3f518`; #1572 머지 후 pull 미반영). R017 「메모리 TODO를 위임 전제로 쓸 때」의 **세션 내 축소판** — 스냅샷의 수명이 세션 간이 아니라 **턴 간**이라는 차이만 있다.
340
340
 
341
+ **사전 실측 리터럴 (Origin: #1646)**: 저장소 상태를 기재한 위임서를 작성하기 **직전에** 아래 3줄을 그대로 실행한다.
342
+
343
+ ```
344
+ git rev-parse --abbrev-ref HEAD
345
+ git rev-parse --short HEAD
346
+ git branch --list
347
+ ```
348
+
349
+ 이 3줄의 **출력 원문**을 위임서 '사전 실측' 절에 그대로 붙여 넣고, 브랜치 존재·SHA를 예측형으로 적지 않는다 — 도구의 부수효과(예: `gh pr merge --delete-branch`가 체크아웃된 브랜치를 base로 전환·pull·삭제하는 부수효과)를 모른 채 이전 관측을 그대로 옮기면 상태가 어긋난다.
350
+
351
+ | Anti-pattern | Required |
352
+ |--------------|----------|
353
+ | `gh pr merge --delete-branch` 등 부수효과가 있는 명령 실행 후, 그 이전에 관측한 브랜치/SHA를 위임서에 그대로 기재 | 위임 직전 위 3줄을 재실행하고 출력 원문을 위임서에 붙여 넣음 |
354
+
355
+ Origin: #1646 (v1.1.58 세션 — 커밋 위임서 `12e74a76` vs 실제 `e101a0b4`, 정리 위임서 '로컬 브랜치 2개 존재' vs 실제 부재; 원인은 `gh pr merge --delete-branch`가 체크아웃된 브랜치를 base로 전환·pull·삭제하는 부수효과였음 — 도구 부수효과를 모른 채 상태를 예측한 사례).
356
+
341
357
  #### 참인 전제 ≠ 참인 함의 — 브랜치 전환 위임 (Origin: #1595 #2)
342
358
 
343
359
  uncommitted 변경이 있는 상태의 브랜치 전환을 위임할 때, 위임서에 **전환의 결과를 확정형으로 예측해 적지 않는다**. "대상 브랜치에 그 파일이 없다"는 **사실**에서 "전환해도 안전하다"는 **함의**는 도출되지 않는다 — 파일이 **없기 때문에** checkout이 그 파일을 삭제해야 하고, modified 상태면 거부된다.
@@ -674,6 +690,8 @@ Subagent NOT required for:
674
690
 
675
691
  "Simple" means READ-ONLY operations. If the task involves any file creation, modification, or deletion, it must be delegated. There is no "too small to delegate" exception for write operations.
676
692
 
693
+ **Carve-out — PPID 스코프 `/tmp` 런타임 상태 마커 (Origin: #1650 C, v1.1.61)**: 프로젝트 트리 **밖**의 `/tmp` 아래에 세션 프로세스 ID로 스코프된 1줄짜리 런타임 마커(예: `/tmp/.claude-fsd-$PPID`)를 만들거나 지우는 것은 프로젝트 파일 쓰기가 아니므로 오케스트레이터가 Bash 한 줄로 직접 수행할 수 있다. 조건: (1) 경로가 `/tmp` 하위이고 프로세스 ID로 스코프됨, (2) 내용이 타임스탬프 등 1줄 신호에 불과함, (3) 프로젝트 트리·홈 디렉토리·설정 파일을 건드리지 않음. 구조화된 파이프라인 상태(`/tmp/.claude-pipeline-{name}-{PPID}.json`)는 이 carve-out 대상이 아니며 기존대로 `tracker-checkpoint`에 위임한다 — 마커와 상태 파일의 차이는 내용 구조(1줄 신호 vs 검증이 필요한 JSON 상태)다. 위임 비용(스폰 30초 이상)이 작업(1줄 touch)보다 큰 경우를 위한 좁은 예외이며, "작아서" 예외가 아니라 **프로젝트 파일이 아니라서** 예외다.
694
+
677
695
  ## Dynamic Agent Creation (No-Match Fallback)
678
696
 
679
697
  When routing detects no matching agent for a specialized task:
@@ -131,6 +131,10 @@ Origin: #1521 (찐빠 #2 — v1.1.32 skills 118→114 동기화에서 파일 열
131
131
 
132
132
  Any change to: agents, agent frontmatter, skills, guides, routing patterns, rules, wiki pages.
133
133
 
134
+ ### auto-dev.yaml 압축 티어와의 양방향 참조 (Origin: #1650 C 부수)
135
+
136
+ 이 소절은 R017 적용 범위 자체를 넓히지 않는다 — 위 범위 열거가 압축 파이프라인에서 어떻게 배선되는지를 기록한다. `.claude/skills/pipeline/workflows/auto-dev.yaml`의 compression-mode-eval은 docs-only 티어에서 deep-verify를 self-review로 대체하되, 변경 집합에 `.claude/rules/**`(또는 agents/skills frontmatter 등 구조 표면)가 포함되면 mgr-sauron R017 검증을 단일 목표 위임으로 **필수 실행**하는 carve-out을 둔다(v1.1.58 세션 — docs-only 티어였으나 sauron이 R002/R010 모순 advisory 1건을 포착). 이 절의 적용 조건과 그 carve-out은 같은 조건을 서로 가리킨다 — 한쪽을 바꾸면 다른 쪽도 같은 커밋에서 갱신한다(R016 Rule Wiring Check). 이전까지는 yaml → R017 단방향 인용만 있어 R017 쪽에서 carve-out의 존재를 알 수 없었다.
137
+
134
138
  ## Structural Migration Verification
135
139
 
136
140
  디렉토리 재구조화, 템플릿 평탄화(flat templates), 브랜치 전략 변경 등 **구조 마이그레이션** 시, 경로 참조와 파일 존재성 회귀를 사전 검사해야 한다. 표준 5-round 검증이 콘텐츠 정합성에 집중하는 반면, 구조 마이그레이션은 경로·존재성 회귀를 별도로 점검한다.
@@ -78,6 +78,17 @@ Agent(description: "[2] Python code review", subagent_type: "lang-python-expert"
78
78
 
79
79
  Single agent spawns do NOT use the `[N]` prefix.
80
80
 
81
+ **단일 스폰도 Core Rule 접두사 필수 (Origin: #1652 #3-3)**: 단일 Agent 스폰은 `[N]` 항목 형식 대신 Core Rule 형식의 접두사 라인(에이전트·모델 대괄호 + 화살표 + Tool 표기 + `Agent`)을 호출 직전에 출력한다. advisor(`.claude/hooks/scripts/r007-r008-drift-advisor.sh`)가 announce로 계수하는 것은 3종 — Tool 표기 라인, Spawning 헤더 라인(대괄호 번호 항목이 0개일 때 폴백으로 계수), 대괄호 번호가 붙은 spawn 항목 — 이며, **대괄호 번호 없이 에이전트타입:모델 다음에 화살표와 설명만 이어붙인 단독 라인은 계수하지 않는다** — Tool 표기 라인도 Spawning 헤더도 없이 그 단독 라인만 쓰면 규칙을 지킨 응답이 R008 누락 1건으로 계상된다(v1.1.60 세션 실측 — advisory가 반복돼 세션 중 접두사 형식을 바꿔야 했다; v1.1.61 적대적 리뷰가 합성 트랜스크립트 양성/음성 짝으로 재현). 정합 방향은 **규칙 문구 쪽**으로 확정한다. advisor 정규식 쪽 대안(번호 없는 단독 라인을 spawn 항목으로 계수)은 `.claude/hooks/**` 승인 사안이라 **#1650 E 항목**으로 이월했으며, 적용 시 이 조항의 advisor 동작 서술을 같은 커밋에서 갱신한다(R016 Rule Wiring Check).
82
+
83
+ ```
84
+ [claude][opus] → Tool: Agent
85
+ [claude][opus] → Target: mgr-updater:sonnet → R008 clause update
86
+ ```
87
+
88
+ | Anti-pattern | Required |
89
+ |--------------|----------|
90
+ | 단일 스폰을 번호 없는 에이전트타입:모델 화살표 설명 단독 라인으로만 announce | Core Rule 접두사 라인(Tool 표기 + Agent)을 호출 직전에 출력; 항목 라인은 Target 라인으로 병기 가능 |
91
+
81
92
  This ensures the Running display:
82
93
  ```
83
94
  ⏺ Running 2 agents… (ctrl+o to expand)
@@ -358,6 +358,9 @@ Save memory IMMEDIATELY upon surprising discovery — do not defer to session en
358
358
  | Unexpected tool behavior / workaround | Save `feedback_*.md` now | Session state defense |
359
359
  | Subagent false-positive detected | Save `feedback_*.md` now | Prevent repeat in same session |
360
360
  | User correction / feedback | Save `feedback_*.md` now | Honor correction immediately |
361
+ | Root-cause hypothesis (원인 진단) | Save `feedback_*.md` ONLY with a `[hypothesis: <unverified-basis summary>]` first-line tag until the code path has been read and the decision logic reproduced 1:1; promote to plain fact only after that verification | Statistical correlation from transcript counts is NOT code causation — an unverified cause saved as fact propagates to issues and future sessions (#1652 #1; R020 「통계적 상관 ≠ 코드 인과」) |
362
+
363
+ **`[hypothesis: …]` 태그 규약 (Origin: #1652 #1)**: 원인 진단을 메모리에 즉시 저장할 때는 본문 첫 줄에 `[hypothesis: <미검증 근거 요약>]`를 붙인다. 검증(코드 경로 대조·판정 로직 1:1 재현) 완료 시 태그를 제거하거나 파일을 정정한다. 태그가 남아 있는 항목은 후속 세션에서 전제로 쓰지 않고 **검증 트리거로만** 사용한다 — 위 「Safety-Related Feedback Memory Framing」과 같은 원리(결론형이 아니라 검증 의무형)다. 실증: v1.1.59 세션에서 #1643 원인을 트랜스크립트 통계(첫 레코드 thinking 13/21)만으로 "advisor가 thinking 전용 첫 레코드를 병합하지 못함"이라 확정 저장했다가 jq 1:1 재현으로 반박됐고(실제 원인은 레이블 문구 모호성), 같은 세션에서 `gh pr merge --delete-branch` 로컬 부수효과도 reflog 3건으로 "확정" 저장한 뒤 4번째 머지에서 재현되지 않아 "원인 미확정"으로 정정했다. 리터럴은 `[hypothesis:` 접두 하나로 통일하며, 검증 여부 조회는 `grep -l '^\[hypothesis:' <memory dir>`로 결정론적으로 수행한다. 최초 적용 대상: `feedback_gh_merge_delete_branch_local_side_effect.md`(v1.1.60 세션에서 "원인 미확정"으로 정정된 항목) — v1.1.61 세션 종료 시 sys-memory-keeper가 태그를 부착한다.
361
364
 
362
365
  See rationale and cross-references via Read tool.
363
366
 
@@ -69,9 +69,44 @@ All PR merge operations are delegated to **mgr-gitnerd** (R010). After merging,
69
69
 
70
70
  Do NOT merge PRs that require user approval for architectural decisions. Surface them with a short summary and wait.
71
71
 
72
+ ### `.claude/hooks/**` 이슈 — 무인 루프 분리 스코프
73
+
74
+ FSD는 사용자가 실시간 응답할 수 없는 무인 루프이므로, scope-selection의 R010 approval-required
75
+ path pre-check(`auto-dev.yaml`)가 `.claude/hooks/**` 대상 이슈를 발견하면 매 이터레이션마다
76
+ 즉시 승인을 요청하지 않는다. 대신 그 이슈를 이번 스코프에서 **분리·이월**하고 나머지 적격 이슈로
77
+ 계속 진행하며, `/homework` 반복 경계(게이트)에서 이월된 hooks 이슈들을 **1회 묶어** 승인 질문으로
78
+ 제시한다. 승인을 받으면 R015 directive persistence로 세션 내 동일 카테고리에 지속 적용한다.
79
+ 무인 여부의 판별 신호는 아래 「무인 모드 마커」 절이 정의한다.
80
+
81
+ ### 무인 모드 마커 — 결정론적 판별 신호 (#1650 C)
82
+
83
+ `pipeline auto-dev`의 scope-selection Step 3가 요구하는 "무인 여부"는 산문 추론이 아니라 마커 파일로
84
+ 판정한다. FSD 진입 시 오케스트레이터가 Bash로 아래 한 줄을 실행한다. 이 마커는 R010 「Exception:
85
+ Simple Tasks」의 carve-out(PPID 스코프 `/tmp` 런타임 상태 마커 — v1.1.61 신설)에 해당하므로
86
+ 오케스트레이터가 직접 실행한다. 구조화된 파이프라인 상태(`/tmp/.claude-pipeline-*-{PPID}.json`)는 이
87
+ 예외 대상이 아니며 기존대로 `tracker-checkpoint`에 위임된다.
88
+
89
+ ```bash
90
+ printf '%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "/tmp/.claude-fsd-$PPID"
91
+ ```
92
+
93
+ - `$PPID`는 Bash 도구 셸의 부모, 즉 Claude Code 프로세스이며 호출 간 안정적이다(2026-09-03 실측: 두
94
+ 호출 모두 동일 값).
95
+ - `claude -p`·예약 실행처럼 마커를 미리 만들 수 없는 경로는 env `OMCUSTOM_UNATTENDED=1`로 대체한다.
96
+ - `pipeline auto-dev` pre-triage Phase 0.6이 마커 존재 또는 env 값을 실측해 `unattended_mode` 상태를
97
+ 산출하고, scope-selection Step 3는 그 상태로 `.claude/hooks/**` 이슈 분리·이월을 결정한다.
98
+ - 매 반복 시작 시 같은 명령으로 마커를 다시 써서 mtime을 갱신한다. Phase 0.6은 mtime이 6시간(360분)보다
99
+ 오래된 마커를 **부재로 취급**한다 — 프로세스 크래시·PID 재사용으로 남은 마커가 이후 수동
100
+ `/pipeline auto-dev`를 무인으로 오판하지 않게 하는 가드다.
101
+ - 수렴(`[FSD Done]`), 릴리즈 cap 도달, safety classifier 차단으로 인한 중단, 사용자 인터럽트 — **어느
102
+ 경로로 끝나든** `command rm -f "/tmp/.claude-fsd-$PPID"`로 마커를 제거한다 — 남겨두면
103
+ 같은 세션의 이후 수동 `/pipeline auto-dev`가 무인으로 오판된다. `rm`이 alias(`trash`)로 셰이딩된
104
+ 셸에서 `rm -f`는 거부되므로 `command`를 붙인다(R005, 2026-09-03 실측).
105
+
72
106
  ### Iteration Flow (per iteration)
73
107
 
74
108
  ```
109
+ [FSD Entry] write marker /tmp/.claude-fsd-$PPID
75
110
  [FSD Iteration N]
76
111
  ├── /pipeline auto-dev → one release (PR create → merge → npm publish → milestone close)
77
112
  ├── /homework → extract 찐빠, confirm gate (or --dry-run if requested)
@@ -79,8 +114,9 @@ Do NOT merge PRs that require user approval for architectural decisions. Surface
79
114
  │ fix-then-merge (known CI failure pattern)
80
115
  │ defer+surface (design decision needed)
81
116
  └── Check convergence: eligible issues = 0 AND open PRs = 0 (merged or deferred)?
82
- ├── YES → [FSD Done] converged naturally
83
- └── NO → next iteration
117
+ ├── YES → [FSD Done] converged naturally → command rm -f /tmp/.claude-fsd-$PPID
118
+ └── NO → cap reached or classifier block? → YES → [FSD Stop] → command rm -f /tmp/.claude-fsd-$PPID
119
+ → NO → next iteration (re-write marker first)
84
120
  ```
85
121
 
86
122
  ## Safety and Discipline
@@ -98,6 +134,16 @@ Each iteration operates under full project rules — no relaxation because FSD i
98
134
 
99
135
  `/homework` runs as a **retrospective gate** between iterations — findings go through `omcustom-feedback`'s Phase 4A confirmation gate. The loop does NOT skip homework on the grounds that it is "automated". If homework requires user confirmation (e.g., to file a feedback issue), the loop pauses and waits.
100
136
 
137
+ **Homework 제출 게이트 묶음 (opt-in, #1652 #4)**: 기본 계약은 위와 같이 **반복마다** 게이트를
138
+ 제시하는 것이다. 사용자가 세션 중 명시적으로 지시하거나(예: "homework 게이트는 수렴 시점에 1회로
139
+ 묶어라") 무인 실행을 선언하며 묶음을 지정한 경우에만, homework 아티팩트(`homework-{HHmmss}.md`)는
140
+ 반복마다 기록하되 `omcustom-feedback` Phase 4A 제출 게이트는 수렴 시점(또는 릴리즈 cap 도달 시점)에
141
+ **1회** 묶어 제시한다 — 이월된 `.claude/hooks/**` 승인 질문과 같은 자리에서 함께 제시한다. 묶음
142
+ 여부는 사용자 결정 사항이며 오케스트레이터가 단독으로 선택하지 않는다(v1.1.59/60 세션에서
143
+ 오케스트레이터가 단독 결정한 것이 스킬 계약 이탈로 회고됐다). 한 번 지시되면 R015 directive
144
+ persistence로 세션 내 유지된다. 묶음 여부는 `unattended_mode`(마커·env)에서 **도출하지 않는다** —
145
+ 무인 판별은 hooks 이슈 분리·이월에만 쓰이고, 게이트 묶음은 사용자 지시가 유일한 트리거다.
146
+
101
147
  If a release operation triggers the safety classifier, the current iteration stops and surfaces the block to the user before continuing.
102
148
 
103
149
  ## When to Use
@@ -78,6 +78,31 @@ steps:
78
78
  4. Do NOT read, echo, or modify any credential material while checking settings — read the
79
79
  single `permissions.defaultMode` field only (R001).
80
80
 
81
+ Phase 0.6 — Detect unattended mode deterministically (#1650 C).
82
+
83
+ Why: scope-selection Step 3 defers `.claude/hooks/**` issues in unattended runs. Until v1.1.60 the
84
+ signal was prose ("entered via /fsd or the session says autonomous"), which is not measurable.
85
+ `/fsd` now writes a marker at entry (fsd SKILL.md 「무인 모드 마커」) and removes it at convergence.
86
+
87
+ 1. Measure (both signals; either one is sufficient):
88
+ marker="/tmp/.claude-fsd-$PPID"; unattended_mode=false; marker_state=absent
89
+ if [ -f "$marker" ]; then
90
+ if [ -n "$(command find "$marker" -mmin +360 2>/dev/null)" ]; then marker_state=stale; else marker_state=present; fi
91
+ fi
92
+ if [ "$marker_state" = "present" ] || [ "${OMCUSTOM_UNATTENDED:-}" = "1" ]; then unattended_mode=true; fi
93
+ echo "unattended_mode=$unattended_mode marker=$marker_state env=${OMCUSTOM_UNATTENDED:-unset}"
94
+ `$PPID` inside the Bash tool is the Claude Code process and is stable across calls, so the
95
+ marker written by `/fsd` in an earlier call is visible here.
96
+ A marker older than 360 minutes is treated as absent (stale guard — crash / PID reuse); `/fsd`
97
+ re-writes the marker at every iteration start. The snippet always exits 0 so a false result is
98
+ not mistaken for a measurement failure.
99
+
100
+ 2. Report: "[pre-triage] unattended_mode=<true|false> (marker /tmp/.claude-fsd-$PPID: <present|stale|absent>; OMCUSTOM_UNATTENDED=<value|unset>)"
101
+
102
+ 3. Output `unattended_mode` as pipeline state for scope-selection Step 3. Do NOT infer unattended
103
+ mode from prose or from the fact that this pipeline was invoked by a skill — only the two
104
+ measured signals above count.
105
+
81
106
  Phase 1 — Ensure required labels exist, then scan issues.
82
107
 
83
108
  1. Create labels if missing (idempotent):
@@ -164,6 +189,14 @@ steps:
164
189
  If approval was already granted THIS session for the same category+target, do not
165
190
  re-request (R015 directive persistence).
166
191
 
192
+ 무인 실행(`/fsd` 등 사용자가 실시간 응답할 수 없는 루프)에서는 `.claude/hooks/**` 대상 이슈를
193
+ 이번 스코프에서 **분리·이월**하고 나머지 적격 이슈로 진행하며, 반복 경계(homework 게이트)에서
194
+ 승인 질문을 **1회** 묶어 제시한다. 승인을 받으면 R015 directive persistence로 세션 내 동일
195
+ 카테고리에 지속 적용. 유인 모드에서는 기존대로 즉시 승인 요청. 무인 판별은 pre-triage Phase 0.6이 실측한
196
+ `unattended_mode` 상태를 사용한다(마커
197
+ `/tmp/.claude-fsd-$PPID` 또는 env `OMCUSTOM_UNATTENDED=1`; #1650 C). 산문 추론("/fsd로 진입한 것
198
+ 같다")으로 무인 여부를 판정하지 않는다.
199
+
167
200
  Output approval_required_paths (list, empty if none) as pipeline state.
168
201
 
169
202
  Assign scoped issues to milestone.
@@ -194,6 +227,10 @@ steps:
194
227
  - plan step: skip release-plan skill; single-response plan instead
195
228
  - deep-plan step: skip deep-plan skill; single-response implementation notes instead
196
229
  - deep-verify step: skip deep-verify skill; perform self-review checklist instead
230
+ — 단, 변경 집합에 `.claude/rules/**`(또는 agents/skills frontmatter 등 구조 표면)가
231
+ 포함되면 self-review로 대체하지 않고 mgr-sauron R017 검증을 **단일 목표 위임으로 필수
232
+ 실행**한다(Tier 2 lite의 R017 조항과 동일 원칙; Origin: v1.1.58 세션 — docs-only 티어였으나
233
+ sauron이 R002/R010 모순 advisory 1건을 포착).
197
234
  - Log: "[compression-mode] docs-only compression activated (scope={n}, all docs/yaml labels)"
198
235
  - ⚠ Analysis artifacts only. Every step's state-change side effect (milestone create/assign,
199
236
  label add/remove, issue assign/comment/close) still runs in full — see "Cross-tier —
@@ -213,7 +250,7 @@ steps:
213
250
  - triage step: MAY replace professor-triage skill spawn with orchestrator integrated analysis
214
251
  - plan step: MAY replace release-plan skill spawn with orchestrator integrated analysis
215
252
  - deep-plan step: MAY replace deep-plan skill spawn with orchestrator integrated analysis
216
- - deep-verify step: perform via mgr-sauron R017 structural verification + core self-check
253
+ - deep-verify step: perform via mgr-sauron R017 structural verification + core self-check (split standard: see the deep-verify step description — R017 spawn + change-type adversarial review, one single-goal delegation each)
217
254
  (instead of full deep-verify skill spawn). If the change set has NO structural surface
218
255
  (no agent/skill/guide/rule frontmatter changed — e.g. a workflow-yaml-only or docs-only change),
219
256
  the mgr-sauron R017 spawn MAY be skipped and replaced by deterministic self-check
@@ -277,6 +314,13 @@ steps:
277
314
  1. A scoped issue carries (in its body or a linked artifact) a CONVERGED research/plan output equivalent to the step's skill — e.g. a `/research` multi-team analysis that explicitly converged, a `/deep-plan` artifact, or a prior `professor-triage`/`release-plan` output.
278
315
  2. The artifact covers the scoped issues' domain (not stale or unrelated).
279
316
  3. The substitution is for a PLANNING/VERIFICATION step ONLY — never for implement, verify-build, release, or ci-check.
317
+ 4. When the artifact is a SAME-SESSION triage/plan output substituting for `deep-plan` (or `plan`),
318
+ every implement-stage delegation prompt MUST instruct the agent to re-locate targets by ANCHOR
319
+ (function name / unique string) and treat any line numbers in the issue body or artifact as
320
+ reference only. Line numbers go stale across release commits — v1.1.60: #1647 cited lines
321
+ 346-348 from v1.1.59, but the script had grown 608→733 lines and two agents had to re-search
322
+ (#1652 #3-1/#3-2). The justification log MUST name the artifact AND state that anchor-based
323
+ re-measurement was included in the delegation prompts.
280
324
 
281
325
  If substituted, emit a MANDATORY justification log naming the artifact:
282
326
  "[compression-mode] step '{step}' satisfied by converged artifact {ref} (substitution); skill spawn skipped"
@@ -397,7 +441,7 @@ steps:
397
441
 
398
442
  - name: deep-verify
399
443
  skill: deep-verify
400
- description: "Multi-angle release quality verification — self-review checklist if docs-only; mgr-sauron R017 + core self-check if lite. MULTI-PHASE: 스킬을 그대로 spawn하지 말고 R020 「위임 경계를 Phase 개수로 설계」에 따라 단일 목표 위임으로 분할해 순차 발주한다 (#1595 #4)."
444
+ description: "Multi-angle release quality verification — self-review checklist if docs-only; mgr-sauron R017 + core self-check if lite. MULTI-PHASE: 스킬을 그대로 spawn하지 말고 R020 「위임 경계를 Phase 개수로 설계」에 따라 단일 목표 위임으로 분할해 순차 발주한다 (#1595 #4). lite 분할 표준(#1652 #3-4): (1) mgr-sauron R017 구조 검증 단일 목표 위임 1건 + (2) 변경 성격별 적대적 리뷰 단일 목표 위임 1건 — 스크립트 변경이면 실행 재현 기반 adversarial-review, 룰/스킬/yaml 텍스트 변경이면 문구 정합·배선 리뷰. 근거: v1.1.59/60 두 반복 연속 적대적 리뷰가 신규 회귀(M-3/M-4, heredoc 위조)를 실행 재현으로 포착."
401
445
  depends_on: verify-build
402
446
 
403
447
  - name: release
@@ -79,6 +79,14 @@ gh issue create \
79
79
  --body "## 출처\n\nv{version} 릴리즈 워크플로우에서 자동 등록.\n\n## 컨텍스트\n\n{상세 컨텍스트}\n\n## 권장 조치\n\n{권장 사항}" \
80
80
  --label "professor"
81
81
  ```
82
+
83
+ **이슈 본문의 코드 위치 표기 — 행 번호 대신 앵커 (#1652 #3-2)**: `## 컨텍스트`에서 코드 위치는 행
84
+ 번호가 아니라 **함수명·고유 앵커 문자열**(예: 함수명, 해당 위치의 고유 주석 문구)로 적는다. 행
85
+ 번호는 다음 릴리즈 커밋에서 stale해진다 — v1.1.60 세션에서 #1647 본문의 행 346-348은 v1.1.59 시점
86
+ 값이었고, 스크립트가 608→733행으로 성장해 구현 에이전트 2개가 재탐색해야 했다. 행 번호를
87
+ 병기하려면 기준 커밋 SHA를 함께 적고 "참고용"임을 명시한다. 후속 위임서도 "행 번호는 참고, 앵커로
88
+ 재탐색"을 전제로 작성된다(auto-dev.yaml substitution 조항 cross-ref).
89
+
82
90
  Add priority label (`P3` default for defects surfaced here; escalate to `P2` if MEDIUM+ severity).
83
91
 
84
92
  **Authority**: user directive (session 102) — genuine defects found during release workflows should be registered without prompting. See also R016 Defect Response Matrix: CI/infra defect and Process gap both require Issue registration.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.59",
2
+ "version": "1.1.61",
3
3
  "lastUpdated": "2026-09-03",
4
4
  "omcustomMinClaudeCode": "2.1.121",
5
5
  "omcustomMinClaudeCodeReason": "Sensitive-path direct Write/Edit on .claude/** under bypassPermissions (R010 deprecation, #1101)",
@@ -78,6 +78,31 @@ steps:
78
78
  4. Do NOT read, echo, or modify any credential material while checking settings — read the
79
79
  single `permissions.defaultMode` field only (R001).
80
80
 
81
+ Phase 0.6 — Detect unattended mode deterministically (#1650 C).
82
+
83
+ Why: scope-selection Step 3 defers `.claude/hooks/**` issues in unattended runs. Until v1.1.60 the
84
+ signal was prose ("entered via /fsd or the session says autonomous"), which is not measurable.
85
+ `/fsd` now writes a marker at entry (fsd SKILL.md 「무인 모드 마커」) and removes it at convergence.
86
+
87
+ 1. Measure (both signals; either one is sufficient):
88
+ marker="/tmp/.claude-fsd-$PPID"; unattended_mode=false; marker_state=absent
89
+ if [ -f "$marker" ]; then
90
+ if [ -n "$(command find "$marker" -mmin +360 2>/dev/null)" ]; then marker_state=stale; else marker_state=present; fi
91
+ fi
92
+ if [ "$marker_state" = "present" ] || [ "${OMCUSTOM_UNATTENDED:-}" = "1" ]; then unattended_mode=true; fi
93
+ echo "unattended_mode=$unattended_mode marker=$marker_state env=${OMCUSTOM_UNATTENDED:-unset}"
94
+ `$PPID` inside the Bash tool is the Claude Code process and is stable across calls, so the
95
+ marker written by `/fsd` in an earlier call is visible here.
96
+ A marker older than 360 minutes is treated as absent (stale guard — crash / PID reuse); `/fsd`
97
+ re-writes the marker at every iteration start. The snippet always exits 0 so a false result is
98
+ not mistaken for a measurement failure.
99
+
100
+ 2. Report: "[pre-triage] unattended_mode=<true|false> (marker /tmp/.claude-fsd-$PPID: <present|stale|absent>; OMCUSTOM_UNATTENDED=<value|unset>)"
101
+
102
+ 3. Output `unattended_mode` as pipeline state for scope-selection Step 3. Do NOT infer unattended
103
+ mode from prose or from the fact that this pipeline was invoked by a skill — only the two
104
+ measured signals above count.
105
+
81
106
  Phase 1 — Ensure required labels exist, then scan issues.
82
107
 
83
108
  1. Create labels if missing (idempotent):
@@ -164,6 +189,14 @@ steps:
164
189
  If approval was already granted THIS session for the same category+target, do not
165
190
  re-request (R015 directive persistence).
166
191
 
192
+ 무인 실행(`/fsd` 등 사용자가 실시간 응답할 수 없는 루프)에서는 `.claude/hooks/**` 대상 이슈를
193
+ 이번 스코프에서 **분리·이월**하고 나머지 적격 이슈로 진행하며, 반복 경계(homework 게이트)에서
194
+ 승인 질문을 **1회** 묶어 제시한다. 승인을 받으면 R015 directive persistence로 세션 내 동일
195
+ 카테고리에 지속 적용. 유인 모드에서는 기존대로 즉시 승인 요청. 무인 판별은 pre-triage Phase 0.6이 실측한
196
+ `unattended_mode` 상태를 사용한다(마커
197
+ `/tmp/.claude-fsd-$PPID` 또는 env `OMCUSTOM_UNATTENDED=1`; #1650 C). 산문 추론("/fsd로 진입한 것
198
+ 같다")으로 무인 여부를 판정하지 않는다.
199
+
167
200
  Output approval_required_paths (list, empty if none) as pipeline state.
168
201
 
169
202
  Assign scoped issues to milestone.
@@ -194,6 +227,10 @@ steps:
194
227
  - plan step: skip release-plan skill; single-response plan instead
195
228
  - deep-plan step: skip deep-plan skill; single-response implementation notes instead
196
229
  - deep-verify step: skip deep-verify skill; perform self-review checklist instead
230
+ — 단, 변경 집합에 `.claude/rules/**`(또는 agents/skills frontmatter 등 구조 표면)가
231
+ 포함되면 self-review로 대체하지 않고 mgr-sauron R017 검증을 **단일 목표 위임으로 필수
232
+ 실행**한다(Tier 2 lite의 R017 조항과 동일 원칙; Origin: v1.1.58 세션 — docs-only 티어였으나
233
+ sauron이 R002/R010 모순 advisory 1건을 포착).
197
234
  - Log: "[compression-mode] docs-only compression activated (scope={n}, all docs/yaml labels)"
198
235
  - ⚠ Analysis artifacts only. Every step's state-change side effect (milestone create/assign,
199
236
  label add/remove, issue assign/comment/close) still runs in full — see "Cross-tier —
@@ -213,7 +250,7 @@ steps:
213
250
  - triage step: MAY replace professor-triage skill spawn with orchestrator integrated analysis
214
251
  - plan step: MAY replace release-plan skill spawn with orchestrator integrated analysis
215
252
  - deep-plan step: MAY replace deep-plan skill spawn with orchestrator integrated analysis
216
- - deep-verify step: perform via mgr-sauron R017 structural verification + core self-check
253
+ - deep-verify step: perform via mgr-sauron R017 structural verification + core self-check (split standard: see the deep-verify step description — R017 spawn + change-type adversarial review, one single-goal delegation each)
217
254
  (instead of full deep-verify skill spawn). If the change set has NO structural surface
218
255
  (no agent/skill/guide/rule frontmatter changed — e.g. a workflow-yaml-only or docs-only change),
219
256
  the mgr-sauron R017 spawn MAY be skipped and replaced by deterministic self-check
@@ -277,6 +314,13 @@ steps:
277
314
  1. A scoped issue carries (in its body or a linked artifact) a CONVERGED research/plan output equivalent to the step's skill — e.g. a `/research` multi-team analysis that explicitly converged, a `/deep-plan` artifact, or a prior `professor-triage`/`release-plan` output.
278
315
  2. The artifact covers the scoped issues' domain (not stale or unrelated).
279
316
  3. The substitution is for a PLANNING/VERIFICATION step ONLY — never for implement, verify-build, release, or ci-check.
317
+ 4. When the artifact is a SAME-SESSION triage/plan output substituting for `deep-plan` (or `plan`),
318
+ every implement-stage delegation prompt MUST instruct the agent to re-locate targets by ANCHOR
319
+ (function name / unique string) and treat any line numbers in the issue body or artifact as
320
+ reference only. Line numbers go stale across release commits — v1.1.60: #1647 cited lines
321
+ 346-348 from v1.1.59, but the script had grown 608→733 lines and two agents had to re-search
322
+ (#1652 #3-1/#3-2). The justification log MUST name the artifact AND state that anchor-based
323
+ re-measurement was included in the delegation prompts.
280
324
 
281
325
  If substituted, emit a MANDATORY justification log naming the artifact:
282
326
  "[compression-mode] step '{step}' satisfied by converged artifact {ref} (substitution); skill spawn skipped"
@@ -397,7 +441,7 @@ steps:
397
441
 
398
442
  - name: deep-verify
399
443
  skill: deep-verify
400
- description: "Multi-angle release quality verification — self-review checklist if docs-only; mgr-sauron R017 + core self-check if lite. MULTI-PHASE: 스킬을 그대로 spawn하지 말고 R020 「위임 경계를 Phase 개수로 설계」에 따라 단일 목표 위임으로 분할해 순차 발주한다 (#1595 #4)."
444
+ description: "Multi-angle release quality verification — self-review checklist if docs-only; mgr-sauron R017 + core self-check if lite. MULTI-PHASE: 스킬을 그대로 spawn하지 말고 R020 「위임 경계를 Phase 개수로 설계」에 따라 단일 목표 위임으로 분할해 순차 발주한다 (#1595 #4). lite 분할 표준(#1652 #3-4): (1) mgr-sauron R017 구조 검증 단일 목표 위임 1건 + (2) 변경 성격별 적대적 리뷰 단일 목표 위임 1건 — 스크립트 변경이면 실행 재현 기반 adversarial-review, 룰/스킬/yaml 텍스트 변경이면 문구 정합·배선 리뷰. 근거: v1.1.59/60 두 반복 연속 적대적 리뷰가 신규 회귀(M-3/M-4, heredoc 위조)를 실행 재현으로 포착."
401
445
  depends_on: verify-build
402
446
 
403
447
  - name: release