okstra 0.74.0 → 0.76.0

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.
@@ -0,0 +1,108 @@
1
+ # Stage discipline 규칙 — 선행 stage 동작 동결 설계
2
+
3
+ - 날짜: 2026-06-12
4
+ - 상태: 설계 승인됨 (구현 전)
5
+ - 대상: `implementation` · `implementation-planning` 프로파일
6
+
7
+ ## 1. 배경과 목표
8
+
9
+ implementation 은 stage-isolated 다 — 한 run = 한 stage 이고, dependent stage 는 선행(`depends-on`) stage 의 done commit 을 base 로 시작한다(`prompts/profiles/implementation.md:27`). 선행 stage 는 이미 conformance 를 통과해 `done` 으로 닫힌 상태인데, 이후 stage 가 그 코드의 **동작을 바꾸면** "verified-stays-verified" 불변식이 깨진다 — 닫힌 stage 가 조용히 회귀한다.
10
+
11
+ 목표: "선행 stage 가 추가·수정한 코드의 기능(동작)은 이후 stage 에서 변경하지 않는다" 라는 규율을 `implementation` 과 `implementation-planning` 양쪽에 도입하고, 이런 stage 규율을 **별도 단일 파일**로 관리한다.
12
+
13
+ ## 2. 결정 사항 (brainstorming 합의)
14
+
15
+ | 항목 | 결정 |
16
+ |---|---|
17
+ | 규칙 파일 소유권 | okstra 내장 공유 파일 (모든 프로젝트 적용, okstra 레포에서 편집·전파) |
18
+ | 강제 수준 | 선언 + 보고서 증거 (validator/wrapper sentinel 없음) |
19
+ | 규칙 언어 | English (기존 Forbidden actions·stage-scope 규칙과 일관) |
20
+ | executor 전사 포인터 | 둔다 (CLI executor 가 규칙을 받도록) |
21
+
22
+ YAGNI 로 제외: validator/wrapper 강제, data.json 스키마 필드, 프로젝트별 override.
23
+
24
+ ## 3. 아키텍처 근거
25
+
26
+ - 프로파일은 `{{INCLUDE:<name>}}` 로 공유 계약을 합친다 — resolver 는 임의 파일을 지원한다(`scripts/okstra_ctl/run.py:973` `_expand_profile_includes`, 재귀 depth ≤4, 누락 시 `PrepareError` fail-fast). 추가 코드 변경 없이 새 사이드카를 INCLUDE 할 수 있다.
27
+ - `implementation-planning` 의 행위자는 lead/planner 본인이라 INCLUDE 로 직접 바인딩된다.
28
+ - `implementation` 의 실제 행위자인 **executor(codex/gemini CLI)는 lead 컨텍스트를 공유하지 않는다**. 기존 계약은 lead 가 규칙을 executor 프롬프트에 **verbatim 전사**하게 한다(`prompts/profiles/_implementation-executor.md:27` "CLI executor transcription"). 따라서 implementation 규칙이 CLI executor 에 닿으려면 전사 포인터가 필요하다.
29
+ - executor 사이드카(`_implementation-executor.md`)는 lead 가 Phase 5 에 raw 로 lazy-load 하며 `{{INCLUDE}}` resolver 를 거치지 않는다(`_implementation-executor.md:3`). 그래서 사이드카에는 규칙 **텍스트를 복사하지 않고** 단일 출처를 가리키는 **전사 지시 한 줄**만 둔다(DRY).
30
+
31
+ ## 4. 설계
32
+
33
+ ### 4.1 신규 단일 파일 `prompts/profiles/_stage-discipline.md` (SSOT)
34
+
35
+ 규칙의 유일한 출처. 핵심 원칙 + phase 별 적용 + 보고서 증거 절을 담는다. 전체 본문:
36
+
37
+ ```markdown
38
+ <!--
39
+ Shared stage-discipline rule. INCLUDEd by implementation-planning.md (binds the
40
+ planner) and implementation.md (binds the lead). The implementation executor
41
+ sidecar instructs the lead to transcribe this rule into a CLI executor prompt,
42
+ since codex/gemini executors do not share lead context.
43
+
44
+ Do NOT write the literal include directive token in this file's body — the
45
+ resolver matches it anywhere and would recurse on this file itself.
46
+ -->
47
+
48
+ ## Stage discipline — preceding stages are behavior-frozen
49
+
50
+ A stage that is already `done` (a `depends-on` predecessor whose work is merged
51
+ into this run's base) is **behavior-frozen**: code it added or modified keeps its
52
+ existing behavior. Later stages MAY call, extend, or compose with that code, but
53
+ MUST NOT change what it already does. This preserves the verified-stays-verified
54
+ invariant — a completed, conformance-passed stage must not silently regress when
55
+ a later stage runs.
56
+
57
+ - **implementation-planning** — decompose stages forward-only. No stage's scope
58
+ may require altering an earlier stage's behavior; each stage builds additively
59
+ on its predecessors' Stage Exit Contracts. If delivering an increment would
60
+ force changing an earlier stage's behavior, that earlier stage was mis-scoped —
61
+ fold the change into that stage or re-partition; do not plan a later stage that
62
+ rewrites it.
63
+ - **implementation** — the executor MUST NOT alter the behavior of any file added
64
+ or modified by a preceding done stage. Touching such a file to *extend* it (a
65
+ new branch, a new caller, an additive field) is allowed; changing its existing
66
+ behavior is not. If a plan step appears to require a behavior change to
67
+ prior-stage code, treat it as a planning defect: record the finding and route
68
+ to a new `implementation-planning` run — never silently rewrite it.
69
+ - **Report evidence (declaration-level).** When this stage modifies a file that
70
+ a preceding stage added or modified, the final report MUST record — in the
71
+ deliverable's existing edits / validation evidence section — which file, why
72
+ it was touched, and how the prior stage's behavior was preserved. Absent that
73
+ note, a reviewer treats a prior-stage edit as an unverified regression risk.
74
+ ```
75
+
76
+ ### 4.2 INCLUDE 배선
77
+
78
+ - `prompts/profiles/implementation.md`: 기존 `{{INCLUDE:_common-contract.md}}`(line 17) 바로 다음 줄에 `{{INCLUDE:_stage-discipline.md}}` 추가.
79
+ - `prompts/profiles/implementation-planning.md`: 기존 `{{INCLUDE:_common-contract.md}}`(line 10) 바로 다음 줄에 `{{INCLUDE:_stage-discipline.md}}` 추가.
80
+
81
+ ### 4.3 executor 전사 포인터
82
+
83
+ `prompts/profiles/_implementation-executor.md` 의 기존 전사 규칙(`:27` "CLI executor transcription") 근처에 형제 bullet 한 줄 추가 (규칙 텍스트 복사 없음):
84
+
85
+ ```markdown
86
+ - **Stage discipline transcription (when a preceding stage is `done`):** the lead
87
+ MUST transcribe the `Stage discipline` rule (from this run's rendered profile)
88
+ verbatim into the dispatched CLI executor prompt, so a codex/gemini executor
89
+ honors the prior-stage behavior-freeze. Declaration-level — no wrapper sentinel.
90
+ ```
91
+
92
+ ### 4.4 전파
93
+
94
+ `tools/build.mjs` 가 `prompts/` → `runtime/` 를 동기화하고 install 이 사용자 머신으로 복사한다. INCLUDE resolver 가 임의 파일을 이미 지원하므로 추가 코드 변경은 없다.
95
+
96
+ ## 5. 테스트
97
+
98
+ `tests/test_okstra_profile_includes.py` 확장:
99
+ - `implementation.md` 렌더 결과에 `Stage discipline — preceding stages are behavior-frozen` 헤딩과 `behavior-frozen` 본문이 INCLUDE 로 들어오는지 단언.
100
+ - `implementation-planning.md` 렌더 결과에 같은 규칙 텍스트가 들어오는지 단언.
101
+ - `_stage-discipline.md` 파일 존재 + `implementation` / `implementation-planning` 두 프로파일에 INCLUDE 디렉티브가 있는지 단언 (executor 사이드카에는 전사 포인터 문자열이 있는지 단언).
102
+
103
+ (누락 INCLUDE 는 `_expand_profile_includes` 가 이미 `PrepareError` 로 fail-fast.)
104
+
105
+ ## 6. 영향받지 않는 것
106
+
107
+ - 다른 phase(`requirements-discovery` / `error-analysis` / `final-verification` / `release-handoff` / `improvement-discovery`)는 무변경 — 규칙은 두 프로파일에만 INCLUDE 된다.
108
+ - data.json 스키마, validator, CLI 플래그, wizard 무변경.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.74.0",
3
- "builtAt": "2026-06-11T18:11:19.238Z",
2
+ "package": "0.76.0",
3
+ "builtAt": "2026-06-11T20:04:51.254Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -135,7 +135,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Cod
135
135
  - Treat the prompt-history path as the canonical worker prompt history artifact for the current run, resolved to absolute against `Project Root` if given as relative.
136
136
  - The assigned model execution value is canonical for CLI execution. Do not substitute a different Codex model unless the task bundle explicitly changes it.
137
137
  - Pass the prompt received from Lead directly to codex after persisting the exact prompt to the assigned path.
138
- - **Executor preflight forwarding check (implementation runs only).** When the lead prompt assigns this dispatch the `Executor` role for an `implementation` run, the persisted prompt body MUST contain the literal heading `Coding-conventions preflight` (transcribed by the lead from `prompts/profiles/_implementation-executor.md` → "Pre-implementation context exploration") — the Codex CLI does not share the lead's context, so an untranscribed gate never reaches the process that writes the code. If the heading is absent, return `CODEX_PREFLIGHT_MISSING: executor dispatch prompt lacks the coding-conventions preflight block` instead of invoking the CLI; the lead is responsible for re-dispatching with the block included. This check does NOT apply to verifier or analysis dispatches.
138
+ - **Executor preflight forwarding check (implementation runs only).** When the lead prompt assigns this dispatch the `Executor` role for an `implementation` run, the persisted prompt body MUST contain the literal heading `Coding-conventions preflight` (the lead appends the body of `prompts/profiles/_coding-conventions-preflight.md` into the dispatch prompt — see `prompts/profiles/_implementation-executor.md` → "Pre-implementation context exploration") — the Codex CLI does not share the lead's context, so an unforwarded gate never reaches the process that writes the code. If the heading is absent, return `CODEX_PREFLIGHT_MISSING: executor dispatch prompt lacks the coding-conventions preflight block` instead of invoking the CLI; the lead is responsible for re-dispatching with the block included. This check does NOT apply to verifier or analysis dispatches.
139
139
  - Include context (code, diff, file paths) if provided.
140
140
  - For long prompts, dispatch through the wrapper with literal absolute paths (plus the worktree path for implementation phase):
141
141
  ```bash
@@ -135,7 +135,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Gem
135
135
  - Treat the prompt-history path as the canonical worker prompt history artifact for the current run, resolved to absolute against `Project Root` if given as relative.
136
136
  - The assigned model execution value is canonical for CLI execution. Do not substitute a different Gemini model unless the task bundle explicitly changes it.
137
137
  - Pass the prompt received from Lead directly to gemini after persisting the exact prompt to the assigned path.
138
- - **Executor preflight forwarding check (implementation runs only).** When the lead prompt assigns this dispatch the `Executor` role for an `implementation` run, the persisted prompt body MUST contain the literal heading `Coding-conventions preflight` (transcribed by the lead from `prompts/profiles/_implementation-executor.md` → "Pre-implementation context exploration") — the Gemini CLI does not share the lead's context, so an untranscribed gate never reaches the process that writes the code. If the heading is absent, return `GEMINI_PREFLIGHT_MISSING: executor dispatch prompt lacks the coding-conventions preflight block` instead of invoking the CLI; the lead is responsible for re-dispatching with the block included. This check does NOT apply to verifier or analysis dispatches.
138
+ - **Executor preflight forwarding check (implementation runs only).** When the lead prompt assigns this dispatch the `Executor` role for an `implementation` run, the persisted prompt body MUST contain the literal heading `Coding-conventions preflight` (the lead appends the body of `prompts/profiles/_coding-conventions-preflight.md` into the dispatch prompt — see `prompts/profiles/_implementation-executor.md` → "Pre-implementation context exploration") — the Gemini CLI does not share the lead's context, so an unforwarded gate never reaches the process that writes the code. If the heading is absent, return `GEMINI_PREFLIGHT_MISSING: executor dispatch prompt lacks the coding-conventions preflight block` instead of invoking the CLI; the lead is responsible for re-dispatching with the block included. This check does NOT apply to verifier or analysis dispatches.
139
139
  - Include context (code, diff, file paths) if provided.
140
140
  - For long prompts, dispatch through the wrapper with literal absolute paths (plus the worktree path for implementation phase):
141
141
  ```bash
@@ -0,0 +1,21 @@
1
+ <!--
2
+ Single source for the executor's Coding-conventions preflight gate. Two delivery
3
+ paths converge here (see _implementation-executor.md "Pre-implementation context
4
+ exploration"):
5
+ - Claude executor reads this file directly before its first Edit / Write.
6
+ - codex / gemini executor cannot read this path (it sits outside the CLI
7
+ sandbox and the CLI only sees its stdin prompt), so the lead appends this
8
+ file's body into the persisted executor prompt at dispatch time.
9
+ The `Coding-conventions preflight` heading below is the literal string the CLI
10
+ wrapper's "Executor preflight forwarding check" greps for in the persisted
11
+ prompt (agents/workers/_cli-wrapper-template.md → Prompt Composition).
12
+ -->
13
+
14
+ # Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop)
15
+
16
+ Load the applicable coding conventions for every language the diff will touch, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`). Lint/test green is necessary but NOT sufficient — self-mocked tests, interaction-only assertions, and untruthful names all pass a green pipeline; this gate is what keeps them out of the diff.
17
+
18
+ - **Language-specific rules load per situation — never inline them here.** Detect each touched file's language (extension / project manifest) and load the matching reference by reading okstra's installed coding-conventions files directly at `~/.claude/skills/okstra-coding-preflight/` (placed there by `okstra install`): read `languages/<lang>.md` (mock/spy API, idioms, test framework) + `clean-code.md` + any `architecture/*` overlay via the Read tool by absolute path. The skill is `user-invocable: false`, so do NOT rely on Skill-tool auto-invocation — read the files directly. For a ports-and-adapters / NestJS-hex layout (`domain/` + `ports/` + `adapters/`, `*.port.*`), load the hexagonal overlay too. This per-language split is the skill's job — the executor does not carry a multi-language block in context.
19
+ - **Project review rule packs:** also look for project-local review skills in `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`. Read the relevant `SKILL.md` plus referenced `references/*.md` files and apply their rules during implementation. This is a prevention pass, not a PR-comment generation workflow: do not dispatch reviewer subagents from the executor. For Fonts Ninja-style PR review packs, the executor must avoid newly introduced duplicate helper stacks, tautological tests that merely re-call the delegated helper, self-mocking, domain rules in adapters/ports, domain objects outside `domain/`, dead APIs, weak public names, and functions that fail the plain-English read.
20
+ - **Language-agnostic principles that ALWAYS bind (the TDD loop MUST satisfy them):** (1) no self-mocking of the SUT — stub/spy only injected collaborators, never the subject's own methods; (2) behavioral assertions on outcomes (return value, state, persisted rows, events, boundary calls) — never `toHaveBeenCalled*` on an internal helper as the only/primary assertion; (3) truthful names — a `get*` / `find*` that writes/inserts, or a name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*`), is a defect; (4) single-purpose functions ≤50 effective lines, plain-English readability.
21
+ - **Graceful degradation (codex / gemini executor runtimes, or any runtime where the `~/.claude/skills/okstra-coding-preflight/` files are absent or unreadable):** do NOT skip the gate — apply the agnostic principles above plus the project's own `CLAUDE.md` / `CONTRIBUTING` / formatter+lint config, and record `coding-conventions: skill-unavailable → applied <project rules + agnostic principles>` in the final report. Never claim a skill read that did not happen.
@@ -19,12 +19,10 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
19
19
 
20
20
  ## Pre-implementation context exploration (executor before first edit)
21
21
 
22
- - **Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop below):** load the applicable coding conventions for every language the diff will touch, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`). Lint/test green is necessary but NOT sufficient — self-mocked tests, interaction-only assertions, and untruthful names all pass a green pipeline; this gate is what keeps them out of the diff.
23
- - **Language-specific rules load per situation — never inline them here.** Detect each touched file's language (extension / project manifest) and load the matching reference by reading okstra's installed coding-conventions files directly at `~/.claude/skills/okstra-coding-preflight/` (placed there by `okstra install`): read `languages/<lang>.md` (mock/spy API, idioms, test framework) + `clean-code.md` + any `architecture/*` overlay via the Read tool by absolute path. The skill is `user-invocable: false`, so do NOT rely on Skill-tool auto-invocation read the files directly. For a ports-and-adapters / NestJS-hex layout (`domain/` + `ports/` + `adapters/`, `*.port.*`), load the hexagonal overlay too. This per-language split is the skill's job — the executor does not carry a multi-language block in context.
24
- - **Project review rule packs:** also look for project-local review skills in `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`. Read the relevant `SKILL.md` plus referenced `references/*.md` files and apply their rules during implementation. This is a prevention pass, not a PR-comment generation workflow: do not dispatch reviewer subagents from the executor. For Fonts Ninja-style PR review packs, the executor must avoid newly introduced duplicate helper stacks, tautological tests that merely re-call the delegated helper, self-mocking, domain rules in adapters/ports, domain objects outside `domain/`, dead APIs, weak public names, and functions that fail the plain-English read.
25
- - **Language-agnostic principles that ALWAYS bind (the TDD loop below MUST satisfy them):** (1) no self-mocking of the SUT stub/spy only injected collaborators, never the subject's own methods; (2) behavioral assertions on outcomes (return value, state, persisted rows, events, boundary calls) never `toHaveBeenCalled*` on an internal helper as the only/primary assertion; (3) truthful names a `get*` / `find*` that writes/inserts, or a name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*`), is a defect; (4) single-purpose functions ≤50 effective lines, plain-English readability.
26
- - **Graceful degradation (codex / gemini executor runtimes, or any runtime where the `~/.claude/skills/okstra-coding-preflight/` files are absent or unreadable):** do NOT skip the gate — apply the agnostic principles above plus the project's own `CLAUDE.md` / `CONTRIBUTING` / formatter+lint config, and record `coding-conventions: skill-unavailable → applied <project rules + agnostic principles>` in the final report. Never claim a skill read that did not happen.
27
- - **CLI executor transcription (BLOCKING when the executor provider is `codex` or `gemini`):** the executor CLI process does NOT share the lead's context — a gate that stays in lead memory never reaches it. The lead MUST copy this entire "Coding-conventions preflight" bullet tree (file-read instructions, project review rule packs, agnostic principles, graceful degradation) verbatim into the dispatched executor prompt body. Enforcement: the CLI wrapper agents refuse an implementation-Executor dispatch whose persisted prompt lacks the literal heading `Coding-conventions preflight`, returning `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` (see `agents/workers/_cli-wrapper-template.md` → Prompt Composition).
22
+ - **Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop below).** The gate body is a single source at `prompts/profiles/_coding-conventions-preflight.md` (sibling of this sidecar): skill-file pointers (`~/.claude/skills/okstra-coding-preflight/`), project review rule packs, the always-binding language-agnostic principles, and graceful degradation. Do NOT re-type that content from memory deliver it by file so it cannot drift or be dropped:
23
+ - **Claude executor:** Read `_coding-conventions-preflight.md` end-to-end before the first `Edit` / `Write`, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`).
24
+ - **CLI executor (BLOCKING when the executor provider is `codex` or `gemini`):** the executor CLI process does NOT share the lead's context, and it cannot Read this sidecar's directory that path sits outside the CLI sandbox and the CLI only sees its stdin prompt, so a file reference never reaches it. The lead MUST physically append the **body** of `_coding-conventions-preflight.md` into the persisted executor prompt at dispatch time: Read the file from the same absolute directory you read this sidecar from, then `Write`/`cat` its body into the persisted prompt. Never hand-retype it. Enforcement: the CLI wrapper agents refuse an implementation-Executor dispatch whose persisted prompt lacks the literal heading `Coding-conventions preflight`, returning `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` (see `agents/workers/_cli-wrapper-template.md` → Prompt Composition).
25
+ - **Stage discipline transcription (when a preceding stage is `done`):** the lead MUST transcribe the `Stage discipline` rule (from this run's rendered profile the INCLUDEd `_stage-discipline.md` body) verbatim into the dispatched CLI executor prompt, so a codex/gemini executor honors the prior-stage behavior-freeze. Declaration-level no wrapper sentinel.
28
26
  - **Non-interactive auto-execution (BLOCKING when the executor provider is `codex` or `gemini`).** A CLI executor runs head-less (`codex exec` / gemini equivalent) — there is no human at the keyboard. Skills loaded during the run (tdd, coding-preflight, and others) contain "get user approval", "state your plan to the user and wait", or "ask before proceeding" gates written for interactive sessions; in this run those gates are **already satisfied** by the upstream `implementation-planning` approval (the plan this stage executes was human-approved). The executor MUST NOT stop to request approval, MUST NOT end its turn after only producing a plan, and MUST carry the stage through end-to-end — RED → GREEN → refactor → per-step commits → `### Stage Carry Evidence`. The ONLY skill step to skip is the interactive user-approval prompt itself; every other skill rule (TDD discipline, conventions, real-IO isolation) still binds. The lead MUST transcribe this bullet verbatim into the dispatched CLI executor prompt (same reason as the preflight transcription rule above — the CLI process does not share lead context). Stopping early for approval in a head-less run is the observed empty-exit failure (exit 0, no diff): treat it as `contract-violated`.
29
27
  - **Mandatory TDD loop**: BEFORE the first `Edit` or `Write` call, the executor MUST apply a red-green-refactor loop for every code change in this run. This is required; skipping it is a `contract-violated` outcome. This governs HOW each step is executed (failing test first → minimal implementation → refactor); it does not override the approved plan's WHAT/file scope.
30
28
  - Order of operations per plan step: (1) write/extend the test that captures the step's acceptance criterion and confirm it fails for the right reason, (2) commit the failing test (`test(<scope>): ...`), (3) implement the minimum change to make it pass, (4) commit the implementation (`feat|fix(<scope>): ...`), (5) refactor without changing behaviour and commit separately if any cleanup is made (`refactor(<scope>): ...`). The failing-then-passing transition between steps (2) and (4) is the `TDD evidence` required by the final report.
@@ -0,0 +1,36 @@
1
+ <!--
2
+ Shared stage-discipline rule. INCLUDEd by implementation-planning.md (binds the
3
+ planner) and implementation.md (binds the lead). The implementation executor
4
+ sidecar instructs the lead to transcribe this rule into a CLI executor prompt,
5
+ since codex/gemini executors do not share lead context.
6
+
7
+ Do NOT write the literal include directive token in this file's body — the
8
+ resolver matches it anywhere and would recurse on this file itself.
9
+ -->
10
+
11
+ ## Stage discipline — preceding stages are behavior-frozen
12
+
13
+ A stage that is already `done` (a `depends-on` predecessor whose work is merged
14
+ into this run's base) is **behavior-frozen**: code it added or modified keeps its
15
+ existing behavior. Later stages MAY call, extend, or compose with that code, but
16
+ MUST NOT change what it already does. This preserves the verified-stays-verified
17
+ invariant — a completed, conformance-passed stage must not silently regress when
18
+ a later stage runs.
19
+
20
+ - **implementation-planning** — decompose stages forward-only. No stage's scope
21
+ may require altering an earlier stage's behavior; each stage builds additively
22
+ on its predecessors' Stage Exit Contracts. If delivering an increment would
23
+ force changing an earlier stage's behavior, that earlier stage was mis-scoped —
24
+ fold the change into that stage or re-partition; do not plan a later stage that
25
+ rewrites it.
26
+ - **implementation** — the executor MUST NOT alter the behavior of any file added
27
+ or modified by a preceding done stage. Touching such a file to *extend* it (a
28
+ new branch, a new caller, an additive field) is allowed; changing its existing
29
+ behavior is not. If a plan step appears to require a behavior change to
30
+ prior-stage code, treat it as a planning defect: record the finding and route
31
+ to a new `implementation-planning` run — never silently rewrite it.
32
+ - **Report evidence (declaration-level).** When this stage modifies a file that
33
+ a preceding stage added or modified, the final report MUST record — in the
34
+ deliverable's existing edits / validation evidence section — which file, why
35
+ it was touched, and how the prior stage's behavior was preserved. Absent that
36
+ note, a reviewer treats a prior-stage edit as an unverified regression risk.
@@ -8,6 +8,7 @@
8
8
  - Optional workers (opt-in via `--workers`):
9
9
  - gemini — when added to the roster it joins the analyser set; omitted by default
10
10
  {{INCLUDE:_common-contract.md}}
11
+ {{INCLUDE:_stage-discipline.md}}
11
12
  - Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
12
13
  - Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows use `Blocks=approval`; the operator cannot flip the approval frontmatter to `approved: true` until those rows are resolved.
13
14
  - never plan around an unconfirmed `intent-inference` augmentation as if it were a settled requirement. After the precondition runs, a `[CONFIRMED …]` marker on the matching `intent-check:` row is the signal that the inference can be treated as settled; otherwise it remains a `Blocks=approval` clarification item per the precondition's `skipped` branch.
@@ -15,6 +15,7 @@
15
15
  - Executor model: `{{EXECUTOR_MODEL_DISPLAY}}` (launch value: `{{EXECUTOR_MODEL_EXECUTION_VALUE}}`)
16
16
  - Wherever this profile mentions the `Executor`, it refers to the role bound above. The other two providers in the roster (`claude` / `codex` / `gemini` minus the executor) are dispatched as **verifiers only** for this run and remain strictly read-only.
17
17
  {{INCLUDE:_common-contract.md}}
18
+ {{INCLUDE:_stage-discipline.md}}
18
19
  - Pre-implementation gate (mandatory — refuse to start if any item fails):
19
20
  - the run brief MUST cite `--approved-plan <path>` pointing to a `final-report.md` produced by a prior `implementation-planning` run located under `runs/implementation-planning/.../reports/final-report.md`
20
21
  - that file's YAML frontmatter MUST carry `approved: true`. report-writer emits `approved: false` by default; the user flips it to `true` to authorise this run. Free-form approvals such as "lgtm" / "go ahead" / paraphrased confirmations are NOT accepted; re-edit the plan file's frontmatter to `approved: true` before invoking implementation, or pass `--approve` so the CLI flips it on the user's behalf (`okstra_ctl.run._apply_cli_approval`).
@@ -196,6 +196,7 @@
196
196
  },
197
197
  "approved_plan_pick": {
198
198
  "label": "approved final-report 경로 (기본: {default})",
199
+ "label_final_verification": "검증 기준이 될 승인된 plan(approved final-report) 을 선택하세요 (기본: {default})",
199
200
  "echo_template": "approved-plan(pick): {value}",
200
201
  "options": {
201
202
  "__use_default__": "기본 경로 사용: {default}",
@@ -216,12 +217,17 @@
216
217
  "echo_template": "approved-plan: {value}"
217
218
  },
218
219
  "approve_plan_confirm": {
219
- "label": "이 플랜으로 진행할까요?\n {path}\n· 예 — 진행합니다. 플랜이 아직 승인 전이면 지금 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 진행합니다. (markdown 만 손으로 고치면 일관성 검증에서 거부되므로 이 경로로 승인하세요.)\n· 아니오 — 진행하지 않습니다.",
220
+ "label": "이 플랜으로 구현을 진행할까요?\n {path}\n· 예 — plan 대로 구현을 실행합니다. 플랜이 아직 승인 전이면 지금 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 진행합니다. (markdown 만 손으로 고치면 일관성 검증에서 거부되므로 이 경로로 승인하세요.)\n· 아니오 — 진행하지 않습니다.",
221
+ "label_final_verification": "이 plan 을 검증 기준으로 삼아 진행할까요?\n {path}\n· 예 — 선택한 stage 의 구현 결과를 위 plan 기준으로 검증합니다. (plan 이 아직 승인 전이면 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 검증합니다.)\n· 아니오 — 진행하지 않습니다.",
220
222
  "html_approval_note": "\nHTML 승인 기록 발견: {sidecar}\n 선택 옵션: {option}\n· 예(기록 적용) — 승인과 함께 위 옵션을 frontmatter 에 기록합니다.",
221
223
  "html_approval_note_default_option": "(추천 옵션 그대로)",
222
224
  "echo_template": "approve-plan: {value}",
223
225
  "options": {
224
- "yes": "예 — 승인하고 진행",
226
+ "yes": "예 — 승인하고 구현 진행",
227
+ "no": "아니오 — 진행하지 않음"
228
+ },
229
+ "options_final_verification": {
230
+ "yes": "예 — 이 plan 기준으로 검증 진행",
225
231
  "no": "아니오 — 진행하지 않음"
226
232
  },
227
233
  "options_html_approval": {
@@ -230,8 +236,10 @@
230
236
  "no": "아니오 — 진행하지 않음"
231
237
  },
232
238
  "echo_variants": {
233
- "selected": "plan 선택: {path} — 다음 단계에서 승인·진행 여부를 확인합니다",
234
- "approved": "approved-plan: {path} (승인·진행 확인됨)",
239
+ "selected": "plan 선택: {path} — 다음 단계에서 승인·구현 진행 여부를 확인합니다",
240
+ "selected_final_verification": "검증 기준 plan 선택: {path} 다음 단계에서 검증 진행 여부를 확인합니다",
241
+ "approved": "approved-plan: {path} (승인·구현 진행 확인됨)",
242
+ "approved_final_verification": "검증 기준 plan 확정: {path} (검증 진행 확인됨)",
235
243
  "approved_with_option": "approved-plan: {path} (승인 + 옵션 `{option}` 적용됨)"
236
244
  },
237
245
  "errors": {
@@ -635,7 +635,12 @@ def _stage_plan_for_confirmation(
635
635
  state.html_approval_sidecar = str(sidecar_path)
636
636
  state.html_approval_option = record.implementation_option
637
637
  t = _p(state.workspace_root, "approve_plan_confirm", path=str(p))
638
- msg = t["echo_variants"]["selected"].format(path=p)
638
+ variants = t["echo_variants"]
639
+ key = ("selected_final_verification"
640
+ if state.task_type == "final-verification"
641
+ and variants.get("selected_final_verification")
642
+ else "selected")
643
+ msg = variants[key].format(path=p)
639
644
  return f"{msg} {suffix}".rstrip() if suffix else msg
640
645
 
641
646
 
@@ -770,8 +775,10 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
770
775
  if raw is None:
771
776
  raise WizardError(f"unknown wizard step_id: {step_id!r}")
772
777
  label_template = raw.get("label", "")
778
+ fv_label_template = raw.get("label_final_verification", "")
773
779
  try:
774
780
  label = label_template.format(**vars)
781
+ label_final_verification = fv_label_template.format(**vars)
775
782
  except KeyError as exc:
776
783
  missing = exc.args[0] if exc.args else "<unknown>"
777
784
  raise WizardError(
@@ -779,8 +786,10 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
779
786
  ) from exc
780
787
  return {
781
788
  "label": label,
789
+ "label_final_verification": label_final_verification,
782
790
  "echo_template": raw.get("echo_template", ""),
783
791
  "options": raw.get("options", {}),
792
+ "options_final_verification": raw.get("options_final_verification", {}),
784
793
  "options_html_approval": raw.get("options_html_approval", {}),
785
794
  "html_approval_note": raw.get("html_approval_note", ""),
786
795
  "html_approval_note_default_option": raw.get(
@@ -1626,6 +1635,8 @@ def _build_approved_plan_pick(state: WizardState) -> Prompt:
1626
1635
  default = reports[0] if reports else None
1627
1636
  t = _p(state.workspace_root, "approved_plan_pick",
1628
1637
  default=str(default) if default is not None else "")
1638
+ is_fv = state.task_type == "final-verification"
1639
+ label = (t["label_final_verification"] or t["label"]) if is_fv else t["label"]
1629
1640
  other_report_label = t["labels"]["other_report"]
1630
1641
  options: list[Option] = []
1631
1642
  if default is not None:
@@ -1637,7 +1648,7 @@ def _build_approved_plan_pick(state: WizardState) -> Prompt:
1637
1648
  options.append(_opt(PICK_OTHER, t["options"][PICK_OTHER]))
1638
1649
  return Prompt(
1639
1650
  step=S_APPROVED_PLAN_PICK, kind="pick",
1640
- label=t["label"], options=options,
1651
+ label=label, options=options,
1641
1652
  echo_template=t["echo_template"],
1642
1653
  )
1643
1654
 
@@ -1679,8 +1690,9 @@ def _submit_approved_plan(state: WizardState, value: str) -> Optional[str]:
1679
1690
  def _build_approve_plan_confirm(state: WizardState) -> Prompt:
1680
1691
  t = _p(state.workspace_root, "approve_plan_confirm",
1681
1692
  path=state.approve_plan_candidate)
1682
- label = t["label"]
1683
- options_map = t["options"]
1693
+ is_fv = state.task_type == "final-verification"
1694
+ label = (t["label_final_verification"] or t["label"]) if is_fv else t["label"]
1695
+ options_map = (t["options_final_verification"] or t["options"]) if is_fv else t["options"]
1684
1696
  if state.html_approval_sidecar:
1685
1697
  label += t["html_approval_note"].format(
1686
1698
  sidecar=state.html_approval_sidecar,
@@ -1728,7 +1740,12 @@ def _submit_approve_plan_confirm(state: WizardState, value: str) -> Optional[str
1728
1740
  if not fully_approved:
1729
1741
  raise WizardError(
1730
1742
  t["errors"]["still_unapproved"].format(path=resolved))
1731
- echo = t["echo_variants"]["approved"].format(path=resolved)
1743
+ variants = t["echo_variants"]
1744
+ approved_key = ("approved_final_verification"
1745
+ if state.task_type == "final-verification"
1746
+ and variants.get("approved_final_verification")
1747
+ else "approved")
1748
+ echo = variants[approved_key].format(path=resolved)
1732
1749
  if apply_option:
1733
1750
  _apply_cli_implementation_option(
1734
1751
  str(resolved), state.html_approval_option)
@@ -1748,10 +1765,7 @@ def _build_stage_pick(state: WizardState) -> Prompt:
1748
1765
  t = _p(state.workspace_root, "stage_pick")
1749
1766
  stages = _parse_stage_objects(state)
1750
1767
  is_fv = state.task_type == "final-verification"
1751
- label = (
1752
- t.get("label_final_verification", t["label"])
1753
- if is_fv else t["label"]
1754
- )
1768
+ label = (t["label_final_verification"] or t["label"]) if is_fv else t["label"]
1755
1769
  done = _done_stage_numbers(state) if is_fv else set()
1756
1770
  options = []
1757
1771
  if _stage_auto_allowed(state):
package/src/install.mjs CHANGED
@@ -167,6 +167,34 @@ async function copyTreeIfChanged(srcRoot, dstRoot, opts) {
167
167
  return { copied, skipped, missingSource };
168
168
  }
169
169
 
170
+ // Single-file variant of copyTreeIfChanged, used for the lead `okstra` skill
171
+ // whose source is one file (agents/SKILL.md) rather than a skill directory.
172
+ async function copyFileIfChanged(src, dst, opts) {
173
+ const { refresh = false, dryRun = false, mode } = opts ?? {};
174
+ let srcBuf;
175
+ try {
176
+ srcBuf = await fs.readFile(src);
177
+ } catch {
178
+ return { copied: 0, skipped: 0, missingSource: true };
179
+ }
180
+ let needsCopy = refresh;
181
+ if (!needsCopy) {
182
+ try {
183
+ const [srcHash, dstHash] = await Promise.all([hashFile(src), hashFile(dst)]);
184
+ needsCopy = srcHash !== dstHash;
185
+ } catch {
186
+ needsCopy = true;
187
+ }
188
+ }
189
+ if (!needsCopy) return { copied: 0, skipped: 1, missingSource: false };
190
+ if (dryRun) {
191
+ process.stdout.write(`[dry-run] copy ${src} -> ${dst}\n`);
192
+ return { copied: 1, skipped: 0, missingSource: false };
193
+ }
194
+ await writeFileAtomic(dst, srcBuf, mode);
195
+ return { copied: 1, skipped: 0, missingSource: false };
196
+ }
197
+
170
198
  async function ensureSymlink(target, linkPath, opts) {
171
199
  const { dryRun = false } = opts ?? {};
172
200
  try {
@@ -244,7 +272,8 @@ async function installLinkMode(repoPath, paths, opts) {
244
272
  }
245
273
 
246
274
  const skillResult = await installSkillsLink(repoAbs, { dryRun, quiet });
247
- await writeSkillsManifest(paths.home, skillResult.installed, { dryRun });
275
+ const leadSkillResult = await installLeadSkillLink(repoAbs, { dryRun, quiet });
276
+ await writeSkillsManifest(paths.home, [...skillResult.installed, ...leadSkillResult], { dryRun });
248
277
 
249
278
  const agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
250
279
  await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
@@ -487,6 +516,43 @@ async function installSkillsLink(repoAbs, opts) {
487
516
  return { installed: names };
488
517
  }
489
518
 
519
+ // The lead `okstra` skill source is agents/SKILL.md (frontmatter `name: okstra`);
520
+ // it doubles as the lead agent contract, so it lives under agents/ rather than
521
+ // skills/. Claude Code only discovers skills at ~/.claude/skills/<name>/SKILL.md,
522
+ // so it must be installed there as its own skill dir — otherwise install ships
523
+ // every okstra-* helper skill but not the lead skill, and edits to agents/SKILL.md
524
+ // never reach the running lead.
525
+ const LEAD_SKILL_NAME = "okstra";
526
+
527
+ async function installLeadSkillCopy(runtimeRoot, opts) {
528
+ const { refresh, dryRun, quiet } = opts;
529
+ const src = join(runtimeRoot, "agents", "SKILL.md");
530
+ const dst = join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME, "SKILL.md");
531
+ const r = await copyFileIfChanged(src, dst, { refresh, dryRun, mode: 0o644 });
532
+ if (r.missingSource) {
533
+ if (!quiet) process.stdout.write(" skills/okstra: runtime/agents/SKILL.md missing — skipped\n");
534
+ return [];
535
+ }
536
+ if (!quiet) {
537
+ process.stdout.write(` skills/okstra: ${r.copied ? "copied" : "unchanged"} -> ${dst}\n`);
538
+ }
539
+ return [LEAD_SKILL_NAME];
540
+ }
541
+
542
+ async function installLeadSkillLink(repoAbs, opts) {
543
+ const { dryRun, quiet } = opts;
544
+ const src = join(repoAbs, "agents", "SKILL.md");
545
+ if (!(await fileExists(src))) {
546
+ if (!quiet) process.stdout.write(" skills/okstra: <repo>/agents/SKILL.md missing — skipped\n");
547
+ return [];
548
+ }
549
+ const dst = join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME, "SKILL.md");
550
+ if (!dryRun) await fs.mkdir(join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME), { recursive: true });
551
+ const action = await ensureSymlink(src, dst, { dryRun });
552
+ if (!quiet) process.stdout.write(` skills/okstra/SKILL.md: ${action}\n`);
553
+ return [LEAD_SKILL_NAME];
554
+ }
555
+
490
556
  function parseInstallArgs(args) {
491
557
  const result = {
492
558
  dryRun: false,
@@ -623,7 +689,12 @@ export async function runInstall(args) {
623
689
  }
624
690
 
625
691
  const skillResult = await installSkillsCopy(runtimeRoot, opts);
626
- await writeSkillsManifest(paths.home, skillResult.installed, { dryRun: opts.dryRun });
692
+ const leadSkillResult = await installLeadSkillCopy(runtimeRoot, opts);
693
+ await writeSkillsManifest(
694
+ paths.home,
695
+ [...skillResult.installed, ...leadSkillResult],
696
+ { dryRun: opts.dryRun },
697
+ );
627
698
 
628
699
  const agentResult = await installAgentsCopy(runtimeRoot, opts);
629
700
  await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
package/src/uninstall.mjs CHANGED
@@ -18,6 +18,7 @@ const BIN_ENTRYPOINTS = [
18
18
  ];
19
19
 
20
20
  const FALLBACK_SKILL_NAMES = [
21
+ "okstra",
21
22
  "okstra-setup",
22
23
  "okstra-brief",
23
24
  "okstra-run",