okstra 0.106.1 → 0.107.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/for-ai/skills/okstra-brief.md +6 -3
- package/docs/for-ai/skills/okstra-container-build.md +1 -1
- package/docs/for-ai/skills/okstra-inspect.md +5 -5
- package/docs/for-ai/skills/okstra-manager.md +9 -3
- package/docs/for-ai/skills/okstra-run.md +18 -2
- package/docs/for-ai/skills/okstra-schedule.md +11 -7
- package/docs/for-ai/skills/okstra-setup.md +2 -2
- package/docs/kr/architecture.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/okstra-antigravity-exec.sh +24 -3
- package/runtime/bin/okstra-claude-exec.sh +30 -3
- package/runtime/bin/okstra-codex-exec.sh +27 -4
- package/runtime/prompts/coding-preflight/clean-code.md +2 -0
- package/runtime/prompts/coding-preflight/overview.md +2 -0
- package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +2 -2
- package/runtime/prompts/lead/convergence.md +9 -5
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +12 -8
- package/runtime/prompts/profiles/implementation-planning.md +6 -6
- package/runtime/schemas/final-report-v1.0.schema.json +27 -9
- package/runtime/skills/okstra-container-build/SKILL.md +1 -1
- package/runtime/templates/reports/final-report.template.md +14 -4
- package/runtime/templates/reports/i18n/en.json +6 -1
- package/runtime/templates/reports/i18n/ko.json +6 -1
- package/runtime/validators/validate-brief.py +37 -0
- package/runtime/validators/validate-implementation-plan-stages.py +55 -12
- package/runtime/validators/validate-run.py +354 -0
|
@@ -32,10 +32,11 @@
|
|
|
32
32
|
2. AI의 해석, 파일 링크, 용어 매핑, format 변환은 모두 `Augmentation` 또는 `> augmented:` blockquote에 둔다.
|
|
33
33
|
3. augmentation은 네 label 중 하나를 가진다: `evidence-link`, `format-conversion`, `terminology-mapping`, `intent-inference`.
|
|
34
34
|
4. `intent-inference`는 `Open Questions`의 `intent-check:`와 짝을 이룬다. 이 관계는 `validators/validate-brief.py`가 검사한다.
|
|
35
|
-
5.
|
|
35
|
+
5. `terminology-mapping` augmentation은 `Open Questions`의 `terminology:`와 짝을 이룬다(validator 검사). 단 Step 4.5 결과 marker `applied glossary:` / `skipped glossary:`는 예외 — 짝 row가 필요 없다.
|
|
36
36
|
6. reporter만 답할 수 있는 질문은 Step 6.5에서 모아 `## Reporter Confirmations`에 verbatim으로 기록한다.
|
|
37
37
|
7. 티켓 분리/연결/순서 관계는 `## Related Task Graph`의 구조화된 table에 둔다. parent-id만으로 작업 순서를 추론하지 않는다.
|
|
38
38
|
8. 모든 okstra-owned write는 `<PROJECT_ROOT>/.okstra/` 안에 둔다. 외부 파일은 reporter가 source로 명시한 경우에만 읽는다.
|
|
39
|
+
9. `Open Questions`의 모든 row는 다섯 prefix 중 하나로 시작한다: `general:`, `terminology:`, `intent-check:`, `conversion-block:`, `adr-candidate:` (validator 강제). `adr-candidate:`는 신호일 뿐 — decision file은 `implementation-planning`이 `<PROJECT_ROOT>/.okstra/decisions/`에 쓴다.
|
|
39
40
|
|
|
40
41
|
## Preflight
|
|
41
42
|
|
|
@@ -201,10 +202,12 @@ brief 본문 top blockquote의 `Recommended next phase:`에 쓴다.
|
|
|
201
202
|
|
|
202
203
|
이미 `[CONFIRMED <date> → RC-N]` marker가 있으면 pending에서 제외한다. 사용자에게 지금 답할지 묻고, 답하면 `## Reporter Confirmations`에 verbatim으로 기록한다. row는 삭제하지 않고 marker를 붙인다.
|
|
203
204
|
|
|
205
|
+
한 run당 최대 12문항. pending이 12개를 넘으면 `conversion-block:` → `intent-check:` 순으로 상위 12개만 묻고, 나머지는 `partial`로 남긴 뒤 어떤 row가 남았는지 사용자에게 알린다.
|
|
206
|
+
|
|
204
207
|
상태값:
|
|
205
208
|
|
|
206
|
-
- `complete`: pending reporter-only row가 모두 답변됨.
|
|
207
|
-
- `partial`: 일부만 답변됨.
|
|
209
|
+
- `complete`: pending reporter-only row가 모두 답변됨. validator는 모든 `intent-check:`/`conversion-block:` row에 `[CONFIRMED …]` marker가 있는지 검사한다.
|
|
210
|
+
- `partial`: 일부만 답변됨. validator는 최소 1개 row에 `[CONFIRMED …]` marker가 있는지 검사한다(아무것도 못 받았으면 `skipped`).
|
|
208
211
|
- `skipped`: 사용자가 downstream phase에 넘기기로 함.
|
|
209
212
|
- `pending`: handoff 전 상태로 보고 진행하지 않는다.
|
|
210
213
|
|
|
@@ -80,7 +80,7 @@ okstra container up --project-root <projectRoot> --task-key <task-key>
|
|
|
80
80
|
- `final-verification(whole-task): stage N not done`: 해당 stage를 implementation으로 끝내야 한다고 말한다.
|
|
81
81
|
- healthcheck 실패: failing service와 CLI가 제공한 `docker compose ... logs` line을 그대로 전달한다.
|
|
82
82
|
|
|
83
|
-
성공하면 stdout JSON을 파싱해 services, watcher pane, published ports를 요약한다. 이후 관리는 `okstra container status <task-key>`와 `down <task-key>`로 한다고 안내한다.
|
|
83
|
+
성공하면 stdout JSON을 파싱해 services, watcher pane, published ports를 요약한다. 이후 관리는 `okstra container status <task-key>`와 `down <task-key>`로 한다고 안내한다. 띄운 뒤 *무엇을 확인할지*는 implementation 리포트의 §5.7.9 Manual User Test (Draft)를 가리킨다 — 그 단계와 기대 결과가 이 빌드의 수동 테스트 스크립트다.
|
|
84
84
|
|
|
85
85
|
## status
|
|
86
86
|
|
|
@@ -40,7 +40,7 @@ okstra ensure-installed --runtime claude-code
|
|
|
40
40
|
okstra check-project --json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
`check-project`는 Bash 호출의 cwd만 본다. cwd가 아닌 프로젝트(형제 repo·monorepo 하위·요청에 명시된 프로젝트)에서 `ok:false`는 setup 부재가 아니라 false negative다 — 바로 멈추지 말고 `okstra check-project --cwd <that-dir> --json`으로 재시도한다(`--cwd`는 leading `cd` 없이 프로젝트를 지정하는 sanctioned 방식). 그것도 `ok:false`일 때만 `/okstra-setup` 안내 후 멈춘다. 이후 `projectRoot`를 literal 값으로 사용하고, 이를 받는 sub-command CLI(`recap`, `context-cost` 등)에는 `--cwd`/`--project-root <projectRoot>`로 전달한다.
|
|
44
44
|
|
|
45
45
|
## intent routing
|
|
46
46
|
|
|
@@ -134,9 +134,9 @@ task 하나의 detail은 catalog를 먼저 보고, 필요하면 `.okstra/tasks/<
|
|
|
134
134
|
|
|
135
135
|
catalog가 없으면 `.okstra/tasks/*/*/task-manifest.json` glob으로 fallback한다. disk manifest가 source of truth다.
|
|
136
136
|
|
|
137
|
-
Re-run은 source run-manifest에서 `projectId`, `taskGroup`, `taskId`, `taskType`, `taskBriefPath`, workers, relatedTasks, model overrides 등을 뽑아 command를 조립한다. `implementation`의 `--base-ref`는 run-manifest에 없고 worktree registry에 있으므로 상황에 따라 묻는다.
|
|
137
|
+
Re-run은 source run-manifest에서 `projectId`, `taskGroup`, `taskId`, `taskType`, `taskBriefPath`, workers, relatedTasks, model overrides 등을 뽑아 command를 조립한다. `implementation`의 `--base-ref`는 run-manifest에 없고 worktree registry에 있으므로 상황에 따라 묻는다. migrated task(빈 `latestRunManifestPath`)는 바로 포기하지 않는다 — `runs/**/manifests/run-manifest-*.json`을 on-disk glob해 파일명 타임스탬프 최신을 고르고, glob도 비었을 때만 사용자에게 묻는다.
|
|
138
138
|
|
|
139
|
-
Resume은 `latestResumeCommandPath` 또는 timeline entry의 `resumeCommandPath`를 확인하고 파일이 있으면 `bash <resume-command-path>`를 안내/실행한다.
|
|
139
|
+
Resume은 `latestResumeCommandPath` 또는 timeline entry의 `resumeCommandPath`를 확인하고 파일이 있으면 `bash <resume-command-path>`를 안내/실행한다. catalog/timeline 경로가 비었거나 옛 `.project-docs/okstra/...`를 가리키면 `runs/**/sessions/claude-resume-*.sh`를 glob해 타임스탬프 최신을 고른다. glob도 비었을 때만 "resume 없음"으로 선언한다.
|
|
140
140
|
|
|
141
141
|
## report
|
|
142
142
|
|
|
@@ -147,7 +147,7 @@ report path 해석 우선순위:
|
|
|
147
147
|
3. `history/timeline.json`의 특정 run `reportPath`
|
|
148
148
|
4. 특정 task-type fallback: `runs/<task-type>/reports/final-report-*.md`, stage-isolated path 포함
|
|
149
149
|
|
|
150
|
-
report
|
|
150
|
+
읽기 깊이를 요청에 맞춘다(final report는 300+줄/50K+ 토큰). 요약/결론/통과 여부 질문("요약", "핵심만", "결론", "통과했어?")은 전체를 읽지 말고 `runs/<task-type-segment>/status/final-<task-type-segment>-<NNN>.status`(stage-isolated: `runs/<task-type-segment>/stage-<N>/status/`) sidecar의 판정 + report 선두 요약 블록만 읽는다. "전체/다 읽어/본문"일 때만 전체 ingest한다. 완료 signal은 있는데 file이 없으면 missing report로 말하고, 아직 완료되지 않았으면 current status와 workStatus를 보여준다.
|
|
151
151
|
|
|
152
152
|
## time
|
|
153
153
|
|
|
@@ -157,7 +157,7 @@ report file 존재를 확인한 뒤, 읽을지 사용자에게 묻는다. 완료
|
|
|
157
157
|
okstra time-report <task-key> --project-root <projectRoot> --json
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
모든 `*Ms`는 `HH:MM:SS`로 변환해 보여준다. `CPU sum`은 lead와 workers 시간을 합친 overlapping cost이며 wall-clock이 아니다.
|
|
160
|
+
모든 `*Ms`는 `HH:MM:SS`로 변환해 보여준다. `CPU sum`은 lead와 workers 시간을 합친 overlapping cost이며 wall-clock이 아니다. wall-clock은 사용자가 명시적으로 물을 때만 `perRunWallClock`에서 보여준다. `단계별`/`stage별`/`어느 단계가 오래`는 task-type 뷰(By task type 표)가 기본 답이다 — '측정 불가'로 처리하지 않는다. intra-run `phaseTimelines`는 'Phase 1~7'·'어느 phase가' 같은 명시적 요청일 때만 렌더하고, 비어 있으면 헤드라인이 아니라 각주로만 언급한다.
|
|
161
161
|
|
|
162
162
|
`unavailable[]`은 totals에 합치지 않고 별도 note로 표시한다.
|
|
163
163
|
|
|
@@ -26,7 +26,7 @@ okstra manager init --manager-id <manager-id> --json
|
|
|
26
26
|
okstra manager discover-projects --json
|
|
27
27
|
okstra manager new project --manager-id <manager-id> --project-id <project-id> --project-root <abs-path> [--role <role>] [--tag <tag>] --json
|
|
28
28
|
okstra manager new task-group --manager-id <manager-id> --task-group <task-group> --json
|
|
29
|
-
okstra manager new task --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --task <project-id:task-group:task-id> --json
|
|
29
|
+
okstra manager new task --manager-id <manager-id> --task-group <task-group> --task-id <task-id> [--task <project-id:task-group:task-id> ...] [--objective <text>] [--common-brief <path>] [--progress-mode <manual|auto>] --json
|
|
30
30
|
okstra manager task assign --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --project-id <project-id> [--child-task-id <child-task-id>] [--role <role>] [--tag <tag>] [--assignment <text>] --json
|
|
31
31
|
okstra manager task note --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --scope <shared|project> [--project-id <project-id>] --body <text> --json
|
|
32
32
|
okstra manager task sync --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --json
|
|
@@ -36,11 +36,16 @@ okstra manager task run --manager-id <manager-id> --project-id <project-id> --ta
|
|
|
36
36
|
|
|
37
37
|
## 저장 모델
|
|
38
38
|
|
|
39
|
-
Manager state 는 `~/.okstra/managers/<manager-id>/` 아래에
|
|
39
|
+
Manager state 는 `~/.okstra/managers/<manager-id>/` 아래에 저장되며, manager 루트와 task 디렉터리 두 계층으로 나뉜다.
|
|
40
|
+
|
|
41
|
+
manager 루트 직하:
|
|
40
42
|
|
|
41
43
|
- `manager.json`: manager id / schema / createdAt
|
|
42
44
|
- `projects.json`: 등록된 projectId, projectRoot, role, tags
|
|
43
|
-
|
|
45
|
+
|
|
46
|
+
task 디렉터리 `task-groups/<safe-group>/<safe-task>/` 아래:
|
|
47
|
+
|
|
48
|
+
- `manifest.json`: manager task objective, common brief, progress mode
|
|
44
49
|
- `children.json`: child task plan, assignment, launch metadata
|
|
45
50
|
- `directives.jsonl`: shared/project directive rows
|
|
46
51
|
- `snapshots.json`: `task sync` 가 project-local `.okstra` 에서 읽어온 read-side snapshot
|
|
@@ -53,6 +58,7 @@ Manager state 는 `~/.okstra/managers/<manager-id>/` 아래에 저장된다.
|
|
|
53
58
|
|
|
54
59
|
`task run` 은 child 작업을 직접 실행하지 않고 launch packet 을 준비한다. 반환 packet 의 핵심 필드는:
|
|
55
60
|
|
|
61
|
+
- `taskKey`: child 의 `project-id:task-group:task-id` (child 식별 공개 키 — `child-launch-prepared` event 에도 기록)
|
|
56
62
|
- `backend`: `$TMUX` 가 있으면 `tmux-child-lead`, 없으면 `subagent-child-lead`
|
|
57
63
|
- `workerDispatchBackend`: v1 에서는 항상 `subagent`
|
|
58
64
|
- `projectRoot`: child project root
|
|
@@ -132,7 +132,7 @@ okstra wizard confirmation --state-file /tmp/okstra-wizard/state.json
|
|
|
132
132
|
okstra wizard render-args --state-file /tmp/okstra-wizard/state.json
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
`args` object의
|
|
135
|
+
`args` object의 key를 `okstra render-bundle` flag로 전달한다. 빈 string 값도 명시적으로 전달한다. 예외: `chain-stages` key는 render-bundle flag가 아니다 — Step 7 무인 연쇄 루프를 구동하는 값이라 flag로 넘기지 않는다(`run.py`는 `--stage`/`--stages`만 받는다). render-bundle block의 `--stage`는 implementation·final-verification 전용, `--stages`는 release-handoff 전용(빈 값 = whole-task)이다.
|
|
136
136
|
|
|
137
137
|
```bash
|
|
138
138
|
okstra render-bundle \
|
|
@@ -189,7 +189,7 @@ implementation에서 Tier 3 conformance가 환경상 불가능할 때만 사용
|
|
|
189
189
|
사용자에게 세 선택지를 준다.
|
|
190
190
|
|
|
191
191
|
1. no-team background로 진행.
|
|
192
|
-
2.
|
|
192
|
+
2. 대기 — dispatch 보류, stage worktree·run context 보존. 점유 run 종료 후 같은 stage를 resume으로 재개하도록 resume 명령(`okstra-inspect` history → resume)을 사용자에게 출력한다.
|
|
193
193
|
3. 직접 입력.
|
|
194
194
|
|
|
195
195
|
이 picker는 스킬이 만드는 것이므로 wizard option 축약 금지 규칙과 별개다.
|
|
@@ -226,6 +226,22 @@ render-bundle 후 `<INSTRUCTION_SET_PATH>/claude-execution-prompt.md`를 verbati
|
|
|
226
226
|
Took over as Claude lead for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
단일 원소 chain이면 Step 6 종료가 곧 run 종료다. 아래 Step 7은 `chain-stages` CSV가 2개 이상일 때만 적용된다.
|
|
230
|
+
|
|
231
|
+
## implementation 무인 연쇄 (chain-stages)
|
|
232
|
+
|
|
233
|
+
`task-type == implementation`이고 render-args `chain-stages` CSV가 원소 2개 이상이면, 현재 세션이 오케스트레이터로서 stage를 의존성 순서대로 무인 연쇄 실행한다. 큐 = `chain-stages`를 `,`로 분해한 위상정렬 stage 리스트. 큐의 각 stage `N`에 대해 순서대로:
|
|
234
|
+
|
|
235
|
+
1. render-bundle을 같은 인자로, 단 `--stage N`으로 재호출한다(base commit은 prepare가 predecessor의 done `head_commit`으로 자동 계산 — 손으로 넘기지 않는다). conformance waiver·concurrent-run·git-reconcile 게이트는 매 stage의 render-bundle마다 동일 적용.
|
|
236
|
+
2. Step 6대로 Claude lead가 되어 그 stage의 Phase 1~7을 인라인 실행한다. Phase 6의 lead persistence가 `runs/<plan-task-key>/consumers.jsonl`에 그 stage의 `status:"done"` 행을 append한다.
|
|
237
|
+
3. `done` 행 기록을 확인한 뒤 다음 stage로 넘어간다. stage 경계마다 컨텍스트(잔여 pane·완료 teammate)를 정리한다.
|
|
238
|
+
4. stage 시작/완료마다 한 줄 보고: `stage N/<총개수> 시작` / `stage N done → 다음 K`.
|
|
239
|
+
|
|
240
|
+
큐를 모두 소화하면 연쇄를 종료하고 완료를 보고한다.
|
|
241
|
+
|
|
242
|
+
- **다음 stage가 아직 ready 아님 — 정상 종료:** 큐의 stage가 다른 implementation run에 started/reserved로 점유돼 render-bundle이 `--stage N already in progress or reserved by another run`(StageTargetError)으로 거부되면, 이는 예외가 아니다 — 연쇄를 **정상 종료**하고 남은 큐를 보고한다(예: `남은 큐: stage 4, 5 — 점유 해제 후 okstra-run으로 재개`).
|
|
243
|
+
- **연쇄 중 예외 게이트:** render-bundle이 concurrent-run 충돌이나 git stale-SHA 재조정을 띄우면 **그 stage에서 연쇄를 멈추고** 게이트를 Step 5 절차대로 사용자에게 제시한다. 사용자가 해소하면 그 자리에서 남은 큐를 이어 재개한다. 데이터 손상·동시 점유 충돌은 사람이 확인 — 무인 연쇄의 안전 경계다.
|
|
244
|
+
|
|
229
245
|
## 금지 패턴
|
|
230
246
|
|
|
231
247
|
- wizard가 내준 질문 순서를 바꾸기.
|
|
@@ -43,7 +43,7 @@ runtime이나 project setup이 없으면 `/okstra-setup` 안내 후 멈춘다.
|
|
|
43
43
|
|
|
44
44
|
## model gate
|
|
45
45
|
|
|
46
|
-
스킬 frontmatter는 `model: opus`다. 활성 모델이 Opus-class 또는 Fable-class가 아니면 사용자에게 전환을 권한다. 사용자가 lower model로 진행하겠다고 명시하면 진행하되, 생성 schedule 상단에
|
|
46
|
+
스킬 frontmatter는 `model: opus`다. 활성 모델이 Opus-class 또는 Fable-class가 아니면 사용자에게 전환을 권한다. 사용자가 lower model로 진행하겠다고 명시하면 진행하되, 생성 schedule 상단에 verbatim warning 한 줄을 넣는다: `> ⚠️ Generated with <model> (not Opus). Cross-task synthesis quality may be reduced.`
|
|
47
47
|
|
|
48
48
|
## task-group 해석
|
|
49
49
|
|
|
@@ -53,6 +53,8 @@ runtime이나 project setup이 없으면 `/okstra-setup` 안내 후 멈춘다.
|
|
|
53
53
|
4. raw `taskGroup` fallback은 쓰지 않는다.
|
|
54
54
|
5. 매칭된 각 task의 `task-manifest.json`을 직접 읽는다. catalog는 stale일 수 있고 manifest가 authoritative다.
|
|
55
55
|
|
|
56
|
+
0개 매칭이면 `해당 task-group을 찾을 수 없습니다.`를 출력하고 멈춘다.
|
|
57
|
+
|
|
56
58
|
## workStatus filter
|
|
57
59
|
|
|
58
60
|
각 manifest에서 workStatus를 확인한다. 없거나 비어 있으면 `okstra-inspect`의 `status.4` inference table을 적용한다.
|
|
@@ -70,7 +72,7 @@ runtime이나 project setup이 없으면 `/okstra-setup` 안내 후 멈춘다.
|
|
|
70
72
|
- `phase-done`
|
|
71
73
|
- 그 밖의 non-done inferred/display 상태
|
|
72
74
|
|
|
73
|
-
필터 후 0개면 schedule file을 만들지
|
|
75
|
+
필터 후 0개면 schedule file을 만들지 않고 `해당 task-group의 모든 task가 done 상태입니다. 생성할 schedule이 없습니다.`를 출력한다.
|
|
74
76
|
|
|
75
77
|
## per-task data extraction
|
|
76
78
|
|
|
@@ -148,7 +150,7 @@ ambiguous하면 가장 가까운 phase에 두고 rationale을 phase section 상
|
|
|
148
150
|
top-level 계약 sections:
|
|
149
151
|
|
|
150
152
|
1. `## At a Glance`
|
|
151
|
-
2. `## Executive Summary`
|
|
153
|
+
2. `## Executive Summary` (필수 subsection `### Effort Sizing 기준` 포함 — `validate-schedule.py`가 검사)
|
|
152
154
|
3. `## Task Dependency Graph`
|
|
153
155
|
4. `## Phase 1: Critical Fixes`
|
|
154
156
|
5. `## Phase 2: Enhancements`
|
|
@@ -176,7 +178,7 @@ phase에 task가 없어도 heading을 유지하고 `_없음_`을 쓴다.
|
|
|
176
178
|
> Source: okstra <mode> (<N> tasks included, <M> done excluded)
|
|
177
179
|
```
|
|
178
180
|
|
|
179
|
-
title suffix `— Work Schedule`은 validator가 검사한다.
|
|
181
|
+
title suffix `— Work Schedule`은 validator가 검사한다. `<project-id>`는 `task-catalog.json` 최상위 `projectId`를 우선 쓰고, 없으면 첫 매칭 manifest의 `projectId`를 쓴다. 임의 값 생성 금지.
|
|
180
182
|
|
|
181
183
|
## At a Glance
|
|
182
184
|
|
|
@@ -215,8 +217,8 @@ enum 값:
|
|
|
215
217
|
|
|
216
218
|
1. `**Problem**:`
|
|
217
219
|
2. `**Solution**:`
|
|
218
|
-
3. `**Work Breakdown**:`
|
|
219
|
-
4. `**Verification Commands**:`
|
|
220
|
+
3. `**Work Breakdown**:` — 뒤에 `| Step | File | Action | Detail |` table
|
|
221
|
+
4. `**Verification Commands**:` — 뒤에 ` ```bash ` fenced block
|
|
220
222
|
5. `**Rollback**:`
|
|
221
223
|
|
|
222
224
|
`[NEEDS-OKSTRA-RUN]` 또는 `[PARSE-ERROR: <section>]` task는 가능한 field만 채우되 marker를 task heading 바로 아래 둔다.
|
|
@@ -242,6 +244,8 @@ arrow는 ASCII `->`만 사용한다.
|
|
|
242
244
|
|
|
243
245
|
## Gantt Chart
|
|
244
246
|
|
|
247
|
+
**directive override (최우선):** 아래 휴리스틱 적용 전 directive source의 `## Directive` section을 먼저 확인한다. 해석 순서(first hit): (1) `--directive-file <abs-path>` 인자, (2) `<PROJECT_ROOT>/.okstra/tasks/<task-group-segment>/schedule/instruction-set/analysis-material.md`, (3) 없으면 휴리스틱 그대로(정상 경로 — 경고·중단 없음). directive가 Gantt 렌더/생략을 지시하면 휴리스틱·skip 규칙을 덮어쓰고, 해당 section에 `> _Per Directive directive: <verbatim 발췌>._` 한 줄을 남긴다. directive가 day allocation·phase weight·sub-task 분해를 주면 bar 길이에 verbatim 반영한다.
|
|
248
|
+
|
|
245
249
|
기본은 렌더한다. day signal이 하나라도 있으면 rough estimate라도 chart를 만든다.
|
|
246
250
|
|
|
247
251
|
렌더 조건 예:
|
|
@@ -278,7 +282,7 @@ axis는 relative day count만 사용한다. calendar date, weekday, "오늘 + N"
|
|
|
278
282
|
|
|
279
283
|
선택 A: 짧은 설명으로 inline replacement.
|
|
280
284
|
|
|
281
|
-
선택 B: 반복되는 code가 많으면 마지막 `## Glossary`에 모든 code를 해석한다.
|
|
285
|
+
선택 B: 반복되는 code가 많으면 마지막 `## Glossary`에 모든 code를 해석한다. glossary table header는 정확히 `| Code | Description |`(영문 literal — validator가 검사). milestone code(`M1`, `M2` …)도 `FC-N`과 동일하게 glossary 미해석 시 validator fail.
|
|
282
286
|
|
|
283
287
|
decision-item letter code(`A1`, `B2`, `C3`, `D4`)는 schedule에 넣지 않는다.
|
|
284
288
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
npx -y okstra@latest install --runtime claude-code
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
이 명령은 idempotent로 취급한다. 이미 설치되어 있어도 다시 실행해 runtime, skill, agent payload를 현재 package 버전과
|
|
43
|
+
이 명령은 idempotent로 취급한다. 이미 설치되어 있어도 다시 실행해 runtime, skill, agent payload를 현재 package 버전과 맞춘다(agent payload = `~/.claude/agents/<worker>.md` worker 정의 + `~/.okstra/installed-agents.json` manifest). 실패하면 stderr를 그대로 사용자에게 보여준다. legacy `okstra-install.sh`로 우회하지 않는다.
|
|
44
44
|
|
|
45
45
|
## 명령 호출 규칙
|
|
46
46
|
|
|
@@ -123,7 +123,7 @@ okstra doctor --runtime claude-code
|
|
|
123
123
|
|
|
124
124
|
짧게 다음 정보를 포함한다.
|
|
125
125
|
|
|
126
|
-
- runtime 위치: `~/.okstra`
|
|
126
|
+
- runtime 위치: `~/.okstra` (설치 요약의 `version stamp: x.y.z` 줄을 함께 보여준다)
|
|
127
127
|
- project metadata: `<PROJECT_ROOT>/.okstra/project.json`
|
|
128
128
|
- `projectId`
|
|
129
129
|
- 다음 단계: `/okstra-run`
|
package/docs/kr/architecture.md
CHANGED
|
@@ -635,7 +635,7 @@ Claude가 작성하는 최종 보고서는 아래 구조를 우선 사용합니
|
|
|
635
635
|
- `## 1. 문제 또는 검증 대상 요약` — §6.1 Consensus / §6.2 Differences 표 각각 `Source items (worker:item)` 컬럼 보존 (cross-worker traceability).
|
|
636
636
|
- `## 2. 에이전트별 실행 현황`
|
|
637
637
|
- `## 3. Cross Verification 결과` — §2.1 Primary Evidence 에 `Source items (worker:item)` + `Source (path:line / log)` 컬럼.
|
|
638
|
-
- `## 4. 최종 판단` — `implementation-planning` 의 §5.5.9 Plan Body Verification 은 `Verdict
|
|
638
|
+
- `## 4. 최종 판단` — `implementation-planning` 의 §5.5.9 Plan Body Verification 은 plan item 별로 그룹핑해 emit: 각 항목의 `subject`(무엇을 검증했는지 한 줄) 를 헤딩으로, 그 아래 워커별 `Worker / Verdict / Breakage kind / Note` 표를 붙이고, gate 값·verdict 토큰·breakage kind(a–f) 범례 3종을 함께 렌더한다.
|
|
639
639
|
- `## 1. Clarification Items` — 통합 8-열 표 한 곳. 기존 §6.1 / §6.2 / §5.5.8 / §5.5.9 Open Questions 는 deprecated 되어 validator 가 등장 시 fail.
|
|
640
640
|
- `## 6. 권장 다음 단계`
|
|
641
641
|
- `## Token Usage Summary` — sentinel (`pending` / `N/A` / `--` / `?` / 빈 셀) 또는 zero (`0` / `$0.00`) 박제 시 validator 가 출고를 차단합니다. `Codex/Antigravity CLI 추가 비용` 행만 "CLI 미사용" 의미로 `$0.00` 허용.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -94,7 +94,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
|
|
|
94
94
|
- **Reading Confirmation** lives in the audit sidecar (`runs/<task-type>/worker-results/report-writer-worker-audit-<task-type>-<seq>.md`), never in the data.json or the main worker-results file.
|
|
95
95
|
- Include all four convergence categories. The schema's `crossVerification.consensus` / `.differences` arrays carry full / partial / contested / worker-unique items; do not omit any.
|
|
96
96
|
- Convergence round history goes in `crossVerification.roundHistory.rounds[]` with `round2SkippedReason`. When convergence is disabled, set `crossVerification.roundHistory` to `{"disabled": true}`. Values come verbatim from `state/convergence-<task-type>-<seq>.json` — do not recompute.
|
|
97
|
-
- `verification-error` votes are their own verdict (`
|
|
97
|
+
- `verification-error` votes are their own verdict (`planItems[].verdicts[].verdict` enum); they are NOT folded into AGREE / DISAGREE counts.
|
|
98
98
|
- **Token Usage cells are `null` in Phase 6.** Leave `tokenUsage.lead.totalTokens` / `.billableTokens` / `.costUsd` (and the worker / grand rows, and per-row `executionStatus[].totalTokens` etc.) as JSON `null`. The renderer emits `--` for nulls. Phase 7's `okstra-token-usage.py --substitute-data` populates them and re-renders. **Never** write `0`, `"not-collected"`, `"--"`, or any sentinel value — those are how zeros sneak into the report.
|
|
99
99
|
- If only one analysis worker produced usable output, perform a reduced-confidence write-up and say so explicitly (e.g. note in `executionStatus[].summary`).
|
|
100
100
|
- If evidence is missing, write `"I don't know"` in the relevant statement field rather than fabricating confidence.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Purpose: Claude Code's Bash permission matcher requires explicit approval for
|
|
5
5
|
# commands that contain shell metacharacters (stdout/stderr redirects, pipes).
|
|
6
|
-
# `agy --print "<prompt>" --model <model> --add-dir <root> 2>>log >
|
|
6
|
+
# `agy --print "<prompt>" --model <model> --add-dir <root> 2>>log > <fifo>`
|
|
7
7
|
# therefore triggers a permission prompt every dispatch even when `Bash(agy:*)`
|
|
8
8
|
# is allowlisted, because the redirect tokens disqualify the simple-prefix match.
|
|
9
9
|
#
|
|
@@ -128,6 +128,11 @@ log_path="${prompt_path%.md}.log"
|
|
|
128
128
|
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
129
129
|
: > "$log_path"
|
|
130
130
|
|
|
131
|
+
# stdout-mirror FIFO path (created at the dispatch block below). Declared here,
|
|
132
|
+
# before the EXIT trap is defined, so the trap's cleanup reference is always set
|
|
133
|
+
# under `set -u`.
|
|
134
|
+
stdout_fifo=""
|
|
135
|
+
|
|
131
136
|
# Heartbeat sidecar (`<prompt>.status.json`). See `okstra-codex-exec.sh` for
|
|
132
137
|
# the full design comment — kept in lock-step. Two consumers:
|
|
133
138
|
# 1. `antigravity-worker` step 8c reads `log_path` to capture a diagnostic
|
|
@@ -186,6 +191,7 @@ _okstra_status_finish() {
|
|
|
186
191
|
if [[ -n "$caller_pane" && -n "$original_caller_title" ]]; then
|
|
187
192
|
tmux select-pane -t "$caller_pane" -T "$original_caller_title" 2>/dev/null || true
|
|
188
193
|
fi
|
|
194
|
+
[[ -n "$stdout_fifo" ]] && rm -f "$stdout_fifo" 2>/dev/null || true
|
|
189
195
|
}
|
|
190
196
|
trap _okstra_status_finish EXIT
|
|
191
197
|
|
|
@@ -236,10 +242,22 @@ fi
|
|
|
236
242
|
# can SIGTERM from the watchdog.
|
|
237
243
|
# stderr: appended to the live log only — keeps the wrapper's stderr clean.
|
|
238
244
|
# exit: agy's own exit code is preserved by `wait`.
|
|
245
|
+
# stdout mirror via a named FIFO instead of a `> >(tee …)` process substitution:
|
|
246
|
+
# bash opens the substituted `/dev/fd/<n>` by pathname, which the macOS seatbelt
|
|
247
|
+
# sandbox around a non-tmux subagent's Bash tool denies (EPERM), so agy would
|
|
248
|
+
# never start. A FIFO under `.okstra/` is opened by an ordinary sandbox-writable
|
|
249
|
+
# path and works in every dispatch context while keeping agy a single
|
|
250
|
+
# addressable PID for the watchdog. See `okstra-codex-exec.sh` for the full note.
|
|
251
|
+
stdout_fifo="${log_path}.stdout.fifo"
|
|
252
|
+
rm -f "$stdout_fifo"
|
|
253
|
+
mkfifo "$stdout_fifo"
|
|
254
|
+
tee -a "$log_path" < "$stdout_fifo" &
|
|
255
|
+
stdout_tee_pid=$!
|
|
256
|
+
|
|
239
257
|
agy --print "$(cat "$prompt_path")" --model "$model" "${add_dir_args[@]}" \
|
|
240
258
|
--print-timeout "$PRINT_TIMEOUT" --dangerously-skip-permissions \
|
|
241
259
|
2>> "$log_path" \
|
|
242
|
-
>
|
|
260
|
+
> "$stdout_fifo" &
|
|
243
261
|
agy_pid=$!
|
|
244
262
|
|
|
245
263
|
# Idle watchdog — see `okstra-codex-exec.sh` for the full rationale.
|
|
@@ -285,6 +303,9 @@ if [[ -n "$watchdog_pid" ]]; then
|
|
|
285
303
|
wait "$watchdog_pid" 2>/dev/null || true
|
|
286
304
|
fi
|
|
287
305
|
|
|
288
|
-
|
|
306
|
+
# Drain the stdout-mirror tee (agy's closed write end gives it EOF), then remove
|
|
307
|
+
# the FIFO (the EXIT trap also clears it on an abnormal exit).
|
|
308
|
+
wait "$stdout_tee_pid" 2>/dev/null || true
|
|
309
|
+
rm -f "$stdout_fifo"
|
|
289
310
|
|
|
290
311
|
exit "$agy_exit"
|
|
@@ -61,6 +61,11 @@ fi
|
|
|
61
61
|
log_path="${prompt_path%.md}.log"
|
|
62
62
|
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
63
63
|
: > "$log_path"
|
|
64
|
+
# stdout/stderr mirror FIFO paths (created at the dispatch block below). Declared
|
|
65
|
+
# here, before the EXIT trap is defined, so the trap's cleanup references are
|
|
66
|
+
# always set under `set -u`.
|
|
67
|
+
stdout_fifo=""
|
|
68
|
+
stderr_fifo=""
|
|
64
69
|
status_path="${prompt_path}.status.json"
|
|
65
70
|
started_ts=$(date +%s)
|
|
66
71
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
@@ -76,15 +81,34 @@ _okstra_status_finish() {
|
|
|
76
81
|
python3 "$script_dir/okstra-wrapper-status.py" \
|
|
77
82
|
finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
|
|
78
83
|
>>"$log_path" 2>&1 || true
|
|
84
|
+
{ [[ -n "$stdout_fifo" ]] && rm -f "$stdout_fifo"; [[ -n "$stderr_fifo" ]] && rm -f "$stderr_fifo"; } 2>/dev/null || true
|
|
79
85
|
}
|
|
80
86
|
trap _okstra_status_finish EXIT
|
|
81
87
|
|
|
82
88
|
cd "$project_root"
|
|
83
89
|
set +e
|
|
90
|
+
# stdout and stderr are each mirrored to the live log AND the wrapper's own
|
|
91
|
+
# stdout/stderr. Process substitutions (`> >(tee …)`) do this, but bash opens the
|
|
92
|
+
# substituted `/dev/fd/<n>` by pathname, and the macOS seatbelt sandbox around a
|
|
93
|
+
# non-tmux subagent's Bash tool denies that open (EPERM) — claude then never
|
|
94
|
+
# starts and the wrapper exits with no output. Named FIFOs under `.okstra/` are
|
|
95
|
+
# opened by ordinary sandbox-writable paths, so the mirror works in every
|
|
96
|
+
# dispatch context (real tmux pane OR sandboxed subagent) while keeping claude a
|
|
97
|
+
# single addressable PID for the watchdog. See `okstra-codex-exec.sh` for the
|
|
98
|
+
# full note.
|
|
99
|
+
stdout_fifo="${log_path}.stdout.fifo"
|
|
100
|
+
stderr_fifo="${log_path}.stderr.fifo"
|
|
101
|
+
rm -f "$stdout_fifo" "$stderr_fifo"
|
|
102
|
+
mkfifo "$stdout_fifo" "$stderr_fifo"
|
|
103
|
+
tee -a "$log_path" < "$stdout_fifo" &
|
|
104
|
+
stdout_tee_pid=$!
|
|
105
|
+
tee -a "$log_path" < "$stderr_fifo" >&2 &
|
|
106
|
+
stderr_tee_pid=$!
|
|
107
|
+
|
|
84
108
|
claude -p --model "$model" ${extra_args[@]+"${extra_args[@]}"} --output-format=stream-json \
|
|
85
109
|
< "$prompt_path" \
|
|
86
|
-
>
|
|
87
|
-
2>
|
|
110
|
+
> "$stdout_fifo" \
|
|
111
|
+
2> "$stderr_fifo" &
|
|
88
112
|
claude_pid=$!
|
|
89
113
|
|
|
90
114
|
# Idle watchdog: if the live log sees no write for $idle_timeout_secs, SIGTERM
|
|
@@ -130,6 +154,9 @@ if [[ -n "$watchdog_pid" ]]; then
|
|
|
130
154
|
kill "$watchdog_pid" 2>/dev/null || true
|
|
131
155
|
wait "$watchdog_pid" 2>/dev/null || true
|
|
132
156
|
fi
|
|
133
|
-
|
|
157
|
+
# Drain the stdout/stderr mirror tees (claude's closed write ends give them EOF),
|
|
158
|
+
# then remove the FIFOs (the EXIT trap also clears them on an abnormal exit).
|
|
159
|
+
wait "$stdout_tee_pid" "$stderr_tee_pid" 2>/dev/null || true
|
|
160
|
+
rm -f "$stdout_fifo" "$stderr_fifo"
|
|
134
161
|
|
|
135
162
|
exit "$claude_exit"
|
|
@@ -167,6 +167,11 @@ log_path="${prompt_path%.md}.log"
|
|
|
167
167
|
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
168
168
|
: > "$log_path"
|
|
169
169
|
|
|
170
|
+
# stdout-mirror FIFO path (created at the dispatch block below). Declared here,
|
|
171
|
+
# before the EXIT trap is defined, so the trap's cleanup reference is always set
|
|
172
|
+
# under `set -u`.
|
|
173
|
+
stdout_fifo=""
|
|
174
|
+
|
|
170
175
|
# Heartbeat sidecar (`<prompt>.status.json`). The codex CLI streams progress
|
|
171
176
|
# over stdout/stderr but the only structured signal the caller subagent
|
|
172
177
|
# polls is `BashOutput`'s binary `running`/`completed` state. The sidecar
|
|
@@ -237,6 +242,7 @@ _okstra_status_finish() {
|
|
|
237
242
|
if [[ -n "$caller_pane" && -n "$original_caller_title" ]]; then
|
|
238
243
|
tmux select-pane -t "$caller_pane" -T "$original_caller_title" 2>/dev/null || true
|
|
239
244
|
fi
|
|
245
|
+
[[ -n "$stdout_fifo" ]] && rm -f "$stdout_fifo" 2>/dev/null || true
|
|
240
246
|
}
|
|
241
247
|
trap _okstra_status_finish EXIT
|
|
242
248
|
|
|
@@ -307,10 +313,25 @@ fi
|
|
|
307
313
|
# model instead of prompting, so the turn proceeds. The sandbox stays
|
|
308
314
|
# `workspace-write`: this removes the approval gate, not the sandbox. It is the
|
|
309
315
|
# codex-side equivalent of the antigravity wrapper's `--dangerously-skip-permissions`.
|
|
316
|
+
# stdout must reach BOTH the live log and the wrapper's own stdout (so the
|
|
317
|
+
# subagent's `BashOutput` captures the final text for Phase 5 synthesis). A
|
|
318
|
+
# process substitution (`> >(tee …)`) does this, but bash opens the substituted
|
|
319
|
+
# `/dev/fd/<n>` by pathname, and the macOS seatbelt sandbox wrapping a non-tmux
|
|
320
|
+
# subagent's Bash tool denies that open (EPERM) — codex then never starts and
|
|
321
|
+
# the wrapper exits with no output (the documented non-tmux dispatch failure). A
|
|
322
|
+
# named FIFO is opened by an ordinary path under `.okstra/` (sandbox-writable),
|
|
323
|
+
# so the mirror works in every dispatch context (real tmux pane OR sandboxed
|
|
324
|
+
# subagent) while keeping codex a single addressable PID for the watchdog.
|
|
325
|
+
stdout_fifo="${log_path}.stdout.fifo"
|
|
326
|
+
rm -f "$stdout_fifo"
|
|
327
|
+
mkfifo "$stdout_fifo"
|
|
328
|
+
tee -a "$log_path" < "$stdout_fifo" &
|
|
329
|
+
stdout_tee_pid=$!
|
|
330
|
+
|
|
310
331
|
codex exec -C "$project_root" ${extra_args[@]+"${extra_args[@]}"} --model "$model" --sandbox workspace-write -c approval_policy=never - \
|
|
311
332
|
< "$prompt_path" \
|
|
312
333
|
2>> "$log_path" \
|
|
313
|
-
>
|
|
334
|
+
> "$stdout_fifo" &
|
|
314
335
|
codex_pid=$!
|
|
315
336
|
|
|
316
337
|
# Idle watchdog: poll the live log's mtime; if no write (stdout or stderr)
|
|
@@ -360,8 +381,10 @@ if [[ -n "$watchdog_pid" ]]; then
|
|
|
360
381
|
wait "$watchdog_pid" 2>/dev/null || true
|
|
361
382
|
fi
|
|
362
383
|
|
|
363
|
-
# Drain the
|
|
364
|
-
#
|
|
365
|
-
|
|
384
|
+
# Drain the stdout-mirror tee so the final lines reach the live log and the
|
|
385
|
+
# caller's stdout before exit. codex's closed write end gives the tee EOF; then
|
|
386
|
+
# remove the FIFO (the EXIT trap also clears it on an abnormal exit).
|
|
387
|
+
wait "$stdout_tee_pid" 2>/dev/null || true
|
|
388
|
+
rm -f "$stdout_fifo"
|
|
366
389
|
|
|
367
390
|
exit "$codex_exit"
|
|
@@ -144,6 +144,8 @@ If the answer is "no, I'd have to group several lines together and invent a name
|
|
|
144
144
|
- A long `.map().filter().reduce()` chain where each stage does non-obvious work and no stage has a name.
|
|
145
145
|
- Boolean expressions long enough that intent is buried — `if (a && b && !c && (d || e.f > 0) && ...)` without a named predicate.
|
|
146
146
|
- A function that orchestrates 4+ distinct logical phases inline (*fetch → resolve → branch → persist → publish*), each more than a line or two. Even without deep nesting, that's enough work to warrant named sub-methods.
|
|
147
|
+
- A chain of `if (x === SomeEnum.A) return …` over the values of one enum — that's a hand-rolled switch. Use a real `switch (x)` so the cases line up and exhaustiveness is visible; it also drops the trailing fall-through `return` that an if-chain needs.
|
|
148
|
+
- A dense data literal — an object/struct/`{...}` carrying several inline ternaries or conditional spreads (`...(cond ? { x } : {})`) — assembled inline in a method that also does other work. Extract a named builder (`buildX(...)`) so the method reads as "compute X → assign X", and each conditional field gets a place to be understood.
|
|
147
149
|
|
|
148
150
|
### 50-line cap
|
|
149
151
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
4. **Clean Code — names carry WHAT, comments explain WHY** — Identifiers must make intent obvious; if a comment would describe WHAT the code does, rename instead. Reserve comments for non-obvious WHY (hidden constraint, workaround, surprising invariant). Delete dead/commented-out code immediately — git history is the archive. *Example — WHAT (rename instead): `// increment counter` above `i++`. WHY (keep): `// retry up to 3x: upstream returns 502 during deploys`.*
|
|
9
9
|
5. **Function length cap — 50 lines** — A single function/method body must stay within 50 lines, counting only effective code (exclude blank lines, comments, and pure data declarations such as large enums, lookup tables, or constant maps). Crossing the cap is an extraction signal, not a style nit. *Self-check: for any function newly added or substantially edited, count effective body lines; if over 50, split before declaring complete, or surface the violation and confirm with the user.*
|
|
10
10
|
6. **Simplest conforming approach** — When choosing how to implement or fix something, pick the simplest, most direct solution path that still conforms to the project's architecture and established conventions. This is broader than KISS (#2): KISS bounds abstraction; this bounds the whole approach. Simplicity never licenses bending the structure or a convention — rule out non-conforming paths first, then take the plainest of what remains. Self-check: name the architecture/convention boundary the chosen approach respects; if a simpler path respects it too, take that one.
|
|
11
|
+
7. **Fix at the cause, inside the change set** — An error surfacing during your work is fixed in the code that produced it — the files you are changing and the modules they directly call. A build/deploy/test failure is NOT a license to edit configuration or manifest files (`package.json`, lockfiles, `tsconfig`, CI config, build scripts) that were already passing before this change; a regression in something previously green almost always lives in the new code, not in long-stable config. If a config/manifest edit looks unavoidable, stop and confirm with the user first — state which file, why the current code can't carry the fix, and what was passing before. *Self-check: every file edited to resolve the error is in the current change set or a module it directly calls; no previously-passing config/manifest was touched to silence the failure without the user's explicit sign-off.* *Example violation: tests fail to import a new module, so you loosen `package.json`/`tsconfig` paths instead of fixing the import in the code you just wrote.*
|
|
11
12
|
|
|
12
13
|
## Routed resource selection
|
|
13
14
|
|
|
@@ -58,6 +59,7 @@ Per-file checks miss cross-cutting issues; each commit can be individually clean
|
|
|
58
59
|
|
|
59
60
|
- [ ] **Domain-literal sweep:** `grep -rn` every domain enum value / predicate you added or touched in WHERE clauses, filters, or branches. The same literal at 2+ I/O sites is a *candidate* scattered decision — ask: would these sites change together when the business rule changes? Same decision → consolidate into one named constant or query builder in the domain layer and make every site reference it. Different decisions that merely share a value → leave them separate; coupling incidental duplication is worse than the repetition. (The identifier grep above does NOT catch this — sweep *values*, not just names.)
|
|
60
61
|
- [ ] **Stand-alone name test for exports:** for each exported identifier, look at its siblings — can a caller pick the right one from the names alone? If a comment must explain which to use, the name fails; encode the distinguishing fact in it (e.g., the input shape: `parseRows` vs `parseRowsFromFlatItems`).
|
|
62
|
+
- [ ] **Self-mock sweep:** for every test file you added or edited, `grep` it for the SUT-stubbing patterns of this language (full list in `languages/*.md` → "Self-mock signals to refuse") — e.g. `spyOn(sut`, `sut.<method> = jest.fn`, `spyk(sut`, `Mockito.spy(`, `@Spy` paired with `@InjectMocks`, `patch(`-ing the class under test, `mockall::mock!` of the unit itself, plus private-reach hacks (`(sut as any).`, `ReflectionTestUtils.invokeMethod(sut`). Any hit where the stubbed/replaced target **is the unit under test** — not an injected collaborator — is a refused self-mock: delete the stub and exercise the real method, or the test only proves its own wiring and survives even if the real implementation is deleted. Mocking injected collaborators at the boundary stays fine; this sweep targets only stubs on the SUT itself. If a method on the SUT feels too painful to leave real, that's a design signal (extract it to a collaborator), not a license to stub it.
|
|
61
63
|
- [ ] **No documented forks:** two deliberate variants of one capability must not survive as parallel implementations with a comment explaining the delta. Re-read both bodies and check the deltas are genuinely parametric: if they reduce to a few orthogonal options, collapse into one implementation taking explicit option parameters that encode them. If encoding the delta would take more than ~3 options, or add more branching than the duplication it removes, they are two capabilities — keep two implementations with distinct honest names and delete the "variant of" framing. Either way the comment-documented fork dies. "The divergence is documented" stays a refused rationalization, and a two-capabilities verdict must come from reading the bodies, not from reluctance to refactor.
|
|
62
64
|
|
|
63
65
|
## Boundaries
|
|
@@ -38,7 +38,7 @@ case "$file_path" in
|
|
|
38
38
|
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs)
|
|
39
39
|
lang="JavaScript-TypeScript"
|
|
40
40
|
ref="languages/javascript-typescript.md"
|
|
41
|
-
extra_hint=" If this file is part of a Node.js server (Express/Fastify/Nest/etc.), also read
|
|
41
|
+
extra_hint=" If this file is part of a Node.js server (Express/Fastify/Nest/etc.), also read frameworks/node-server.md."
|
|
42
42
|
;;
|
|
43
43
|
*.py)
|
|
44
44
|
lang="Python"
|
|
@@ -69,7 +69,7 @@ fi
|
|
|
69
69
|
: > "$marker"
|
|
70
70
|
|
|
71
71
|
skill_root="$HOME/.okstra/prompts/coding-preflight"
|
|
72
|
-
msg="[coding-preflight] About to edit a ${lang} file (${file_path}). Before writing, you MUST Read ${skill_root}/${ref} plus ${skill_root}/clean-code.md (the skill is user-invocable:false — read the files directly).${extra_hint} If the project uses ports-and-adapters (domain/ + ports/ + adapters/, *.port.* files), also Read ${skill_root}/
|
|
72
|
+
msg="[coding-preflight] About to edit a ${lang} file (${file_path}). Before writing, you MUST Read ${skill_root}/${ref} plus ${skill_root}/clean-code.md (the skill is user-invocable:false — read the files directly).${extra_hint} If the project uses ports-and-adapters (domain/ + ports/ + adapters/, *.port.* files), also Read ${skill_root}/architectures/hexagonal.md. (Fires once per session per language.)"
|
|
73
73
|
|
|
74
74
|
jq -nc \
|
|
75
75
|
--arg event "PreToolUse" \
|
|
@@ -648,6 +648,8 @@ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lea
|
|
|
648
648
|
|
|
649
649
|
Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
|
|
650
650
|
|
|
651
|
+
When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: upload v2 를 신규 모듈로 분리", `P-Step-1.1` → "Stage 1 Step 2: `npm run test:v2` 로 회귀 검증". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_item_subject_substance` fails a subject that is a placeholder — under 3 chars, equal to the item id, or shaped like a bare `P-*` id.
|
|
652
|
+
|
|
651
653
|
### Plan-body verdict semantics
|
|
652
654
|
|
|
653
655
|
The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their meaning is plan-specific:
|
|
@@ -677,9 +679,9 @@ When `config.adversarial == true` (the default for `implementation-planning`; se
|
|
|
677
679
|
- The burden of proof sits on the plan: an item earns `AGREE` only if the verifier actively tried to break it and could not.
|
|
678
680
|
- The verifier MUST open the file paths / symbols / commands the item cites and confirm they exist and are executable as written. This is the one allowed widening of the lightweight "judge from internal consistency and stated commands / paths" rule — confirming the existence of cited paths is not "re-analyzing the original requirements".
|
|
679
681
|
- If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–e); uncertainty resolves toward DISAGREE, not AGREE.
|
|
680
|
-
-
|
|
682
|
+
- **Single-vote-blocking kinds.** A single `DISAGREE` is approval-blocking on its own — no majority needed — when the breakage kind is `a` (cited path/symbol mismatch) or `d` (rollback violates commit/dependency order) on *any* plan item, or `f` (requirement-coverage mismatch) on a `P-Req-*` item. These defects are concrete, safety-critical, and adversarially verifiable (the verifier confirmed the cited path / order / requirement), so one correct dissent must not be outvoted. Each creates a `majority-disagree` classification and MUST become a `Blocks=approval` clarification row. Kinds `b` / `c` / `e` still need a majority — `b` especially is prone to planning-vs-implementation environment false positives. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` (`_SINGLE_VOTE_BLOCKING_KINDS = {a, d}` + the P-Req `f` rule).
|
|
681
683
|
|
|
682
|
-
Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden)
|
|
684
|
+
Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). The gate *threshold* stays majority-based for the majority-gated kinds (`b`/`c`/`e`), with the single-vote-blocking exception above for the concrete, safety-critical kinds (`a`/`d`, and `f` on P-Req). A majority requires at least two participating (non-error) votes, so a lone surviving `DISAGREE` whose peer returned a non-result does NOT block on a majority-gated kind — a worker failure must not make the gate stricter than a healthy roster would.
|
|
683
685
|
|
|
684
686
|
### Round protocol (single round at default `maxRounds=1`)
|
|
685
687
|
|
|
@@ -690,20 +692,20 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
690
692
|
- `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
|
|
691
693
|
- `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
|
|
692
694
|
- `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
|
|
693
|
-
- `majority-disagree` — majority of analysers `DISAGREE`
|
|
695
|
+
- `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes), OR any single-vote-blocking kind fires: one `DISAGREE(a)` / `DISAGREE(d)` on any item, or one `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks the Approval marker**.
|
|
694
696
|
- `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
|
|
695
697
|
5. Gate result resolution:
|
|
696
698
|
- any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
|
|
697
699
|
- all dispatches non-result → `aborted-non-result`
|
|
698
700
|
- any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
|
|
699
701
|
- all items `full-consensus` → `passed`
|
|
700
|
-
6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report (template at `templates/reports/final-report.template.md`). The §5.5.9 body
|
|
702
|
+
6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v1.0.schema.json`; template at `templates/reports/final-report.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
|
|
701
703
|
7. For every `majority-disagree` item, lead adds a row to `## 1. Clarification Items` with:
|
|
702
704
|
- new `C-<N>` ID (numbering continues from any existing rows)
|
|
703
705
|
- `Statement` summarising the disagreement and the worker breakage `<kind>`
|
|
704
706
|
- `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
|
|
705
707
|
- `Blocks=approval`
|
|
706
|
-
- the
|
|
708
|
+
- the item's `planItems[].clarificationId` set to that `C-<N>` (1:1 link). `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item's `clarificationId` is missing, dangling, or points at a non-`approval` row.
|
|
707
709
|
8. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
|
|
708
710
|
|
|
709
711
|
### `plan-body-verification-<task-type>-<seq>.json` schema
|
|
@@ -720,6 +722,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
720
722
|
"planItems": [
|
|
721
723
|
{
|
|
722
724
|
"id": "P-Opt-1",
|
|
725
|
+
"subject": "Option A: upload v2 를 신규 모듈로 분리",
|
|
723
726
|
"sourceSection": "4.5.1",
|
|
724
727
|
"ticketId": "<id-or-unknown>",
|
|
725
728
|
"votes": {"claude-worker": "AGREE", "codex-worker": "AGREE"},
|
|
@@ -728,6 +731,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
728
731
|
},
|
|
729
732
|
{
|
|
730
733
|
"id": "P-Step-3",
|
|
734
|
+
"subject": "Stage 2 Step 3: 마이그레이션 실행 후 회귀 테스트",
|
|
731
735
|
"sourceSection": "4.5.4",
|
|
732
736
|
"ticketId": "TICKET-123",
|
|
733
737
|
"votes": {"claude-worker": "DISAGREE(a)", "codex-worker": "DISAGREE(a)"},
|