okstra 0.90.0 → 0.91.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/kr/architecture.md +2 -1
- package/docs/project-structure-overview.md +3 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/codex-worker.md +3 -3
- package/runtime/bin/okstra-codex-exec.sh +13 -1
- package/runtime/prompts/lead/report-writer.md +9 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +2 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/python/okstra_ctl/report_views.py +6 -0
- package/runtime/python/okstra_ctl/wizard.py +19 -12
- package/runtime/schemas/final-report-v1.0.schema.json +14 -0
- package/runtime/skills/okstra-run/SKILL.md +1 -0
- package/runtime/templates/reports/final-report.template.md +12 -0
- package/runtime/templates/reports/i18n/en.json +8 -0
- package/runtime/templates/reports/i18n/ko.json +8 -0
- package/runtime/validators/validate-run.py +40 -0
package/docs/kr/architecture.md
CHANGED
|
@@ -894,6 +894,7 @@ resume 판단 기준:
|
|
|
894
894
|
Claude가 작성하는 최종 보고서는 아래 구조를 우선 사용합니다 (brief 의 augmentation 이 더 구체적인 형식을 요구할 때만 그것을 따릅니다).
|
|
895
895
|
|
|
896
896
|
- `## Verdict Card` — **최상단 의무 섹션**. Final Conclusion / Verdict Token / Direction / Approval Required? / Next Step 5 행. Verdict Token / Direction / Next Step 셀은 본문 §2 (실행 현황) 와 §6 (다음 단계) 의 권위 셀과 byte-match 해야 합니다.
|
|
897
|
+
- `## 작업 배경과 근거` — **모든 task-type 의무 섹션** (data.json `rationale`). 검토자용 서술로 네 질문에 순서대로 답합니다: 왜 이 작업을 하는가(`motivation`) / 왜 이게 문제인가(`problem`) / 그래서 어떤 작업이 필요한가(`approach`) / 왜 이게 합리적 선택인가(`justification`). 표가 아니라 프로즈이며, 각 필드는 `path:line` · 보고서 ID(`C-001`) · `§5.4` 같은 증거 참조나 명시적 불충분 표시(`근거 불충분`) 중 하나를 반드시 포함해야 합니다 — `validators/validate-run.py` 의 `_validate_rationale_evidence` 가 둘 다 없는 필드를 fail 합니다.
|
|
897
898
|
- (선택) `## 0. Clarification Response Carried In From Previous Run` — 직전 run 에서 응답이 carry-in 된 경우에만 렌더링. 빈 carry-in 일 때는 헤딩 자체를 출력하지 않습니다.
|
|
898
899
|
- `## 1. 문제 또는 검증 대상 요약` — §6.1 Consensus / §6.2 Differences 표 각각 `Source items (worker:item)` 컬럼 보존 (cross-worker traceability).
|
|
899
900
|
- `## 2. 에이전트별 실행 현황`
|
|
@@ -977,7 +978,7 @@ Phase 7 step 1.5 가 final-report MD 한 본을 입력으로 두 view 를 결정
|
|
|
977
978
|
phase 산출물의 출고 가능 여부를 강제하는 진입점:
|
|
978
979
|
|
|
979
980
|
- `validators/validate-workflow.sh` — phase contract 통합 검증.
|
|
980
|
-
- `validators/validate-run.py` — run-level final-report 본문 contract (Verdict Card 존재, deprecated §6.1/§6.2/§5.5.8/§5.5.9 Open Questions 부재, Plan Body Verification gate × Approval 마커 cross-check, Token Usage sentinel/zero 차단, 워커-결과 audit 사이드카 존재).
|
|
981
|
+
- `validators/validate-run.py` — run-level final-report 본문 contract (Verdict Card 존재, `rationale` 4개 필드의 증거-앵커 강제(`_validate_rationale_evidence`), deprecated §6.1/§6.2/§5.5.8/§5.5.9 Open Questions 부재, Plan Body Verification gate × Approval 마커 cross-check, Token Usage sentinel/zero 차단, 워커-결과 audit 사이드카 존재).
|
|
981
982
|
- `validators/validate-report-views.py` — slim MD / HTML view 의 phase substring 보존 및 form-control 영역 검사.
|
|
982
983
|
- `validators/validate-brief.py` — brief schema (front-matter, `Reporter Confirmations` 섹션 존재, root parent-id self 규칙, slug 컨벤션 등) 강제. `bash validators/validate-brief.sh <brief.md>` 가 thin wrapper.
|
|
983
984
|
|
|
@@ -179,7 +179,8 @@ Top-level scripts:
|
|
|
179
179
|
| `okstra-render-report-views.py` | Render slim MD + HTML views from final-report Markdown |
|
|
180
180
|
| `okstra-error-log.py` | Normalize worker/lead error sidecars |
|
|
181
181
|
| `okstra-spawn-followups.py` | Follow-up spawning helper |
|
|
182
|
-
| `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + trace, lead pane 제외) |
|
|
182
|
+
| `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + trace, lead pane 제외); `--reclaim-completed` 모드는 `@okstra_status` 가 종료(stage=exited)인 trace pane 만 회수하고 진행 중 pane 은 보존 |
|
|
183
|
+
| `okstra-subagent-reclaim.sh` | 활성 run 을 순회하며 완료된 trace pane 만 회수하는 엔트리 (`SubagentStop`/`TaskCompleted` 훅 배선) |
|
|
183
184
|
|
|
184
185
|
### 4.3 `scripts/okstra_ctl/` — Python orchestration core
|
|
185
186
|
|
|
@@ -204,6 +205,7 @@ Important modules:
|
|
|
204
205
|
| `wizard.py` | `okstra-run` prompt state machine; user-facing Korean strings live in `prompts/wizard/prompts.ko.json` |
|
|
205
206
|
| `index.py`, `jsonl.py`, `reconcile.py`, `listing.py`, `batch.py`, `backfill.py` | `~/.okstra` run index and history operations |
|
|
206
207
|
| `session.py`, `tmux.py`, `seeding.py`, `locks.py`, `invocation.py`, `sequence.py`, `ids.py`, `material.py` | Supporting lifecycle helpers |
|
|
208
|
+
| `pane_reclaim.py` | 완료 trace pane 회수 대상 판정; 진행-중 status 집합을 `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT 에서 import |
|
|
207
209
|
| `improvement_lenses.py` | improvement-discovery phase 의 lens enum SSOT + cap 상수 (DEFAULT 8, ABSOLUTE 12, MIN/MAX PRIORITY 1/4, SOURCE_WORKERS) |
|
|
208
210
|
|
|
209
211
|
### 4.4 `scripts/okstra_project/`
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -36,12 +36,12 @@ The fourth argument is **mandatory for implementation phase** and optional other
|
|
|
36
36
|
|
|
37
37
|
The wrapper internally runs:
|
|
38
38
|
```bash
|
|
39
|
-
codex exec -C "<project-root>" [--add-dir "<worktree-path>"] --model "<model>" --sandbox workspace-write - < "<prompt-path>" 2>/dev/null
|
|
39
|
+
codex exec -C "<project-root>" [--add-dir "<worktree-path>"] --model "<model>" --sandbox workspace-write -c approval_policy=never - < "<prompt-path>" 2>/dev/null
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
The wrapper exists because Claude Code's Bash permission matcher rejects simple-prefix matches when the command contains stdin/stderr redirects. Calling `codex exec ... < <path> 2>/dev/null` directly triggers a permission prompt every dispatch even when `Bash(codex exec:*)` is allowlisted. The wrapper folds the redirects inside, so the harness sees a single non-redirect command that matches `Bash($HOME/.okstra/bin/okstra-codex-exec.sh:*)`.
|
|
43
43
|
|
|
44
|
-
**Do NOT use** non-existent
|
|
44
|
+
**Do NOT use** the non-existent `-q` flag. The approval policy MUST be set with `-c approval_policy=never` (the `-a`/`--ask-for-approval` flag is NOT accepted by `codex exec` — it errors with `unexpected argument '-a'`); without `approval_policy=never` codex runs under the default `on-request` policy and, having no TTY to answer an approval prompt, ends the turn in a few seconds with exit 0 and no result file. **Do NOT** invoke `codex exec ... < ... 2>/dev/null` directly — always go through the wrapper.
|
|
45
45
|
|
|
46
46
|
## Execution Rules
|
|
47
47
|
|
|
@@ -77,7 +77,7 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
|
|
|
77
77
|
```bash
|
|
78
78
|
$HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>"
|
|
79
79
|
```
|
|
80
|
-
Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, the stdin redirect from the prompt file, and stderr suppression internally. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
|
|
80
|
+
Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, `-c approval_policy=never` (non-interactive: never block on an approval prompt the TTY-less dispatch cannot answer), the stdin redirect from the prompt file, and stderr suppression internally. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
|
|
81
81
|
|
|
82
82
|
**Poll loop (BashOutput-only, 30-minute cap):**
|
|
83
83
|
- Record `start_ts` at dispatch time via a single `Bash` call: `date +%s` (output captured).
|
|
@@ -296,7 +296,19 @@ fi
|
|
|
296
296
|
# stderr: appended to the live log only — mirrors the prior `2>/dev/null`
|
|
297
297
|
# contract of keeping the wrapper's stderr stream clean.
|
|
298
298
|
# exit: codex's own exit code is preserved by `wait`.
|
|
299
|
-
|
|
299
|
+
#
|
|
300
|
+
# approval_policy=never: this wrapper runs codex fully non-interactively — stdin
|
|
301
|
+
# is the prompt file (EOF after it) and stdout/stderr are pipes (process
|
|
302
|
+
# substitution), not a TTY. Under codex's default `on-request` policy the agent
|
|
303
|
+
# BLOCKS on an approval prompt the instant it wants to act outside the
|
|
304
|
+
# workspace-write sandbox (e.g. read a sibling repo the prompt references); with
|
|
305
|
+
# no TTY to answer, codex ends the turn after a few seconds with exit 0 and NO
|
|
306
|
+
# model output — the silent empty-result failure this wrapper otherwise reports
|
|
307
|
+
# as a spurious success. `never` returns sandbox-escalation failures to the
|
|
308
|
+
# model instead of prompting, so the turn proceeds. The sandbox stays
|
|
309
|
+
# `workspace-write`: this removes the approval gate, not the sandbox. It is the
|
|
310
|
+
# codex-side equivalent of the antigravity wrapper's `--dangerously-skip-permissions`.
|
|
311
|
+
codex exec -C "$project_root" ${extra_args[@]+"${extra_args[@]}"} --model "$model" --sandbox workspace-write -c approval_policy=never - \
|
|
300
312
|
< "$prompt_path" \
|
|
301
313
|
2>> "$log_path" \
|
|
302
314
|
> >(tee -a "$log_path") &
|
|
@@ -268,6 +268,14 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
268
268
|
|
|
269
269
|
**Verdict Card (top-of-report, mandatory).** Render `## Verdict Card` between the report header and the (conditional) Approval block. Its `Verdict Token` / `Direction` / `Next Step` cells MUST byte-match the corresponding cells in `## 7. Final Verdict` and the first item of `## 6.`. Divergence is `contract-violated`.
|
|
270
270
|
|
|
271
|
+
**Background and Rationale (top-of-report, mandatory — every task-type).** Fill the data.json `rationale` object (rendered as `## 작업 배경과 근거`, right after the Verdict Card). It is the reviewer-facing narrative that answers four questions, in order — write each as **prose**, not a table:
|
|
272
|
+
- `motivation` — 왜 이 작업을 하는가 (목표·맥락).
|
|
273
|
+
- `problem` — 왜 이게 문제인가 (현재 상태의 결함).
|
|
274
|
+
- `approach` — 그래서 어떤 작업이 필요한가 (택한 방향).
|
|
275
|
+
- `justification` — 왜 이게 합리적 선택인가 (대안 대비, 근거).
|
|
276
|
+
|
|
277
|
+
Every field MUST anchor its claim with at least one evidence reference — a `path:line`, a report ID (`C-001` / `F-013` / `§5.4`), or another cited source already present in this report. When the evidence is genuinely insufficient, say so explicitly (`근거 불충분 — <이유>`) instead of inventing one. `validators/validate-run.py` → `_validate_rationale_evidence` fails any field carrying neither a citation nor an insufficiency marker, so an ungrounded or fabricated rationale is rejected at validation time. Do NOT restate the Verdict Card or the §5.4 trade-off matrix verbatim — this section is the *why*, in connected prose, that those tables compress.
|
|
278
|
+
|
|
271
279
|
0. **Clarification Response Carried In** — render this `## 0.` heading ONLY when `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty. Walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile against new evidence, and record the outcome (`resolved` / `obsolete`) with citation before drafting the verdict. When no carry-in path was provided, OMIT the `## 0.` heading entirely — the validator fails an empty Section 0 stub.
|
|
272
280
|
1. **Cross Verification Results** — 4 categories (Full / Partial / Contested / Worker-Unique) when convergence is enabled, per `convergence`. Prepend the Round History sub-table (columns: `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches | skippedWorkers`) plus a `round2SkippedReason: <value>` note, pulled verbatim from `convergence-<task-type>-<seq>.json`. Empty contested list renders as `- 합의 미달 항목 없음.`. Convergence-disabled runs use the legacy Consensus/Differences format and omit the round table.
|
|
273
281
|
2. **Final Verdict** — `Direction` ∈ `continue-investigation` / `begin-implementation` / `approve` / `reject` / `hold`. **Verdict Token** is `not-applicable` for every task-type except `final-verification` — see "Final-verification verdict token contract" below for that case.
|
|
@@ -281,7 +289,7 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
281
289
|
|
|
282
290
|
### Writing Guidelines
|
|
283
291
|
|
|
284
|
-
- Write in Markdown. **Prefer tables over prose bullet lists** for any section that enumerates multiple items with the same shape (evidence rows, risks, options, dependencies, rollback steps, follow-ups, open questions). Bullets are reserved for short, single-line standalone statements (e.g., "- 추가 정보 요청 없음."). When the template provides a table form, do NOT degrade it back to bullets in the rendered report.
|
|
292
|
+
- Write in Markdown. **Prefer tables over prose bullet lists** for any section that enumerates multiple items with the same shape (evidence rows, risks, options, dependencies, rollback steps, follow-ups, open questions). Bullets are reserved for short, single-line standalone statements (e.g., "- 추가 정보 요청 없음."). When the template provides a table form, do NOT degrade it back to bullets in the rendered report. **Exception — `## 작업 배경과 근거` (`rationale`) is deliberately prose**: it is connected narrative explaining the *why*, not a same-shape enumeration, so write full sentences there rather than forcing it into a table.
|
|
285
293
|
- Write the final report body in the language passed in **Report Language**
|
|
286
294
|
above (`en` or `ko`). The template's fixed labels (section asides,
|
|
287
295
|
empty-states, token summary, column headers, release-handoff labels)
|
|
@@ -103,7 +103,7 @@ profile document.
|
|
|
103
103
|
- each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
|
|
104
104
|
- each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
|
|
105
105
|
- write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (예/아니오, 보기 중 하나, 숫자/날짜, 파일 경로, 짧은 서술 등); supply concrete option choices when applicable.
|
|
106
|
-
- if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: <answer> — <rationale>; Alternatives: <options>`. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
106
|
+
- if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: <answer> — <rationale>; Alternatives: <options>`. When there is more than one alternative, enumerate them as `(a) … (b) …` so each renders as its own selectable option; a single alternative may be written plainly. Do **not** append a pick-one answer-space summary such as `(A / B 중 택1)` or `(… 중 택N)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
107
107
|
- the same `final-report.md` file is the canonical artifact carried into the next run; the user appends answers inline before rerunning. The preferred turn-around is `scripts/okstra.sh --resume-clarification --task-key <project-id>:<task-group>:<task-id>` (opens the latest report in `$EDITOR`, then auto-reruns the same phase with `--clarification-response` carry-in). The lower-level form `--clarification-response <path>` remains available for scripted runs.
|
|
108
108
|
- if a clarification response was carried in for this run, render the conditional `## 0. Clarification Response Carried In From Previous Run` section (the template's `RENDER_IF` guard activates it), walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile each one against new evidence, and update its `Status` to `resolved` or `obsolete` before issuing the next decision/verdict. When no carry-in path was provided, omit the `## 0.` heading entirely — the validator fails reports that emit an empty Section 0 stub (e.g. "No prior clarification response was provided for this run.").
|
|
109
109
|
- Verdict Card (shared — applies to every final-report regardless of profile):
|
|
@@ -59,7 +59,8 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
|
|
|
59
59
|
- read-only inspection commands: `git status`, `git diff`, `git log`, `grep`, `rg`, `find`, `cat`, `ls`, file Read tools
|
|
60
60
|
- build, lint, type-check, and test commands (`npm test`, `pytest`, `go build`, `cargo test`, `bash -n`, etc.)
|
|
61
61
|
- **local git operations only**: `git add`, `git commit`. Prefer small commits keyed to plan steps.
|
|
62
|
-
- **
|
|
62
|
+
- **Explicit-path staging only (BLOCKING).** Stage exactly the files a plan step touches by naming each path — `git add <path1> <path2>`. Never use the bulk forms `git add .`, `git add -A`, `git add -u`, or `git add --all`: they sweep in unrelated edits, stray build output, and editor scratch files. Never use `git add -f` (force) under any circumstance — `-f` is the *only* way a `.gitignore`d path reaches a commit, and a forced ignored file is always a defect. If `git add <path>` reports the path is ignored, that file does not belong in the commit; do not override it — leave it untracked.
|
|
63
|
+
- **No ignored / okstra files in commits (BLOCKING).** Before every `git commit`, run `git diff --cached --name-only` and pipe the staged paths through `git check-ignore --stdin --no-index`; abort the commit if it reports **any** match — a staged path that `.gitignore` excludes means a bulk-add or `-f` leaked it in. The check explicitly covers `.okstra/` (and `.project-docs/` when the legacy symlink is present): `.okstra/**` is gitignored, and force-staging it onto the stage branch is the one way these verification artifacts reach the upstream PR. Conformance/qa evidence belongs in the carry sidecar and verifier result — committing it is never correct, even when a step's instructions seem to ask for it.
|
|
63
64
|
- **Commit message format (mandatory)**: every commit message MUST follow Conventional Commits — `<type>(<scope>): <subject>` for the first line, optional body separated by a blank line, optional footer. Constraints:
|
|
64
65
|
- `<type>` MUST be one of: `feat` / `fix` / `perf` / `revert` / `deps` / `docs` / `refactor` / `build` / `ci` / `chore` / `test`. When the repo is `release-please`-managed, this aligns the commit with a configured changelog section.
|
|
65
66
|
- `<scope>` SHOULD be the plan step identifier or the primary module touched (e.g. `feat(report-writer): ...`). Omit the parentheses only when no meaningful scope applies.
|
|
@@ -97,7 +97,7 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
|
|
|
97
97
|
- **Tautological delegation assertion:** a test asserts the SUT result equals a direct call to the same pure helper/collaborator that the SUT delegates to, instead of asserting an independent literal value or observable state.
|
|
98
98
|
- **Untruthful name:** a read-named function (`get*` / `find*` / `load*`) that writes/inserts/mutates; an adapter or repository name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*` / `findActive*`).
|
|
99
99
|
- **Hexagonal (only when the overlay is loaded):** business logic inside a port body; an adapter method that is not pure I/O (post-fetch JS filtering on domain state, domain-rule evaluation); a domain object declared outside the `domain/` boundary.
|
|
100
|
-
- **
|
|
100
|
+
- **gitignored file committed to the branch:** any path in the `git diff --name-only <base>...HEAD` enumeration that `.gitignore` excludes — enumerate them by piping that list through `git check-ignore --stdin --no-index`. A committed ignored file means the executor bulk-added (`git add .`/`-A`) or force-staged (`git add -f`) it, leaking build output, scratch files, or verification artifacts into the eventual PR. This explicitly includes `.okstra/` paths (and `.project-docs/` when the legacy symlink is present): `.okstra/**` is gitignored, so a committed okstra file (qa scripts, conformance results) is always this defect. Cite each path; recommend `git rm --cached <path>` to untrack it while keeping the file on disk. Conformance/qa evidence belongs in the carry sidecar / verifier result, never in git history.
|
|
101
101
|
- **Real-IO test in source tree:** a changed/added test under the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*` — that opens a **real** DB connection / DSN, makes a real `fetch` / `axios` / `http` request, or otherwise hits real external IO without mocking the injected collaborator (a live handle, not a stub/spy). Real-IO tests MUST live under `<task_root>/qa/scripts/` per the executor's *Real-IO test isolation* rule — a live-IO test in source silently breaks the project's CI suite and violates the artifact-home rule. Cite the test file + the real-IO line; recommend moving it to `<task_root>/qa/scripts/` (or declaring it as a Tier 3 conformance script). Mock-only unit tests in source are NOT a hit.
|
|
102
102
|
- **Advisory findings (recorded as recommendations; verdict MAY still PASS):** function >50 effective lines, a single body mixing read+write stages, weak readability, a missing-but-non-critical outcome assertion, newly orphaned private/public code that is safe to remove but not on a critical path, or weak-but-not-misleading names. These land in the verifier result as `should-fix` / `nit` recommendations, not as a `FAIL`.
|
|
103
103
|
- **Output.** Every finding — blocking or advisory — is a structured item in the verifier's worker result (`path:line`, rule, severity, suggested fix) so it carries into Phase 5.5 convergence and the final report. A blocking hit sets the verifier verdict to `FAIL` with the rule cited, using the same verdict machinery as the Discrepancy rule above. `Claude lead` MUST NOT silently downgrade a cited blocking finding to advisory during synthesis; an override requires a concrete cited reason, exactly as for the Discrepancy rule.
|
|
@@ -490,6 +490,11 @@ def _parse_enum_options(statement: str) -> list[tuple[str, str]]:
|
|
|
490
490
|
|
|
491
491
|
_RECOMMENDED_CUE = "Recommended:"
|
|
492
492
|
_ALTERNATIVES_CUE = "Alternatives:"
|
|
493
|
+
# Authors sometimes append a pick-one answer-space summary like
|
|
494
|
+
# "(A / B 중 택1)" to the Expected form. The rendered <select> already
|
|
495
|
+
# enforces single choice, so this annotation must never reach an option
|
|
496
|
+
# label — strip a trailing parenthetical that contains "택<n>".
|
|
497
|
+
_PICK_ONE_ANNOTATION = re.compile(r"\s*\([^()]*택\s*\d+\s*\)\s*$")
|
|
493
498
|
|
|
494
499
|
|
|
495
500
|
def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
@@ -500,6 +505,7 @@ def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
|
500
505
|
``Recommended:`` cue — the caller falls back to the statement enum."""
|
|
501
506
|
if not expected_form:
|
|
502
507
|
return []
|
|
508
|
+
expected_form = _PICK_ONE_ANNOTATION.sub("", expected_form)
|
|
503
509
|
rec_idx = expected_form.find(_RECOMMENDED_CUE)
|
|
504
510
|
if rec_idx < 0:
|
|
505
511
|
return []
|
|
@@ -1928,24 +1928,19 @@ def _submit_handoff_stage_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
1928
1928
|
|
|
1929
1929
|
|
|
1930
1930
|
def _suggest_latest_final_report(state: WizardState) -> str:
|
|
1931
|
-
"""
|
|
1931
|
+
"""clarification carry-in 으로 추천할 직전 final-report 의 relpath.
|
|
1932
1932
|
|
|
1933
|
-
|
|
1933
|
+
resume-clarification 은 "같은 phase 를 답변과 함께 재실행" 이므로 재실행
|
|
1934
|
+
task-type **자신의** 보고서(``runs/<task-type>/reports/final-report-*.md``)를
|
|
1935
|
+
우선한다. 그 phase 에 보고서가 없을 때만(예: 다음 phase 로 진행) 전체 phase 의
|
|
1936
|
+
mtime 최신으로 폴백한다 — 과거엔 무조건 전체 mtime 최신이라 더 최근에 재렌더된
|
|
1937
|
+
다른 phase 보고서를 잘못 집을 수 있었다. 못 찾으면 빈 문자열.
|
|
1934
1938
|
"""
|
|
1935
1939
|
if not state.task_group or not state.task_id or not state.project_root:
|
|
1936
1940
|
return ""
|
|
1937
1941
|
runs_base = task_runs_dir(state.project_root, state.task_group, state.task_id)
|
|
1938
1942
|
if not runs_base.is_dir():
|
|
1939
1943
|
return ""
|
|
1940
|
-
# run 산출물 구조는 runs/<task-type>/reports/final-report-*.md (1단계).
|
|
1941
|
-
# 과거 `*/*/reports/...` (2단계) glob 은 실제 구조와 어긋나 항상 빈 결과여서
|
|
1942
|
-
# clarification 단계가 직전 final-report 를 추천하지 못했다.
|
|
1943
|
-
candidates = [
|
|
1944
|
-
p for p in runs_base.glob("*/reports/final-report-*.md")
|
|
1945
|
-
if p.is_file()
|
|
1946
|
-
]
|
|
1947
|
-
if not candidates:
|
|
1948
|
-
return ""
|
|
1949
1944
|
|
|
1950
1945
|
def _mtime_safe(p: Path) -> float:
|
|
1951
1946
|
try:
|
|
@@ -1953,7 +1948,19 @@ def _suggest_latest_final_report(state: WizardState) -> str:
|
|
|
1953
1948
|
except OSError:
|
|
1954
1949
|
return -1.0
|
|
1955
1950
|
|
|
1956
|
-
|
|
1951
|
+
def _newest(glob_pattern: str) -> Optional[Path]:
|
|
1952
|
+
cands = [p for p in runs_base.glob(glob_pattern) if p.is_file()]
|
|
1953
|
+
return max(cands, key=_mtime_safe) if cands else None
|
|
1954
|
+
|
|
1955
|
+
# 산출물 구조는 runs/<task-type>/reports/final-report-*.md (1단계).
|
|
1956
|
+
best: Optional[Path] = None
|
|
1957
|
+
if state.task_type:
|
|
1958
|
+
seg = slugify_task_segment(state.task_type)
|
|
1959
|
+
best = _newest(f"{seg}/reports/final-report-*.md")
|
|
1960
|
+
if best is None:
|
|
1961
|
+
best = _newest("*/reports/final-report-*.md")
|
|
1962
|
+
if best is None:
|
|
1963
|
+
return ""
|
|
1957
1964
|
try:
|
|
1958
1965
|
return str(best.relative_to(Path(state.project_root)))
|
|
1959
1966
|
except ValueError:
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"frontmatter",
|
|
11
11
|
"header",
|
|
12
12
|
"verdictCard",
|
|
13
|
+
"rationale",
|
|
13
14
|
"summary",
|
|
14
15
|
"executionStatus",
|
|
15
16
|
"tokenUsage",
|
|
@@ -153,6 +154,19 @@
|
|
|
153
154
|
}
|
|
154
155
|
},
|
|
155
156
|
|
|
157
|
+
"rationale": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"description": "## 작업 배경과 근거 — reviewer-facing narrative answering the four questions in order. Each field is prose (not a table) and MUST carry at least one evidence reference (path:line, a report ID such as C-001/F-013/§5.4, or another cited source) OR an explicit insufficiency marker (e.g. '근거 불충분'). validate-run.py fails a field with neither. Required for every task-type.",
|
|
160
|
+
"required": ["motivation", "problem", "approach", "justification"],
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"properties": {
|
|
163
|
+
"motivation": { "type": "string", "minLength": 1, "description": "왜 이 작업을 하는가 — 목표·맥락." },
|
|
164
|
+
"problem": { "type": "string", "minLength": 1, "description": "왜 이게 문제인가 — 현재 상태의 결함을 증거로." },
|
|
165
|
+
"approach": { "type": "string", "minLength": 1, "description": "그래서 어떤 작업이 필요한가 — 택한 방향." },
|
|
166
|
+
"justification": { "type": "string", "minLength": 1, "description": "왜 이게 합리적 선택인가 — 대안 대비 근거." }
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
156
170
|
"summary": {
|
|
157
171
|
"type": "array",
|
|
158
172
|
"description": "## Summary of the Problem or Verification Target. 3-5 rows.",
|
|
@@ -127,6 +127,7 @@ That is the entire interactive flow. The wizard handles:
|
|
|
127
127
|
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = 의존성 충족된 가장 빠른 미완료 stage, 또는 특정 stage 번호) + executor pick. approved-plan 선택 시 그 run 의 sibling `user-responses/` 에서 plan 과 source-report·seq 가 일치하는, 보고서에서 내보낸 `## APPROVAL` sidecar 를 감지하면 approve-confirm 단계가 3-옵션(`yes_apply` 내보낸 기록대로 승인+옵션 적용 추천 / `yes` 승인만 / `no` 중단)으로 확장된다 — `yes_apply` 는 옵션을 plan 의 `optionCandidates` 에 대해 검증한 뒤 기존 승인·옵션 경로로 적용한다,
|
|
128
128
|
- `release-handoff`-only sub-flow: approved plan 자동 해소 후 `handoff_stage_pick` 멀티선택 — eligible stage 묶음(stage-group) 또는 전체 task(accepted whole-task 검증 보고서 존재 시) 선택; 결과는 render-args 의 `stages` 키(csv, whole-task 면 빈 값)로 나간다,
|
|
129
129
|
- `Use defaults / Customize` branch with profile-aware worker/model questions,
|
|
130
|
+
- **resume-clarification (in-session 등가)** — 셸의 `okstra.sh --resume-clarification` 에 대응하는 별도 모드나 플래그는 없고, 표준 흐름의 두 단계가 그 실질을 수행한다. (1) `reuse_previous` (직전 run 설정 재사용 예/아니오 — `requirements-discovery` / `error-analysis` / `implementation-planning` 에서, 직전 run-inputs 가 있을 때만): YES 면 워커·모델·directive·related-tasks 를 한 번에 prefill 한다. (2) `clarification_pick`: 재실행하는 **task-type 자신의** 직전 `final-report` 가 있으면 그것을 carry-in 입력으로 자동 추천하고(없으면 전체 phase 중 mtime 최신으로 폴백), 같은 run 의 `user-responses/` 사이드카(사용자가 채운 답변)를 함께 첨부한다. 선택된 경로는 prepare 의 `--clarification-response` 로 전달된다 — 사용자는 보고서의 `Export user response` 로 사이드카를 만들어 `runs/<task-type>/user-responses/` 에 둔 뒤 같은 phase 를 다시 실행하면 된다,
|
|
130
131
|
- `release-handoff` PR template override + persist scope,
|
|
131
132
|
- final `Proceed / Edit` confirmation; on `Edit` the wizard asks which step to rewind to and clears every later answer.
|
|
132
133
|
|
|
@@ -67,6 +67,18 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
|
|
|
67
67
|
| Approval Required? | `{% if verdictCard.approvalRequired %}yes — {{ t("verdictCard.approvalRequiredSuffix") }}{% else %}no{% endif %}` |
|
|
68
68
|
| Next Step | {{ verdictCard.nextStep | mdcell }} |
|
|
69
69
|
|
|
70
|
+
## {{ t("rationale.heading") }}
|
|
71
|
+
|
|
72
|
+
{{ t("rationale.intro") }}
|
|
73
|
+
|
|
74
|
+
**{{ t("rationale.motivationLabel") }}** — {{ rationale.motivation }}
|
|
75
|
+
|
|
76
|
+
**{{ t("rationale.problemLabel") }}** — {{ rationale.problem }}
|
|
77
|
+
|
|
78
|
+
**{{ t("rationale.approachLabel") }}** — {{ rationale.approach }}
|
|
79
|
+
|
|
80
|
+
**{{ t("rationale.justificationLabel") }}** — {{ rationale.justification }}
|
|
81
|
+
|
|
70
82
|
## 1. Clarification Items
|
|
71
83
|
|
|
72
84
|
{{ t("sectionIntro.clarificationItems") }}
|
|
@@ -62,6 +62,14 @@
|
|
|
62
62
|
"rationaleLabel": "Rationale summary",
|
|
63
63
|
"nextStepLabel": "Next step"
|
|
64
64
|
},
|
|
65
|
+
"rationale": {
|
|
66
|
+
"heading": "Background and Rationale",
|
|
67
|
+
"intro": "Why this work, what it changes, and why the chosen approach is sound — written so a reviewer can follow it. Each item cites its evidence (path:line, report ID, etc.).",
|
|
68
|
+
"motivationLabel": "Why this work",
|
|
69
|
+
"problemLabel": "Why it is a problem",
|
|
70
|
+
"approachLabel": "What work is needed",
|
|
71
|
+
"justificationLabel": "Why this is the reasonable choice"
|
|
72
|
+
},
|
|
65
73
|
"ticketCoverage": {
|
|
66
74
|
"intro": "The tickets this run covered, with the sections and related items where each ticket appears.",
|
|
67
75
|
"columnSections": "Sections",
|
|
@@ -62,6 +62,14 @@
|
|
|
62
62
|
"rationaleLabel": "근거 요약",
|
|
63
63
|
"nextStepLabel": "다음 단계"
|
|
64
64
|
},
|
|
65
|
+
"rationale": {
|
|
66
|
+
"heading": "작업 배경과 근거",
|
|
67
|
+
"intro": "이 작업을 왜·무엇을·왜 그렇게 하는지 검토자가 따라갈 수 있도록 정리한다. 각 항목은 근거(파일:줄, 보고서 ID 등)를 인용한다.",
|
|
68
|
+
"motivationLabel": "왜 이 작업을 하는가",
|
|
69
|
+
"problemLabel": "왜 이게 문제인가",
|
|
70
|
+
"approachLabel": "그래서 어떤 작업이 필요한가",
|
|
71
|
+
"justificationLabel": "왜 이게 합리적 선택인가"
|
|
72
|
+
},
|
|
65
73
|
"ticketCoverage": {
|
|
66
74
|
"intro": "이 run 이 다룬 ticket 과, 각 ticket 이 등장한 섹션·관련 항목 목록입니다.",
|
|
67
75
|
"columnSections": "등장 섹션",
|
|
@@ -1462,10 +1462,50 @@ def validate_final_report_data(report_path: Path, failures: list[str]) -> None:
|
|
|
1462
1462
|
for err in errors:
|
|
1463
1463
|
failures.append(f"final-report data.json: {err}")
|
|
1464
1464
|
|
|
1465
|
+
_validate_rationale_evidence(data, failures)
|
|
1466
|
+
|
|
1465
1467
|
if (data.get("header") or {}).get("taskType") == "final-verification":
|
|
1466
1468
|
_validate_final_verification_consistency(data, failures)
|
|
1467
1469
|
|
|
1468
1470
|
|
|
1471
|
+
# path:line (foo.service.ts:268), report ID (C-001 / F-013 / G-crit-002 / RC-1),
|
|
1472
|
+
# or a section reference (§5.4) — any one satisfies "this claim is anchored".
|
|
1473
|
+
_EVIDENCE_TOKEN = re.compile(
|
|
1474
|
+
r"[\w./-]+\.\w+:\d+|\b[A-Z]{1,3}(?:-[a-z]+)?-\d+\b|§\s*\d")
|
|
1475
|
+
# Explicit "I don't know" escapes — anti-fabrication's other valid answer.
|
|
1476
|
+
_INSUFFICIENCY_MARKERS = (
|
|
1477
|
+
"근거 불충분", "근거가 불충분", "증거 불충분", "증거가 불충분",
|
|
1478
|
+
"증거 없음", "증거가 없", "모름", "알 수 없", "확인 불가",
|
|
1479
|
+
"i don't know", "unknown", "insufficient evidence", "no evidence",
|
|
1480
|
+
)
|
|
1481
|
+
_RATIONALE_FIELDS = ("motivation", "problem", "approach", "justification")
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
def _validate_rationale_evidence(data: dict, failures: list[str]) -> None:
|
|
1485
|
+
"""Every `## 작업 배경과 근거` field must anchor its claim: carry at least
|
|
1486
|
+
one evidence reference (path:line, report ID, §section) OR an explicit
|
|
1487
|
+
insufficiency marker. Neither present → unverifiable narrative, which is
|
|
1488
|
+
the fabrication the section exists to prevent. Schema guarantees the
|
|
1489
|
+
fields are present and non-empty; this enforces they are *grounded*."""
|
|
1490
|
+
rationale = data.get("rationale")
|
|
1491
|
+
if not isinstance(rationale, dict):
|
|
1492
|
+
return # absence/shape is the schema's job; don't double-report.
|
|
1493
|
+
for field in _RATIONALE_FIELDS:
|
|
1494
|
+
text = rationale.get(field)
|
|
1495
|
+
if not isinstance(text, str):
|
|
1496
|
+
continue
|
|
1497
|
+
if _EVIDENCE_TOKEN.search(text):
|
|
1498
|
+
continue
|
|
1499
|
+
if any(m in text.lower() for m in _INSUFFICIENCY_MARKERS):
|
|
1500
|
+
continue
|
|
1501
|
+
failures.append(
|
|
1502
|
+
f"final-report data.json: rationale.{field} cites no evidence "
|
|
1503
|
+
f"(expected a path:line, a report ID like C-001/F-013, or a §"
|
|
1504
|
+
f"section reference) and gives no explicit insufficiency marker "
|
|
1505
|
+
f"(e.g. '근거 불충분'). Anchor the claim or state what is unknown."
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
|
|
1469
1509
|
def _validate_final_verification_consistency(data: dict, failures: list[str]) -> None:
|
|
1470
1510
|
"""Enforce verdict ↔ blocker/condition/routing consistency on the
|
|
1471
1511
|
final-verification data.json (SSOT). The schema guarantees field SHAPE;
|