oh-my-customcode 1.1.48 → 1.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/cli/index.js +171 -294
- package/dist/index.js +65 -65
- package/package.json +2 -2
- package/templates/.claude/agents/agora-runner.md +114 -0
- package/templates/.claude/hooks/hooks.json +10 -0
- package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +6 -1
- package/templates/.claude/hooks/scripts/claude-md-reinject.sh +65 -0
- package/templates/.claude/hooks/scripts/r007-r008-drift-advisor.sh +41 -3
- package/templates/.claude/hooks/scripts/session-env-check.sh +29 -3
- package/templates/.claude/rules/MAY-optimization.md +7 -1
- package/templates/.claude/rules/MUST-agent-design.md +15 -4
- package/templates/.claude/rules/MUST-agent-teams.md +19 -0
- package/templates/.claude/rules/MUST-completion-verification.md +53 -0
- package/templates/.claude/rules/MUST-continuous-improvement.md +20 -4
- package/templates/.claude/rules/MUST-enforcement-policy.md +7 -3
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +45 -0
- package/templates/.claude/rules/MUST-parallel-execution.md +61 -8
- package/templates/.claude/rules/MUST-permissions.md +8 -0
- package/templates/.claude/rules/MUST-safety.md +6 -0
- package/templates/.claude/rules/MUST-sync-verification.md +21 -0
- package/templates/.claude/rules/MUST-tool-identification.md +22 -4
- package/templates/.claude/rules/SHOULD-ecomode.md +4 -0
- package/templates/.claude/rules/SHOULD-error-handling.md +2 -0
- package/templates/.claude/rules/SHOULD-interaction.md +4 -0
- package/templates/.claude/rules/SHOULD-memory-integration.md +2 -0
- package/templates/.claude/rules/SHOULD-verification-ladder.md +2 -0
- package/templates/.claude/skills/agora/SKILL.md +325 -0
- package/templates/.claude/skills/agora/scripts/agora.sh +761 -0
- package/templates/.claude/skills/agora/scripts/anonymize.sh +492 -0
- package/templates/.claude/skills/agora/scripts/judge.sh +427 -0
- package/templates/.claude/skills/agora/scripts/response-schema.json +26 -0
- package/templates/.claude/skills/agora/scripts/reviewers.sh +312 -0
- package/templates/.claude/skills/agora/scripts/verdict-schema.json +34 -0
- package/templates/.claude/skills/hada-scout/SKILL.md +1 -1
- package/templates/.claude/skills/help/SKILL.md +1 -1
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +21 -3
- package/templates/.claude/skills/sauron-watch/SKILL.md +1 -1
- package/templates/.claude/skills/status/SKILL.md +3 -3
- package/templates/.claude/skills/token-efficiency-audit/SKILL.md +1 -1
- package/templates/CLAUDE.md +3 -3
- package/templates/CLAUDE.md.en +3 -3
- package/templates/CLAUDE.md.ko +3 -3
- package/templates/README.md +5 -5
- package/templates/guides/agent-eval/README.md +1 -1
- package/templates/manifest.json +3 -3
- package/templates/workflows/auto-dev.yaml +21 -3
package/dist/index.js
CHANGED
|
@@ -19,28 +19,28 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
// src/utils/fs.ts
|
|
20
20
|
var exports_fs = {};
|
|
21
21
|
__export(exports_fs, {
|
|
22
|
-
|
|
23
|
-
writeJsonFile: () => writeJsonFile,
|
|
24
|
-
validatePreserveFilePath: () => validatePreserveFilePath,
|
|
25
|
-
resolveTemplatePath: () => resolveTemplatePath,
|
|
26
|
-
resolvePath: () => resolvePath,
|
|
27
|
-
remove: () => remove,
|
|
28
|
-
readTextFile: () => readTextFile,
|
|
29
|
-
readJsonFile: () => readJsonFile,
|
|
30
|
-
normalizePath: () => normalizePath,
|
|
31
|
-
move: () => move,
|
|
32
|
-
listFiles: () => listFiles,
|
|
33
|
-
isAbsolutePath: () => isAbsolutePath,
|
|
34
|
-
getRelativePath: () => getRelativePath,
|
|
35
|
-
getPackageRoot: () => getPackageRoot,
|
|
36
|
-
getFileStats: () => getFileStats,
|
|
37
|
-
filesAreIdentical: () => filesAreIdentical,
|
|
38
|
-
fileExists: () => fileExists,
|
|
39
|
-
ensureDirectory: () => ensureDirectory,
|
|
40
|
-
createTempDir: () => createTempDir,
|
|
41
|
-
copyFile: () => copyFile,
|
|
22
|
+
calculateChecksum: () => calculateChecksum,
|
|
42
23
|
copyDirectory: () => copyDirectory,
|
|
43
|
-
|
|
24
|
+
copyFile: () => copyFile,
|
|
25
|
+
createTempDir: () => createTempDir,
|
|
26
|
+
ensureDirectory: () => ensureDirectory,
|
|
27
|
+
fileExists: () => fileExists,
|
|
28
|
+
filesAreIdentical: () => filesAreIdentical,
|
|
29
|
+
getFileStats: () => getFileStats,
|
|
30
|
+
getPackageRoot: () => getPackageRoot,
|
|
31
|
+
getRelativePath: () => getRelativePath,
|
|
32
|
+
isAbsolutePath: () => isAbsolutePath,
|
|
33
|
+
listFiles: () => listFiles,
|
|
34
|
+
move: () => move,
|
|
35
|
+
normalizePath: () => normalizePath,
|
|
36
|
+
readJsonFile: () => readJsonFile,
|
|
37
|
+
readTextFile: () => readTextFile,
|
|
38
|
+
remove: () => remove,
|
|
39
|
+
resolvePath: () => resolvePath,
|
|
40
|
+
resolveTemplatePath: () => resolveTemplatePath,
|
|
41
|
+
validatePreserveFilePath: () => validatePreserveFilePath,
|
|
42
|
+
writeJsonFile: () => writeJsonFile,
|
|
43
|
+
writeTextFile: () => writeTextFile
|
|
44
44
|
});
|
|
45
45
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
46
46
|
import { fileURLToPath } from "node:url";
|
|
@@ -294,14 +294,14 @@ var init_fs = () => {};
|
|
|
294
294
|
// src/core/registry.ts
|
|
295
295
|
var exports_registry = {};
|
|
296
296
|
__export(exports_registry, {
|
|
297
|
-
|
|
298
|
-
registryToList: () => registryToList,
|
|
299
|
-
registerProject: () => registerProject,
|
|
300
|
-
readRegistry: () => readRegistry,
|
|
301
|
-
migrateFromLockfiles: () => migrateFromLockfiles,
|
|
302
|
-
isTempPath: () => isTempPath,
|
|
297
|
+
_setRegistryDirForTesting: () => _setRegistryDirForTesting,
|
|
303
298
|
cleanRegistry: () => cleanRegistry,
|
|
304
|
-
|
|
299
|
+
isTempPath: () => isTempPath,
|
|
300
|
+
migrateFromLockfiles: () => migrateFromLockfiles,
|
|
301
|
+
readRegistry: () => readRegistry,
|
|
302
|
+
registerProject: () => registerProject,
|
|
303
|
+
registryToList: () => registryToList,
|
|
304
|
+
unregisterProject: () => unregisterProject
|
|
305
305
|
});
|
|
306
306
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
307
307
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -2031,7 +2031,7 @@ var package_default = {
|
|
|
2031
2031
|
workspaces: [
|
|
2032
2032
|
"packages/*"
|
|
2033
2033
|
],
|
|
2034
|
-
version: "1.1.
|
|
2034
|
+
version: "1.1.50",
|
|
2035
2035
|
description: "Batteries-included agent harness for Claude Code",
|
|
2036
2036
|
type: "module",
|
|
2037
2037
|
bin: {
|
|
@@ -2079,7 +2079,7 @@ var package_default = {
|
|
|
2079
2079
|
yaml: "^2.8.2"
|
|
2080
2080
|
},
|
|
2081
2081
|
devDependencies: {
|
|
2082
|
-
"@anthropic-ai/sdk": "^0.
|
|
2082
|
+
"@anthropic-ai/sdk": "^0.117.1",
|
|
2083
2083
|
"@biomejs/biome": "^2.3.12",
|
|
2084
2084
|
"@types/bun": "^1.3.6",
|
|
2085
2085
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -5367,40 +5367,40 @@ var src_default = {
|
|
|
5367
5367
|
VERSION
|
|
5368
5368
|
};
|
|
5369
5369
|
export {
|
|
5370
|
-
|
|
5371
|
-
warn,
|
|
5372
|
-
update,
|
|
5373
|
-
success,
|
|
5374
|
-
setLogLevel,
|
|
5375
|
-
setLocale,
|
|
5376
|
-
saveConfig,
|
|
5377
|
-
resolveTemplatePath,
|
|
5378
|
-
renderGitWorkflowKO,
|
|
5379
|
-
renderGitWorkflowEN,
|
|
5380
|
-
readJsonFile,
|
|
5381
|
-
preserveCustomizations,
|
|
5382
|
-
mergeConfig,
|
|
5383
|
-
loadConfig,
|
|
5384
|
-
install,
|
|
5385
|
-
info,
|
|
5386
|
-
getTemplateManifest,
|
|
5387
|
-
getProviderLayout,
|
|
5388
|
-
getPackageRoot,
|
|
5389
|
-
getDefaultWorkflow,
|
|
5390
|
-
getDefaultConfig,
|
|
5391
|
-
getConfigPath,
|
|
5392
|
-
fileExists,
|
|
5393
|
-
error,
|
|
5394
|
-
ensureDirectory,
|
|
5395
|
-
detectProvider,
|
|
5396
|
-
detectGitWorkflow,
|
|
5397
|
-
src_default as default,
|
|
5398
|
-
debug,
|
|
5399
|
-
createLogger,
|
|
5400
|
-
createDirectoryStructure,
|
|
5401
|
-
copyTemplates,
|
|
5402
|
-
copyDirectory,
|
|
5403
|
-
checkForUpdates,
|
|
5370
|
+
VERSION,
|
|
5404
5371
|
applyUpdates,
|
|
5405
|
-
|
|
5372
|
+
checkForUpdates,
|
|
5373
|
+
copyDirectory,
|
|
5374
|
+
copyTemplates,
|
|
5375
|
+
createDirectoryStructure,
|
|
5376
|
+
createLogger,
|
|
5377
|
+
debug,
|
|
5378
|
+
src_default as default,
|
|
5379
|
+
detectGitWorkflow,
|
|
5380
|
+
detectProvider,
|
|
5381
|
+
ensureDirectory,
|
|
5382
|
+
error,
|
|
5383
|
+
fileExists,
|
|
5384
|
+
getConfigPath,
|
|
5385
|
+
getDefaultConfig,
|
|
5386
|
+
getDefaultWorkflow,
|
|
5387
|
+
getPackageRoot,
|
|
5388
|
+
getProviderLayout,
|
|
5389
|
+
getTemplateManifest,
|
|
5390
|
+
info,
|
|
5391
|
+
install,
|
|
5392
|
+
loadConfig,
|
|
5393
|
+
mergeConfig,
|
|
5394
|
+
preserveCustomizations,
|
|
5395
|
+
readJsonFile,
|
|
5396
|
+
renderGitWorkflowEN,
|
|
5397
|
+
renderGitWorkflowKO,
|
|
5398
|
+
resolveTemplatePath,
|
|
5399
|
+
saveConfig,
|
|
5400
|
+
setLocale,
|
|
5401
|
+
setLogLevel,
|
|
5402
|
+
success,
|
|
5403
|
+
update,
|
|
5404
|
+
warn,
|
|
5405
|
+
writeJsonFile
|
|
5406
5406
|
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"workspaces": [
|
|
4
4
|
"packages/*"
|
|
5
5
|
],
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.50",
|
|
7
7
|
"description": "Batteries-included agent harness for Claude Code",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"yaml": "^2.8.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@anthropic-ai/sdk": "^0.
|
|
54
|
+
"@anthropic-ai/sdk": "^0.117.1",
|
|
55
55
|
"@biomejs/biome": "^2.3.12",
|
|
56
56
|
"@types/bun": "^1.3.6",
|
|
57
57
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agora-runner
|
|
3
|
+
description: Executes exactly one agora session step (start, one round, or finish) via the skill scripts and returns only a verdict summary. Never returns reviewer text, vendor attribution or sealed paths.
|
|
4
|
+
model: claude-sonnet-5
|
|
5
|
+
tools: [Bash, Read, Write, Glob]
|
|
6
|
+
skills: [agora]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 역할
|
|
10
|
+
|
|
11
|
+
`agora` 스킬에서 **파일을 쓰는 진입점 전부**를 전담합니다 — `--start`, `--round`, `--set-stop`, `--report` 넷입니다. 오케스트레이터는 파일을 쓰지 않는 `--gate`만 직접 실행합니다(R010).
|
|
12
|
+
|
|
13
|
+
**위임 1건 = 아래 단계 하나**이며, 여러 단계를 한 번에 처리하지 않습니다 (R020 「위임 경계를 Phase 개수로 설계」 — 다중 Phase 위임은 경계에서 mid-step 종료가 반복 재발했습니다).
|
|
14
|
+
|
|
15
|
+
| 위임 유형 | 실행 명령 | 단일 목표 |
|
|
16
|
+
|-----------|-----------|-----------|
|
|
17
|
+
| 세션 시작 | `--start` (게이트드 모드에서 **라운드 1까지** 수행) | 세션 생성 + 라운드 1 |
|
|
18
|
+
| 라운드 진행 | `--round <N>` | 라운드 하나 |
|
|
19
|
+
| 세션 마감 | `--set-stop <CODE>` → `--report` | 종료 사유 기록 + 보고서 생성 |
|
|
20
|
+
|
|
21
|
+
## 실행 절차
|
|
22
|
+
|
|
23
|
+
### 세션 시작 (`--start`)
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
bash .claude/skills/agora/scripts/agora.sh --start "<topic>" [--attach <path>]... [--max-rounds <N>]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`--auto`는 오케스트레이터가 명시적으로 지시한 경우에만 붙입니다 — 매 라운드 게이트를 생략하고 상한까지 전부 돌기 때문입니다(최악 약 2시간, 토큰 약 600k).
|
|
30
|
+
|
|
31
|
+
**stdout에는 세션 디렉토리 절대경로 한 줄만** 나옵니다. `dir=$(bash agora.sh --start ...)` 관용구로 캡처하십시오. 게이트 블록은 **stderr로 나가므로** 반환 계약에 담을 수 없습니다 — 오케스트레이터가 `--gate --session-dir <dir> --round 1`로 다시 렌더할 것이므로, 러너는 stderr 게이트 블록을 반환에 옮겨 적지 않습니다.
|
|
32
|
+
|
|
33
|
+
반환 전에 `bash agora.sh --decide-stop < <dir>/state.json`으로 정지 코드를 산출해 `stop_code`로 함께 돌려줍니다. **라운드 1에서 정지 조건이 걸린 경우 `--start`가 `.stop` 기록과 `report.md` 생성까지 스스로 수행하고 게이트를 렌더하지 않으므로**, 이때는 `--set-stop`도 `--report`도 다시 실행하지 마십시오. `state.json`의 `.stop`이 이미 채워졌는지로 판별합니다.
|
|
34
|
+
|
|
35
|
+
### 라운드 진행 (`--round`)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bash .claude/skills/agora/scripts/agora.sh --round <N> --session-dir <dir> [--extra-agenda <json-array>]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
오케스트레이터로부터 `e` 게이트 응답이 전달된 경우에만 `--extra-agenda`를 붙이며, **JSON 배열이 아니면 exit 64로 거부됩니다**(예: `'["롤백 명령 시퀀스를 제시할 것"]'`).
|
|
42
|
+
|
|
43
|
+
`--round`는 `.stop`도 `max_rounds`도 검사하지 않고 `--decide-stop`을 호출하지도 않으므로, 정지 판단은 직접 수행합니다:
|
|
44
|
+
|
|
45
|
+
1. `bash agora.sh --round <N> --session-dir <dir>` 로 한 라운드를 진행합니다.
|
|
46
|
+
2. `bash agora.sh --decide-stop < <dir>/state.json` 으로 정지 코드를 산출해 반환 계약의 `stop_code`로 돌려줍니다.
|
|
47
|
+
|
|
48
|
+
**여기서 멈추고 반환합니다.** `--set-stop`도 `--report`도 이 위임에서 실행하지 않습니다 — 정지 여부는 사용자 게이트 응답을 받은 오케스트레이터가 결정하며, 마감은 별도 위임입니다.
|
|
49
|
+
|
|
50
|
+
`--gate`는 실행하지 않습니다 — 반환 계약에 게이트 블록을 담을 필드가 없어 출력이 그대로 버려지므로, 파일을 쓰지 않는 순수 출력 명령인 `--gate`는 오케스트레이터가 직접 실행합니다.
|
|
51
|
+
|
|
52
|
+
### 세션 마감 (`--set-stop` → `--report`)
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
bash .claude/skills/agora/scripts/agora.sh --set-stop <CODE> --session-dir <dir>
|
|
56
|
+
bash .claude/skills/agora/scripts/agora.sh --report --session-dir <dir>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`--set-stop`을 반드시 `--report`보다 먼저, 그리고 건너뛰지 말고 실행합니다.** 게이트드 모드에서 라운드 2 이후 `.stop`에는 다른 writer가 없으므로, 생략하면 세션이 아무리 깨끗하게 끝나도 `report.md`가 `종료 사유: UNKNOWN`을 출력합니다.
|
|
60
|
+
|
|
61
|
+
`<CODE>`는 오케스트레이터가 지정한 값을 그대로 씁니다. 지정이 없으면 직전 `--decide-stop` 결과를 씁니다.
|
|
62
|
+
|
|
63
|
+
| `<CODE>` | 사용 시점 |
|
|
64
|
+
|----------|-----------|
|
|
65
|
+
| `CONSENSUS` · `STALLED` · `MAX_ROUNDS` | `--decide-stop`이 그 코드를 반환했을 때 |
|
|
66
|
+
| `USER` | 사용자가 게이트에서 `s`(중단하고 보고서)를 선택했을 때 |
|
|
67
|
+
| `CONTINUE` | **사용 불가** — exit 64로 거부됩니다. 정지하지 않았다는 뜻이므로 라운드를 더 도십시오 |
|
|
68
|
+
|
|
69
|
+
`--set-stop`이 비영으로 끝나면 **`--report`를 실행하지 말고** 그 사실을 보고하십시오 (아래 exit 73 항목).
|
|
70
|
+
|
|
71
|
+
## Exit code 대응
|
|
72
|
+
|
|
73
|
+
모든 명령의 종료 코드를 확인한 뒤 아래에 따라 대응합니다. `agora.sh`는 하위 스크립트의 코드를 그대로 전파하므로 `--round` 하나가 아래 전부를 반환할 수 있습니다. **어떤 코드에서도 라운드를 자체 판단으로 재실행하지 마십시오** — 벤더가 다시 과금됩니다.
|
|
74
|
+
|
|
75
|
+
| 코드 | 보고할 내용 | 러너의 행동 |
|
|
76
|
+
|------|-------------|-------------|
|
|
77
|
+
| `0` | 정상 | 반환 계약대로 반환 |
|
|
78
|
+
| `1` | **지문 검출로 라운드 중단** — 익명 번들에 벤더 지문이 섞여 익명성 보호 차원에서 중단됨. `SEALED/mapping/`과 `anon/`에는 아무것도 쓰이지 않음 | 즉시 보고 후 종료. 재실행 금지 |
|
|
79
|
+
| `3` | **유효 리뷰어 2인 미만으로 라운드 중단** — CLI 결측 단계 또는 응답 스키마 위반 단계에서 걸림 | 즉시 보고 후 종료 |
|
|
80
|
+
| `4` | **심판 로테이션 3슬롯 전부 실패로 세션 중단** — CLI 실패·타임아웃·파싱 실패·스키마 위반 포함. 로테이션 대체까지가 이미 시도의 전부 | 즉시 보고 후 종료 |
|
|
81
|
+
| `64` | **호출 오류** — 옵션·필수 플래그·`--extra-agenda` JSON 배열 여부·`--set-stop` 코드를 점검. 벤더는 호출되지 않았음 | 호출 형태가 명백히 틀렸으면 **한 번만** 교정 재시도, 아니면 보고 후 종료 |
|
|
82
|
+
| `65` | **직전 라운드 sealed 데이터 무결성 문제** — 파일은 있으나 파싱 불가. 지문 검출(`1`)과 다른 원인 | 즉시 보고 후 종료 |
|
|
83
|
+
| `66` | **세션 디렉토리(또는 익명 번들)를 찾을 수 없음** — 경로를 점검 | 경로 오류면 보고 후 종료 |
|
|
84
|
+
| `68` | **설정 오류** — `verdict-schema.json`을 읽거나 파싱할 수 없음. 심판의 실패가 아니라 배포 결함이라 재시도해도 동일하게 실패 | 즉시 보고 후 종료 |
|
|
85
|
+
| `73` | **라운드는 돌았으나 기록되지 않음** — 리뷰어 3벤더와 심판이 이미 호출·과금된 뒤 `state.json`/`report.md` 쓰기가 실패. 가장 비싼 실패 | **산출물을 소비하지 말고, 라운드를 재실행하지도 말 것.** stderr 진단 줄을 그대로 보고하고 종료 |
|
|
86
|
+
|
|
87
|
+
`1`·`65`로 중단된 원인을 파악한다며 `SEALED/` 하위 파일을 읽지 마십시오 — 진단 목적이라도 금지 사항 위반입니다. stderr 진단 줄만 보고합니다.
|
|
88
|
+
|
|
89
|
+
세션 디렉토리 경로는 절대 경로로 출력되므로 다른 cwd에서 실행해도 세션을 찾을 수 있습니다.
|
|
90
|
+
|
|
91
|
+
## 반환 계약
|
|
92
|
+
|
|
93
|
+
아래 필드만 반환합니다. 라운드를 실행하지 않은 위임(세션 마감)에서는 해당 없는 필드를 생략합니다.
|
|
94
|
+
|
|
95
|
+
| 필드 | 출처 |
|
|
96
|
+
|------|------|
|
|
97
|
+
| `session_dir` | `--start` 의 stdout (세션 시작 위임에서만) |
|
|
98
|
+
| `round` | `verdict/round-N.json` `.round` |
|
|
99
|
+
| `consensus` | `.consensus` |
|
|
100
|
+
| `verdict` | `.verdict` |
|
|
101
|
+
| `resolved` | `.resolved` (id + resolution) |
|
|
102
|
+
| `unresolved` | `.unresolved` (id + severity + positions) |
|
|
103
|
+
| `agenda` | `.agenda` |
|
|
104
|
+
| `new_findings` | `.new_findings` |
|
|
105
|
+
| `stop_code` | `bash agora.sh --decide-stop < state.json` 결과 |
|
|
106
|
+
| `exit_code` | 실행한 명령의 종료 코드 (0이 아니면 위 표의 대응과 함께) |
|
|
107
|
+
| `stop_recorded` | 세션 마감 위임에서 `--set-stop` 성공 여부 |
|
|
108
|
+
| `report_path` | 세션 마감 위임에서 생성된 `report.md` 경로 |
|
|
109
|
+
|
|
110
|
+
반환 페이로드는 verdict 요약만 담습니다.
|
|
111
|
+
|
|
112
|
+
## 금지 사항
|
|
113
|
+
|
|
114
|
+
`SEALED/` 하위 파일을 읽지 않으며, 리뷰어 원문·벤더 식별자·`SEALED/` 경로 문자열을 반환에 포함하지 않습니다. `anon/round-N.json`의 `A`/`B`/`C` 라벨은 라운드마다 다시 섞이므로 **어떤 벤더로도 해석하거나 라운드 간 비교하지 마십시오**. 사용자에게 직접 질문하거나 승인을 구하지 않으며, 게이트 표시는 오케스트레이터가 전담합니다.
|
|
@@ -195,6 +195,16 @@
|
|
|
195
195
|
}
|
|
196
196
|
],
|
|
197
197
|
"description": "Advisory check for plugin cache directories missing node_modules (#1207)"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"matcher": "*",
|
|
201
|
+
"hooks": [
|
|
202
|
+
{
|
|
203
|
+
"type": "command",
|
|
204
|
+
"command": "bash .claude/hooks/scripts/claude-md-reinject.sh"
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"description": "Re-inject project CLAUDE.md into model context on session start/resume/clear and compact re-entry — matcher \"*\" covers all SessionStart sources including \"compact\" (#1617)"
|
|
198
208
|
}
|
|
199
209
|
],
|
|
200
210
|
"UserPromptSubmit": [
|
|
@@ -11,7 +11,12 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
11
11
|
|
|
12
12
|
input=$(cat)
|
|
13
13
|
|
|
14
|
-
# Skip
|
|
14
|
+
# Skip unless Agent Teams is CONFIRMED active.
|
|
15
|
+
# session-env-check.sh writes one of: disabled | env-set | enabled (#1588).
|
|
16
|
+
# `env-set` = the env var is set but TeamCreate presence is UNVERIFIED — R018 is dormant,
|
|
17
|
+
# so recommending TeamCreate here would be unactionable advice.
|
|
18
|
+
# The `!= "enabled"` test below therefore deliberately skips on `env-set`; only the measured
|
|
19
|
+
# OMCUSTOM_AGENT_TEAMS_VERIFIED=1 path re-enables this advisor.
|
|
15
20
|
ENV_STATUS="/tmp/.claude-env-status-${PPID}"
|
|
16
21
|
if [ -f "$ENV_STATUS" ]; then
|
|
17
22
|
teams_status=$(grep "agent_teams=" "$ENV_STATUS" 2>/dev/null | cut -d= -f2 || echo "unknown")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# claude-md-reinject.sh — SessionStart: re-inject project CLAUDE.md into model context (#1617)
|
|
3
|
+
#
|
|
4
|
+
# 배경:
|
|
5
|
+
# 장기 세션에서 compact 이후 프로젝트 CLAUDE.md(강제 규칙 원문)가 모델 컨텍스트에서 유실되면
|
|
6
|
+
# 규칙 amnesia가 재발한다. 기존 PostCompact 훅(hooks.json)은 "prompt" 타입으로 요약 지침만
|
|
7
|
+
# 재주입할 뿐, CLAUDE.md 원문 전체를 재주입하지 않는다.
|
|
8
|
+
#
|
|
9
|
+
# 배선 이벤트 (Phase 1 판단, #1617):
|
|
10
|
+
# R006 실측 기준(이 저장소 rules/MUST-agent-design.md) additionalContext 지원 이벤트 목록에
|
|
11
|
+
# SessionStart는 있고 PostCompact는 없다. 한편 공식 문서(hooks.md, 2026-08-29 재확인)의
|
|
12
|
+
# SessionStart matcher는 startup / resume / clear / compact 네 가지이며, "compact"는
|
|
13
|
+
# auto/manual compact 직후를 가리킨다 — 즉 "session start 전체와 compact 재개 이후 재주입"
|
|
14
|
+
# 요구사항은 SessionStart 단일 이벤트(matcher "*")로 전부 커버된다. 별도 PostCompact 배선은
|
|
15
|
+
# 불필요 — 공식 문서 이벤트 목록(### 헤더 스캔)에 PostCompact 자체가 없다(PreCompact만 존재).
|
|
16
|
+
#
|
|
17
|
+
# 동작:
|
|
18
|
+
# stdin JSON의 .source(startup/resume/clear/compact/기타)를 로그 헤더에 포함해
|
|
19
|
+
# hookSpecificOutput.additionalContext로 CLAUDE.md 전체를 재주입한다.
|
|
20
|
+
#
|
|
21
|
+
# Opt-out: OMCUSTOM_CLAUDEMD_REINJECT=off (기본 on — 이 훅은 R021 advisory-first이되 기본 활성).
|
|
22
|
+
# Graceful degradation (R021): CLAUDE.md 부재 / jq 부재 / 초과 크기 → 무음 exit 0. 절대 exit 2 금지.
|
|
23
|
+
|
|
24
|
+
input=$(cat 2>/dev/null || echo '{}')
|
|
25
|
+
|
|
26
|
+
if [ "${OMCUSTOM_CLAUDEMD_REINJECT:-on}" = "off" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
source_field=$(printf '%s' "$input" | jq -r '.source // "unknown"' 2>/dev/null) || source_field="unknown"
|
|
35
|
+
|
|
36
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
37
|
+
CLAUDE_MD="$PROJECT_ROOT/CLAUDE.md"
|
|
38
|
+
|
|
39
|
+
if [ ! -f "$CLAUDE_MD" ]; then
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# 크기 가드 — 훅 출력 폭주로 세션을 wedge시키는 실패 클래스 방지 (R021).
|
|
44
|
+
MAX_BYTES=${OMCUSTOM_CLAUDEMD_REINJECT_MAX_BYTES:-102400}
|
|
45
|
+
file_size=$(wc -c < "$CLAUDE_MD" 2>/dev/null | tr -d ' ') || file_size=0
|
|
46
|
+
if [ -z "$file_size" ] || [ "$file_size" -gt "$MAX_BYTES" ] 2>/dev/null; then
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
claude_md_content=$(cat "$CLAUDE_MD" 2>/dev/null) || exit 0
|
|
51
|
+
if [ -z "$claude_md_content" ]; then
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
header="[claude-md-reinject] CLAUDE.md re-injection (source: ${source_field})"
|
|
56
|
+
|
|
57
|
+
# 사람이 보는 감사 추적용 (SessionStart의 stdout 자체가 컨텍스트로 들어가므로, 사람용 로그는
|
|
58
|
+
# stderr로만 남긴다 — additionalContext 본문에는 섞지 않는다).
|
|
59
|
+
printf '%s\n' "$header" >&2
|
|
60
|
+
|
|
61
|
+
jq -cn --arg header "$header" --arg body "$claude_md_content" \
|
|
62
|
+
'{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: ($header + "\n\n" + $body)}}' \
|
|
63
|
+
2>/dev/null || exit 0
|
|
64
|
+
|
|
65
|
+
exit 0
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
# OMCUSTOM_R007_ADVISOR=off — advisory 완전 비활성화 (pass-through)
|
|
117
117
|
# OMCUSTOM_TRANSCRIPT_BASE — transcript 디렉토리 경로 override (설정 시 최우선)
|
|
118
118
|
# OMCUSTOM_R007_MARKER_DIR — dedup 마커 디렉토리 override (기본 ${TMPDIR:-/tmp})
|
|
119
|
+
# OMCUSTOM_R008_REVERSE=on — 역방향 신호(예고했으나 도구 미호출) 활성화. 기본 off.
|
|
119
120
|
|
|
120
121
|
set -euo pipefail
|
|
121
122
|
|
|
@@ -163,7 +164,14 @@ if [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
|
163
164
|
fi
|
|
164
165
|
|
|
165
166
|
# ── 마지막 assistant 턴 재구성 + R007/R008 판정 (jq 1회 fork) ──
|
|
166
|
-
# 출력: "<turn-uuid>\t<r007-count>\t<r008-count>" (턴이 없으면 무출력)
|
|
167
|
+
# 출력: "<turn-uuid>\t<r007-count>\t<r008-count>\t<r008-reverse-count>" (턴이 없으면 무출력)
|
|
168
|
+
#
|
|
169
|
+
# ── 역방향 신호는 이 advisor 전용 (session-reflection.sh에 복제하지 않는다) ────────────
|
|
170
|
+
# forward 판정식은 session-reflection.sh(Stop 훅)와 공유되지만, 역방향 신호는 의도적으로
|
|
171
|
+
# 여기에만 둔다. 근거: 역방향이 잡는 결함 턴은 tool_use가 0건이라 PostToolUse/SubagentStop이
|
|
172
|
+
# 애초에 발화하지 않고, Stop 훅은 세션이 끝난 뒤라 그 턴을 교정할 기회가 이미 없다. 두
|
|
173
|
+
# 파일에 복제하면 같은 결함을 두 번 보고하면서 교정 기회는 여전히 0인 상태가 된다.
|
|
174
|
+
# 기본 off(OMCUSTOM_R008_REVERSE)인 실험 신호를 복제하면 되돌릴 지점도 두 곳이 된다.
|
|
167
175
|
JQ_LAST_TURN='
|
|
168
176
|
split("\n")
|
|
169
177
|
| map(select(length > 0) | (fromjson? // empty))
|
|
@@ -194,7 +202,23 @@ split("\n")
|
|
|
194
202
|
| ($an_tool + (if $an_spawn_item > 0 then $an_spawn_item else $an_spawn_hdr end)) as $announce
|
|
195
203
|
| ([ $blocks[] | select((.type? == "tool_use") and ((.name? // "") != "Skill")) ] | length) as $ntools
|
|
196
204
|
| (if $ntools > $announce then $ntools - $announce else 0 end) as $r008
|
|
197
|
-
|
|
205
|
+
# REVERSE signal (#1595 제안 #6): announced a tool call, then ended the turn without
|
|
206
|
+
# emitting ANY tool_use block (R009 Self-Check #6). Uses its OWN ANCHORED counter — do NOT
|
|
207
|
+
# reuse $announce. MEASURED over 482 orchestrator turns:
|
|
208
|
+
# * naive `announce - ntools > 0` fires on 36/482 and DOUBLES the advisory rate; 16 are
|
|
209
|
+
# pure Skill-exclusion artifacts and 15 more are prose matching the UNANCHORED regexes.
|
|
210
|
+
# * narrowing to "no tool_use at all" + ANCHORED `→ Tool:` yields 3/3 true positives,
|
|
211
|
+
# 0 false positives; the anchor costs 1 of 969 real announce lines corpus-wide (0.1%).
|
|
212
|
+
# The anchor MUST NOT be applied to $an_tool: forward is max(0, ntools - announce), so
|
|
213
|
+
# under-counting announce INCREASES reported violations.
|
|
214
|
+
# Condition uses ALL tool_use blocks (Skill included) — using the Skill-excluded $ntools
|
|
215
|
+
# would fire on compliant turns that only invoked Skill. This deliberately differs from
|
|
216
|
+
# the $ntools denominator above; it is a separate variable, not a weakening of the Skill
|
|
217
|
+
# exemption (#1569), which still governs the forward verdict.
|
|
218
|
+
| ([ $lines[] | select(test("^\\[[^\\]]+\\]\\[[^\\]]+\\] ?(→|->|—>) ?Tool:")) ] | length) as $an_anchored
|
|
219
|
+
| ($blocks | map(select(.type? == "tool_use")) | length) as $nall_tools
|
|
220
|
+
| (if $nall_tools == 0 and $an_anchored > 0 then $an_anchored else 0 end) as $r008rev
|
|
221
|
+
| [$tuuid, ($r007 | tostring), ($r008 | tostring), ($r008rev | tostring)] | @tsv
|
|
198
222
|
end
|
|
199
223
|
'
|
|
200
224
|
|
|
@@ -207,12 +231,22 @@ fi
|
|
|
207
231
|
turn_uuid=$(printf '%s' "$result" | cut -f1)
|
|
208
232
|
r007_violations=$(printf '%s' "$result" | cut -f2)
|
|
209
233
|
r008_violations=$(printf '%s' "$result" | cut -f3)
|
|
234
|
+
r008_reverse=$(printf '%s' "$result" | cut -f4)
|
|
210
235
|
|
|
211
236
|
: "${r007_violations:=0}"
|
|
212
237
|
: "${r008_violations:=0}"
|
|
238
|
+
: "${r008_reverse:=0}"
|
|
239
|
+
|
|
240
|
+
# 역방향 신호는 OPT-IN, 기본 off.
|
|
241
|
+
# 측정 정밀도는 3/3이지만 표본이 3건뿐이고, 배선 구조상 "예방"이 불가능하다:
|
|
242
|
+
# 결함 턴은 tool_use가 0이라 PostToolUse/SubagentStop이 발화하지 않고, UserPromptSubmit은
|
|
243
|
+
# 사용자가 이미 개입한 뒤에 발화한다. 기본 on 전환 전에 Stop 배선을 별도로 실측해야 한다.
|
|
244
|
+
if [ "${OMCUSTOM_R008_REVERSE:-off}" != "on" ]; then
|
|
245
|
+
r008_reverse=0
|
|
246
|
+
fi
|
|
213
247
|
|
|
214
248
|
# ── 위반이 없으면 아무것도 출력하지 않는다 (오탐 방지) ──
|
|
215
|
-
if [ "$r007_violations" -eq 0 ] && [ "$r008_violations" -eq 0 ]; then
|
|
249
|
+
if [ "$r007_violations" -eq 0 ] && [ "$r008_violations" -eq 0 ] && [ "$r008_reverse" -eq 0 ]; then
|
|
216
250
|
exit 0
|
|
217
251
|
fi
|
|
218
252
|
|
|
@@ -232,6 +266,10 @@ fi
|
|
|
232
266
|
advisory_text=$(printf '[R007/R008 Advisory] 직전 응답에서 식별 누락 감지 (R007 헤더=%s, R008 접두사=%s). 이번 응답은 ┌─ Agent: 헤더로 시작하고, 모든 도구 호출에 [agent][model] → Tool: 접두사를 포함하십시오.' \
|
|
233
267
|
"$r007_violations" "$r008_violations")
|
|
234
268
|
|
|
269
|
+
if [ "$r008_reverse" -gt 0 ]; then
|
|
270
|
+
advisory_text="${advisory_text} [R009 Self-Check #6] 직전 턴은 도구 호출 ${r008_reverse}건을 예고하고도 tool_use 블록 없이 종료했습니다 — 예고한 호출을 지금 실행하거나, 실행하지 않기로 한 사유를 명시하십시오."
|
|
271
|
+
fi
|
|
272
|
+
|
|
235
273
|
# 사람이 보는 감사 추적용 (exit 0에서는 모델에 전달되지 않음 — #1547 참고)
|
|
236
274
|
printf '%s\n' "$advisory_text" >&2
|
|
237
275
|
|
|
@@ -3,7 +3,8 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
# Session Environment Check Hook
|
|
5
5
|
# Trigger: SessionStart
|
|
6
|
-
# Purpose: Check availability of codex CLI and Agent Teams
|
|
6
|
+
# Purpose: Check availability of codex CLI and the Agent Teams env-var INTENT (#1588),
|
|
7
|
+
# report via stderr
|
|
7
8
|
# Protocol: stdin JSON -> stdout pass-through, exit 0 always
|
|
8
9
|
|
|
9
10
|
input=$(cat)
|
|
@@ -37,10 +38,30 @@ if command -v rtk >/dev/null 2>&1; then
|
|
|
37
38
|
RTK_STATUS="available"
|
|
38
39
|
fi
|
|
39
40
|
|
|
40
|
-
# Check Agent Teams
|
|
41
|
+
# Check Agent Teams ENV-VAR INTENT — NOT activation (#1588)
|
|
42
|
+
#
|
|
43
|
+
# R018 Detection (.claude/rules/MUST-agent-teams.md) resolves Agent Teams as ACTIVE only when
|
|
44
|
+
# CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 AND TeamCreate is present in the tool list.
|
|
45
|
+
# A shell hook CANNOT observe the tool list. MEASURED against @anthropic-ai/claude-code 2.1.233:
|
|
46
|
+
# * SessionStart stdin carries only
|
|
47
|
+
# {session_id, transcript_path, cwd, permission_mode, agent_id, agent_type,
|
|
48
|
+
# hook_event_name, source, model} — NO hook event carries a tool inventory.
|
|
49
|
+
# * availability additionally requires a remote gate and a plan entitlement, neither of
|
|
50
|
+
# which is cached on disk, so the shell cannot read them either.
|
|
51
|
+
# The env var is NECESSARY-BUT-NOT-SUFFICIENT. Reporting "enabled" here made
|
|
52
|
+
# agent-teams-advisor.sh recommend TeamCreate in an environment where that tool does not exist.
|
|
53
|
+
#
|
|
54
|
+
# VALUE CONTRACT: agent-teams-advisor.sh reads this via `grep "agent_teams=" | cut -d= -f2`,
|
|
55
|
+
# so the value MUST NOT contain '='.
|
|
41
56
|
AGENT_TEAMS_STATUS="disabled"
|
|
42
57
|
if [ "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-0}" = "1" ]; then
|
|
43
|
-
AGENT_TEAMS_STATUS="
|
|
58
|
+
AGENT_TEAMS_STATUS="env-set"
|
|
59
|
+
# Escape hatch: the operator has MEASURED TeamCreate in the live tool list (R020 — "the tool
|
|
60
|
+
# exists" is itself a claim requiring measurement). This is the only path that may report
|
|
61
|
+
# activation, because only a human/model can see the tool list.
|
|
62
|
+
if [ "${OMCUSTOM_AGENT_TEAMS_VERIFIED:-0}" = "1" ]; then
|
|
63
|
+
AGENT_TEAMS_STATUS="enabled"
|
|
64
|
+
fi
|
|
44
65
|
fi
|
|
45
66
|
|
|
46
67
|
# Claude Code version detection
|
|
@@ -202,6 +223,11 @@ echo " codex CLI: ${CODEX_STATUS}" >&2
|
|
|
202
223
|
echo " gemini CLI: ${GEMINI_STATUS}" >&2
|
|
203
224
|
echo " RTK CLI: ${RTK_STATUS}" >&2
|
|
204
225
|
echo " Agent Teams: ${AGENT_TEAMS_STATUS}" >&2
|
|
226
|
+
if [ "$AGENT_TEAMS_STATUS" = "env-set" ]; then
|
|
227
|
+
echo " CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set — this is INTENT, not activation." >&2
|
|
228
|
+
echo " R018 also requires TeamCreate in the tool list; a shell hook cannot observe it." >&2
|
|
229
|
+
echo " Confirm TeamCreate before treating R018 as active (OMCUSTOM_AGENT_TEAMS_VERIFIED=1)." >&2
|
|
230
|
+
fi
|
|
205
231
|
echo " Claude Code: v${CLAUDE_VERSION} (${COMPAT_STATUS})" >&2
|
|
206
232
|
if [ "$COMPAT_STATUS" = "outdated" ]; then
|
|
207
233
|
echo " ⚠ Claude Code v${MIN_COMPAT_VERSION}+ recommended for full hook compatibility" >&2
|
|
@@ -24,12 +24,16 @@
|
|
|
24
24
|
|
|
25
25
|
> **`ls | tail` 시계열 오판 (#1417)**: `ls`는 파일명을 알파벳/사전순으로 정렬하므로 `ls <dir> | tail`로 "가장 최근 파일"을 판단하면 오판한다(파일명 순서 ≠ mtime 순서). 시계열 최신 판단은 `ls -t`, `find <dir> -newermt <ts>`, 또는 stat/timestamp 기반 정렬을 명시한다. `tail`만으로 "최신" 단정 금지. Origin: #1417 (외부 통화녹음 진단 세션 — `ls TPhoneCallRecords | tail -6`이 알파벳순이라 최신을 6/18로 오판 → `find -newermt`로 6/19~20 파일 발견해 정정).
|
|
26
26
|
|
|
27
|
+
> **`readdir` 순서는 플랫폼·런타임 의존이며 정렬되지 않는다 (#1599)**: 디렉토리 열거 결과는 **정렬순도 생성순도 아니다**. bun의 `readdir`는 APFS에서 파일시스템의 이름 해시 순서를 그대로 반환하며 node의 `readdirSync`와도 다른 순서를 낸다 — 같은 코드가 런타임·파일시스템·항목 이름에 따라 다른 순서를 낸다. 따라서 "첫 항목"·"마지막 항목"으로 대상을 고르지 말고 **정렬 키를 명시**(`.sort()`, mtime 기준 정렬)하거나 **필터로 유일성을 보장**한다. 함께: 열거 결과의 모든 항목이 디렉토리라고 가정하지 말고 `withFileTypes: true` + `isDirectory()`로 판별한다 — 관측 파일·`.DS_Store` 등이 섞이면 "첫 항목"이 디렉토리가 아닐 수 있다. **테스트에서의 파급**: 대상 디렉토리명이 `date -u +%Y-%m-%d`처럼 날짜에서 파생되면 이름이 바뀌는 날 해시 순서가 뒤집혀 **clean clone 첫 실행부터 달력 날짜의 절반에서 실패**한다(실측: 8/15 통과, 8/16~17 실패). 위 `ls | tail` 시계열 오판(#1417)의 **API 각도 변형**이다 — 도구가 순서를 보장한다는 미확인 전제에서 결과를 해석한 같은 계열. Origin: #1599 (agora 테스트 `findSessionDir`가 출력 루트를 readdir 후 전 항목을 디렉토리로 가정; 코드 조치는 `withFileTypes:true` + `isDirectory()` 필터 + 관측 파일 분리로 완료). Cross-ref: R005 「계수/매칭 방법 확인」(도구 기본 동작 미확인), R023(Conditional-Output Verification).
|
|
28
|
+
|
|
27
29
|
> **파이프 뒤 `$?`는 마지막 명령의 exit code (#1492, zsh 변형 #1540)**: `script.sh | tail -N; echo $?`처럼 검증 스크립트를 파이프에 연결한 뒤 `$?`로 읽으면 파이프라인 **마지막 명령**(`tail`)의 종료코드를 얻는다 — 스크립트 자체가 실패(exit 1)해도 `tail`이 성공(exit 0)하면 `$?=0`으로 "통과"를 오판한다. **1차 지침**: 검증 스크립트는 파이프 없이 단독 실행한다. 부득이 파이프를 써야 한다면, 원본 exit code를 읽는 문법은 **셸마다 다르다** — bash는 `${PIPESTATUS[0]}`(대문자, 0-indexed), zsh는 `$pipestatus[1]`(소문자, 1-indexed)이며 서로 호환되지 않는다. **이 저장소의 기본 셸이자 Claude Code Bash 도구 실행 셸은 zsh**이므로, bash 문법 `${PIPESTATUS[0]}`을 그대로 쓰면 zsh에서는 미정의 변수로 취급되어 **오류 없이 빈 값**을 반환한다 — 조건문에서 빈 값은 거짓으로 평가돼 "검증 통과"처럼 보이는 조용한 오판을 재생산한다. 셸을 사전 확인(`echo $SHELL` / `$BASH_VERSION` 존재 여부)한 뒤 해당 셸의 문법을 쓴다. **주의**: `${PIPESTATUS[0]}` 자체는 R023 Workflow JS 템플릿 리터럴 이스케이프 이슈(#1438, `${...}`를 JS가 평가해 ReferenceError)와 별개 문제 — 본 항목은 셸에서 파이프 뒤 exit code를 읽는 각도다. Origin: #1492 (Session 132 회고 찐빠 #3); zsh 변형은 #1540 (Session 138 회고 찐빠 #6) — `gh run watch ... | tail` 뒤 `${PIPESTATUS[0]}`가 zsh에서 빈 값을 반환해 CI 결론을 재실측해야 했음. Cross-ref: R020 ("command executed" ≠ "succeeded").
|
|
28
30
|
|
|
29
31
|
> **계수/매칭 방법 확인 (#1521)**: 카운트를 대조하기 전에 **비교 대상이 무엇을 어떻게 세는지** 먼저 확인한다 — 같은 지표라도 계수 방법이 다르면 값이 달라진다. 대표 함정 4종: (a) glob(`ls *.md`, 최상위만) vs 재귀 `find`(하위 디렉토리 포함), (b) 부분 문자열 grep(`grep "sdd"`가 `sdd-dev`까지 매칭), (c) 확장자 필터(`--include='*.md'`가 `CLAUDE.md.en`을 미매칭), (d) **머지 커밋 diff 기본 생략** — `git show --name-only <머지커밋>`은 diff를 기본적으로 출력하지 않아 변경 파일 0개로 오독된다. 머지 커밋의 변경 파일을 세려면 `--first-parent`(1차 부모 대비) 또는 `-m`(각 부모별 diff)을 명시한다. 검증 스크립트와 대조할 때는 **스크립트의 실제 계수 로직을 읽고** 같은 방법으로 센다. 위 `ls | tail` 시계열 오판(#1417)과 동류로, 도구의 기본 동작을 확인하지 않은 채 결과를 해석해 오탐에 이르는 패턴이다. Origin: #1521 (2026-07-20 세션에서 3회 반복; 두 서브에이전트가 독립적으로 동일 오탐에 도달); (d)는 #1553 찐빠 #4 (2026-07-30 세션에서 머지 커밋 `--name-only` 0파일을 "변경 없음"으로 오독).
|
|
30
32
|
|
|
31
33
|
> **도구 이름 ≠ 그 프로그램 (#1590)**: 도구를 쓰기 전에 `type <tool>`로 실체를 확인한다. Bash 도구의 `grep`은 `~/.claude/shell-snapshots/snapshot-zsh-*.sh`의 **셸 함수**이며 `ugrep --ignore-files`에 위임한다. 그 결과 `.gitignore`의 리터럴 `CLAUDE.md` 패턴을 존중해, **force-tracked 파일을 재귀 탐색에서 조용히 누락**한다(에러 없이 exit 0). 명시 경로를 준 grep은 정상 동작하므로 **traversal만 영향**을 받는다. 실측(2026-08-15): 동일 패턴·동일 대상에 대해 셸 함수 36 / `command grep` 43 / `git grep` 38 히트 — 셸 함수만 `CLAUDE.md`를 0 히트로 놓쳤다. 진단 함정: `git check-ignore`는 **index-aware**라 tracked 파일에 "not ignored"(exit 1)를 반환한다 — 원인을 보려면 `git check-ignore --no-index`를 써야 한다. 처방: 저장소 전수 조사는 `git grep`을 표준으로 한다(R017 Count Sync cross-ref). Origin: #1590.
|
|
32
34
|
|
|
35
|
+
> **v2.1.234+**: macOS/Linux 네이티브 빌드의 내장 `grep`이 pathological pattern에서 메모리 고갈 대신 fail fast하고, `-m N`과 `-A/-C` 옵션을 함께 쓸 때의 context 출력 정확도가 수정되었습니다(v2.1.235에서 추가 보강). 위 「도구 이름 ≠ 그 프로그램」(#1590) 조항과 인접한 함정입니다 — 이 저장소의 Bash 도구 `grep`은 셸 함수로 셰이딩돼 있으므로, 내장 `grep` 자체의 견고성 개선과 셰이딩 문제는 **별개 축**입니다. Darwin(이 저장소 실행 환경) 네이티브 빌드에 해당합니다.
|
|
36
|
+
|
|
33
37
|
<!--
|
|
34
38
|
> **v2.1.206+**: `/doctor`에 checked-in CLAUDE.md에서 코드베이스로부터 파생 가능한 내용을 잘라내도록 제안하는 체크가 추가되었습니다 — R005 "Context Optimization via HTML Comments"의 컨텍스트 절감 원칙과 정합(모델 불필요 메타데이터 축소).
|
|
35
39
|
-->
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
|
|
41
45
|
> **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분 초과 시 백그라운드 전환을 전제로 후속 작업을 진행합니다.
|
|
42
46
|
|
|
43
|
-
> **v2.1.233
|
|
47
|
+
> **v2.1.233+ (정정: v2.1.239에서 실제로 보장됨)**: `WebFetch`의 세션 URL 캐시 TTL이 `CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS`로 조정 가능해졌습니다(기본 15분 — **단, v2.1.239 이전에는 이 15분이 지켜지지 않고 만료된 콘텐츠가 세션 전체 동안 메모리에 남아있었습니다**. v2.1.239가 이 결함을 수정해 이제야 15분 TTL이 실제로 보장됩니다). **재확인 함정**: 같은 URL을 TTL 내 재조회하면 캐시가 반환되므로 **독립적인 2차 확인이 아닙니다** — R020 Degraded-Output Re-Verification Gate가 요구하는 "결정론적 2차 소스"로 동일 URL의 WebFetch 재호출을 쓰지 말고, 다른 소스나 CLI 실측(`npm view`, `gh`)을 사용합니다. **회고적 함의**: v2.1.239 이전 세션에서는 이 재확인 함정이 "TTL 15분 이내"가 아니라 **세션 내내** 유효했으므로, 그 시기의 WebFetch 재조회 기반 판단은 15분보다 훨씬 오래된 stale 데이터에 의존했을 수 있습니다.
|
|
44
48
|
|
|
45
49
|
> **v2.1.224+**: mid-turn에 연결된 MCP 도구가 **이름 고지 없이** tool search로 deferred되던 결함이 수정되었습니다. 구버전에서는 세션 도중 붙은 MCP 서버의 도구가 이름조차 노출되지 않아 "그런 도구 없음"으로 오판할 수 있었으므로, 위 tool-availability 주의(`command -v` 사전 확인과 동류)를 MCP 도구에도 적용합니다 — 도구 부재 결론 전에 `ToolSearch`로 실측합니다.
|
|
46
50
|
|
|
@@ -48,6 +52,8 @@
|
|
|
48
52
|
|
|
49
53
|
> **v2.1.229+**: 도구 호출의 `glob`/`file_path`/`command` 값이 **비문자열일 때 에러 화면으로 크래시**하던 문제가 수정되었습니다(해당 세션의 `--resume`에서도 재발). 구버전에서 이 크래시는 세션을 복구 불가 상태로 만들면서 **원인이 도구 인자 타입이라는 단서를 남기지 않았으므로**, 스크립트로 도구 인자를 조립할 때 문자열 타입을 보장합니다(cross-ref R023 Workflow Script Sanity Check). 좁은 터미널에서 progress bar·마크다운 표 렌더링 시 발생하던 RangeError 크래시(`claude --continue`/`--resume` 시작 시에도 발생)도 함께 수정되었습니다.
|
|
50
54
|
|
|
55
|
+
> **v2.1.233+**: Linux에서 Bash 도구 명령에 **memory cgroup**을 걸 수 있게 되어(`CLAUDE_CODE_TOOL_MEMORY_LIMIT`, opt-in) 폭주하는 빌드가 세션을 마비시키지 못합니다. 이 변수가 설정된 환경에서는 대용량 빌드·테스트가 **OOM으로 죽을 수 있으므로**, 실패를 코드 결함으로 특성화하기 전에 이 변수 설정 여부를 확인합니다(R020 Read-Before-Characterize). 같은 릴리즈에서 **샌드박스 활성 Linux의 유휴 세션이 CPU 코어 1개를 100% 점유하던 문제**도 수정되었습니다 — 구버전 Linux에서 병렬 배치의 CPU 포화·타임아웃 실패를 "부하 의존"으로 귀속하기 전에 유휴 세션의 상시 점유를 배제해야 했습니다(cross-ref R009 「파일 disjoint ≠ 자원 disjoint」). 이 저장소의 기본 실행 환경은 Darwin이므로 두 항목 모두 **현재 미적용**이며, Linux CI·컨테이너 실행에만 해당합니다.
|
|
56
|
+
|
|
51
57
|
### Capability-Aware Tool Scheduling
|
|
52
58
|
|
|
53
59
|
When dispatching parallel tool calls, consider per-tool capabilities to optimize scheduling:
|