okstra 0.123.0 → 0.125.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.
- package/README.md +1 -0
- package/docs/architecture/storage-model.md +3 -1
- package/docs/architecture.md +16 -2
- package/docs/cli.md +3 -1
- package/docs/for-ai/README.md +41 -35
- package/docs/for-ai/skills/okstra-brief-gen.md +105 -105
- package/docs/for-ai/skills/okstra-container-build.md +61 -61
- package/docs/for-ai/skills/okstra-graphify.md +64 -0
- package/docs/for-ai/skills/okstra-inspect.md +87 -86
- package/docs/for-ai/skills/okstra-manager.md +32 -32
- package/docs/for-ai/skills/okstra-memory.md +49 -50
- package/docs/for-ai/skills/okstra-pr-gen.md +48 -0
- package/docs/for-ai/skills/okstra-rollup.md +58 -58
- package/docs/for-ai/skills/okstra-run.md +95 -95
- package/docs/for-ai/skills/okstra-schedule-gen.md +106 -106
- package/docs/for-ai/skills/okstra-setup.md +63 -64
- package/docs/for-ai/skills/okstra-user-response.md +48 -0
- package/docs/performance-improvement-plan-v2.md +6 -6
- package/docs/pr-template-usage.md +34 -34
- package/docs/project-structure-overview.md +91 -70
- package/docs/task-process/README.md +33 -33
- package/docs/task-process/common-flow.md +26 -26
- package/docs/task-process/error-analysis.md +20 -21
- package/docs/task-process/final-verification.md +41 -41
- package/docs/task-process/implementation-planning.md +33 -33
- package/docs/task-process/implementation.md +38 -38
- package/docs/task-process/release-handoff.md +46 -46
- package/docs/task-process/requirements-discovery.md +22 -23
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +4 -4
- package/runtime/agents/workers/claude-worker.md +2 -2
- package/runtime/agents/workers/codex-worker.md +4 -4
- package/runtime/agents/workers/report-writer-worker.md +4 -4
- package/runtime/bin/lib/okstra/usage.sh +1 -1
- package/runtime/prompts/coding-preflight/frameworks/node-server.md +1 -1
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/convergence.md +10 -20
- package/runtime/prompts/lead/okstra-lead-contract.md +15 -17
- package/runtime/prompts/lead/plan-body-verification.md +18 -18
- package/runtime/prompts/lead/report-writer.md +46 -44
- package/runtime/prompts/lead/team-contract.md +11 -122
- package/runtime/prompts/profiles/_common-contract.md +15 -22
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +28 -2
- package/runtime/prompts/profiles/error-analysis.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +13 -13
- package/runtime/prompts/profiles/implementation.md +2 -1
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +17 -18
- package/runtime/python/okstra_ctl/models.py +4 -2
- package/runtime/python/okstra_ctl/run.py +8 -2
- package/runtime/python/okstra_ctl/stage_fix_carry.py +112 -0
- package/runtime/schemas/final-report-v1.0.schema.json +6 -6
- package/runtime/skills/_fragments/bash-invocation-rule.md +1 -0
- package/runtime/skills/_fragments/preflight-outdated-cli.md +1 -0
- package/runtime/skills/_fragments/python-bootstrap-note.md +1 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +117 -122
- package/runtime/skills/okstra-container-build/SKILL.md +24 -14
- package/runtime/skills/okstra-graphify/SKILL.md +12 -4
- package/runtime/skills/okstra-inspect/SKILL.md +39 -747
- package/runtime/skills/okstra-inspect/facets/cost.md +84 -0
- package/runtime/skills/okstra-inspect/facets/error-zip.md +41 -0
- package/runtime/skills/okstra-inspect/facets/errors.md +69 -0
- package/runtime/skills/okstra-inspect/facets/history.md +90 -0
- package/runtime/skills/okstra-inspect/facets/logs.md +89 -0
- package/runtime/skills/okstra-inspect/facets/recap.md +96 -0
- package/runtime/skills/okstra-inspect/facets/report.md +61 -0
- package/runtime/skills/okstra-inspect/facets/status.md +145 -0
- package/runtime/skills/okstra-inspect/facets/time.md +56 -0
- package/runtime/skills/okstra-manager/SKILL.md +1 -1
- package/runtime/skills/okstra-memory/SKILL.md +3 -3
- package/runtime/skills/okstra-pr-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-rollup/SKILL.md +12 -6
- package/runtime/skills/okstra-run/SKILL.md +49 -88
- package/runtime/skills/okstra-schedule-gen/SKILL.md +36 -30
- package/runtime/skills/okstra-setup/SKILL.md +1 -1
- package/runtime/skills/okstra-setup/references/project-config.md +17 -16
- package/runtime/skills/okstra-usage/SKILL.md +5 -2
- package/runtime/skills/okstra-user-response/SKILL.md +15 -3
- package/runtime/templates/prd/brief.template.md +92 -92
- package/runtime/templates/project-docs/task-index.template.md +1 -1
- package/runtime/templates/reports/error-analysis-input.template.md +1 -1
- package/runtime/templates/reports/fan-out-unit.template.md +6 -6
- package/runtime/templates/reports/final-verification-input.template.md +6 -6
- package/runtime/templates/reports/implementation-input.template.md +1 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
- package/runtime/templates/reports/improvement-discovery-input.template.md +1 -1
- package/runtime/templates/reports/quick-input.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +1 -1
- package/runtime/templates/reports/schedule.template.md +22 -22
- package/runtime/templates/reports/task-brief.template.md +3 -3
- package/runtime/templates/reports/user-response.template.md +20 -20
- package/runtime/templates/worker-prompt-preamble.md +111 -13
- package/runtime/validators/validate-schedule.py +1 -1
|
@@ -34,7 +34,7 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
|
|
|
34
34
|
- 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) implement the minimum change to make it pass, (3) commit the test and its implementation together in a single commit (`feat|fix(<scope>): ...`) — do NOT commit the failing test separately, (4) refactor without changing behaviour and commit separately if any cleanup is made (`refactor(<scope>): ...`). The failing-then-passing transition is preserved as `TDD evidence` in the final report (failing output captured before the merged commit, passing output after), not as two separate commits.
|
|
35
35
|
- Doc-only / config-only / pure-rename steps that have no observable runtime behaviour are exempt from the failing-test requirement, but the executor MUST cite the exemption per step in the final report (`TDD exemption: <reason>`).
|
|
36
36
|
- When the touched area has no existing test harness, the executor MUST stand up the minimum harness needed to host one regression test for this run rather than skipping TDD entirely. Record the harness-bootstrap step as an `Out-of-plan edit` if it is not in the plan.
|
|
37
|
-
- **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion
|
|
37
|
+
- **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `static-analysis only …, unverified (not executed)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
|
|
38
38
|
- **Real-IO test isolation (BLOCKING).** A test that exercises a **real** datastore, HTTP endpoint, external service, message queue, or filesystem — a live DB connection / DSN, a real `fetch` / `axios` / `http` request, an actual S3 / queue client, anything the project's normal CI test suite cannot run because that backend is absent — MUST be written under the task's qa scripts directory `<task_root>/qa/scripts/` (`<TASK_QA_PATH>/scripts`; the `qa/` root itself holds only data sidecars — the Tier 3 conformance manifest and `result-*.json`). It MUST NOT be written into the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*`, or anywhere the project's lint/test globs collect. Two reasons: (a) the project's CI / normal suite has no real DB or network, so a real-IO test placed in source silently breaks the pipeline; (b) it is an okstra verification artifact, and the artifact-home rule confines okstra outputs to `.okstra/`. **The dividing line is the IO, not the intent:** a unit test that stubs/spies only *injected collaborators* (mock — no real socket, no real DB handle) is a TDD red-green artifact and stays in source; the moment a test opens a real connection or makes a real network call it belongs in qa. A stage's real-IO requirement check is a Tier 3 conformance script under `<task_root>/qa/scripts/` (declared via the implementation-planning conformance entry) — never smuggle real IO into a `*.spec.*` in source to make it run "as a unit test". The `db-test` real-execution gate above is satisfied by the conformance/db-test path against the replica, NOT by adding a live-DB `*.spec.*` to the project suite. **Author qa specs with the project's own test framework — never hand-roll `describe`/`it`/`expect`.** When the project ships a test runner as a devDependency (jest / vitest / pytest …), the qa spec uses it, invoked with the project config plus a discovery override pointing at the qa scripts dir (jest: `npx jest --config <project jest config> --roots <task_root>/qa/scripts --runInBand <spec-name>`) — the project config keeps module aliases resolving while the default sweep never collects the file; never widen the project's own test config to include qa paths. For TypeScript qa specs also write `<task_root>/qa/scripts/tsconfig.json` (`extends` the project tsconfig, adds the runner's `types` entry, `"include": ["**/*.ts"]`) so editors resolve path aliases and test globals — it is a qa artifact like the rest (untracked). **These qa artifacts stay untracked — never commit them.** `.okstra/**` is gitignored (the artifact-home rule); conformance scripts and their results are *executed* and recorded in the carry sidecar / verifier result, never written into git history. A committed `.okstra/qa` file is a stage-branch defect that leaks okstra internals into the eventual PR (see the `git add` rules below).
|
|
39
39
|
- re-read the approved plan end-to-end and parse the `## 5.5 Stage Map`. Read the **Stage** injected in the launch prompt (`Stage for this implementation run`): the single stage number this run owns. The runtime already selected and reserved this stage (one run = one stage) — do NOT recompute the start stage from `consumers.jsonl`.
|
|
40
40
|
- load every `runs/<plan-key>/carry/stage-<i>.json` for `i ∈ depends-on(this stage)` and inject them into the executor's working context as "runtime carry-in". For a `depends-on (none)` stage, no sidecar load — task-brief only.
|
|
@@ -42,7 +42,7 @@ Verifier obtains the QA command set from exactly two declared sources, in order
|
|
|
42
42
|
|
|
43
43
|
Tier 1 commands run verbatim first. Then every Tier 2 entry runs once. Then the Tier 3 stage conformance script (below) runs once. Each command runs in the worktree cwd, and is recorded in the worker result with its exact command line, exit code, and the tail of stdout/stderr. Substituting or paraphrasing a Tier 1 command is forbidden (see Verifier-specific forbidden actions below).
|
|
44
44
|
|
|
45
|
-
### Tier 3 — stage conformance scripts (
|
|
45
|
+
### Tier 3 — stage conformance scripts (requirement-conformance verification)
|
|
46
46
|
|
|
47
47
|
Tier 3 proves the stage actually *meets the upper-level requirement* it was scoped to, by running a declared conformance script against the running state — Tiers 1·2 only prove the diff *builds and passes*. This is a real gate: its result sidecar is the input the `validate-run.py` Tier 3 gate reads, so a missing or non-PASS result BLOCKS acceptance.
|
|
48
48
|
|
|
@@ -102,6 +102,32 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
|
|
|
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.
|
|
104
104
|
|
|
105
|
+
### Fix-run incremental scope (applies when the profile carries a "Fix-Run Carry" block)
|
|
106
|
+
|
|
107
|
+
A fix run re-verifies a stage whose previous run already passed a full static
|
|
108
|
+
sweep and failed only on cited blocking findings. Re-sweeping the whole stage
|
|
109
|
+
diff re-buys wall-clock without new information, so the static scope narrows —
|
|
110
|
+
the command re-run does not:
|
|
111
|
+
|
|
112
|
+
- **Command re-run stays full.** Every Tier 1/2/3 command from the plan's
|
|
113
|
+
validation set runs end-to-end exactly as in a first run. QA-RESULT gating
|
|
114
|
+
(`validate-run.py` Tier 3) is unchanged.
|
|
115
|
+
- **Static design & test-quality sweep narrows to the fix diff.** Enumerate
|
|
116
|
+
`git diff <prev-head>..HEAD` (the `Previous run HEAD` line of the Fix-Run
|
|
117
|
+
Carry block) instead of the whole stage diff, and re-check each carried
|
|
118
|
+
blocking finding against the current tree. The blocking/advisory taxonomy
|
|
119
|
+
above applies unchanged to that narrowed file set.
|
|
120
|
+
- **Carried findings drive the verdict.** Every carried blocking finding MUST
|
|
121
|
+
be re-checked and cited in the verifier result as `resolved` (with the fix
|
|
122
|
+
commit) or `still-failing` (verdict `FAIL`). A new blocking defect inside
|
|
123
|
+
the fix diff also forces `FAIL`. Files untouched since `<prev-head>` are
|
|
124
|
+
out of static-sweep scope — they already passed the previous full sweep.
|
|
125
|
+
|
|
126
|
+
Enforcement: the carried-finding re-check lands in the verifier result file
|
|
127
|
+
(Phase 5.5 convergence consumes it); a fix-run verifier result that cites no
|
|
128
|
+
carried findings is a contract violation the lead records via
|
|
129
|
+
`okstra error-log append-observed --error-type contract-violation`.
|
|
130
|
+
|
|
105
131
|
### DB / IO / SQL change — real-execution gate (mock-only acceptance forbidden)
|
|
106
132
|
|
|
107
133
|
A mocked unit test cannot observe the SQL a query builder actually emits — `count({ col: 'FontFamily.fontFamily' })` passes a mocked suite yet throws `Unknown column` on a real database. For this class of change a green mock-only suite is therefore NOT evidence; only a run against a real (or faithful-replica) datastore is. This gate is the verifier's enforcement of that rule.
|
|
@@ -109,7 +135,7 @@ A mocked unit test cannot observe the SQL a query builder actually emits — `co
|
|
|
109
135
|
- **Trigger.** Fires when `git diff <base>...HEAD` touches DB/IO/SQL: ORM / query-builder code (sequelize / typeorm / prisma / knex / raw SQL), `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string.
|
|
110
136
|
- **Requirement when fired.** The verifier MUST reproduce a real-DB execution: run the `db-test` tier (Tier 1 = plan `validation` db step; else Tier 2 = `project.json.qaCommands.db-test`) against a **local / replica** datastore (same engine + schema — never shared / staging / prod, consistent with the verifier forbidden-actions list) and record its exact command + exit code. A mock, an in-memory shim that does not parse real SQL, or static reasoning does NOT satisfy this.
|
|
111
137
|
- **No `db-test` command available → blocking, not a passive skip.** If neither tier declares a `db-test` command, the verifier records the blocking finding `db-test not configured — DB change unverified (mock-only)` and sets the verdict to `FAIL`; it MUST NOT emit only the passive `qa-command not configured` note and pass. Recommended fix: declare a `db-test` command in `project.json.qaCommands` or the plan's validation set.
|
|
112
|
-
- **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion
|
|
138
|
+
- **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion `static-analysis only …, unverified (not executed)` (never `verified`), records it as a blocking finding, and sets `FAIL`. Never downplay the real run as "too heavy / static proof suffices".
|
|
113
139
|
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push.
|
|
114
140
|
|
|
115
141
|
## All-verifier-failure policy
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows use `Blocks=next-phase`; any unconfirmed inference may be used as a labelled hypothesis only.
|
|
13
13
|
- the reporter's symptom description in `Source Material` is the ground truth for what to reproduce. Do not paraphrase it when stating the symptom in the report; quote it.
|
|
14
14
|
- read `Related Task Graph` before forming root-cause candidates. `depends-on`, `blocks` / `blocked-by`, parent/child, follow-up, and split edges define upstream/downstream boundaries for the symptom: identify whether this task's failure is caused by, blocks, or merely relates to another task before merging causes.
|
|
15
|
-
- any `intent-inference` augmentation that re-characterises the symptom (e.g. classifying "
|
|
15
|
+
- any `intent-inference` augmentation that re-characterises the symptom (e.g. classifying a vague reporter phrase like "it sometimes doesn't work" as "intermittent failure on a specific code path") is a **hypothesis**, not a confirmed symptom. If `[CONFIRMED …]` appears on the matching `intent-check:` row, treat that confirmation as the symptom. Otherwise follow the precondition's `skipped` branch above and keep the inference labelled as a hypothesis in the root-cause analysis.
|
|
16
16
|
- `conversion-block:` rows mean the brief could not map a reporter statement to project vocabulary; never invent the missing mapping in this phase.
|
|
17
17
|
- Diagnosis loop:
|
|
18
18
|
- **Symptom lock:** state the reporter's symptom verbatim, then translate it into one observable failure condition. If no observable condition can be derived from the brief, record that gap as the first blocker instead of guessing.
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
- practical next diagnostic steps
|
|
35
35
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
36
36
|
- if any blocking uncertainty remains at the time of writing the final report, populate `## 1. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
|
|
37
|
-
- prefer plain Korean over abbreviations (e.g. write
|
|
37
|
+
- prefer plain Korean over abbreviations (e.g. write out the full Korean phrase for "requests per second" instead of "QPS", and for "reproduction steps" instead of "repro")
|
|
38
38
|
{{INCLUDE:_clarification-recommendation.md}}
|
|
39
39
|
- **Codebase-first ambiguity resolution (defect rule)**: any ambiguity about repro, file behavior, or symbol semantics that can be answered by `Read` / `Grep` / log inspection MUST be resolved that way and recorded with file:line (or log-line) evidence. Writing a clarification row for something the codebase or shipped logs already answer is a defect of this phase.
|
|
40
40
|
- **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <reporter-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only the reporter can answer this" (reporter-side data, business priority, environment they observed). A row with `none` that *could* have been answered by code or logs is a defect.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
- Pre-verification entry gate (resolved & enforced by `okstra render-bundle` prep — the lead does NOT recompute it):
|
|
27
27
|
- the verification target (scope / worktree / base / stages / source reports / diff stat) is injected as the `VERIFICATION_TARGET` block. The lead MUST treat it as authoritative and MUST NOT re-pick a target from the brief.
|
|
28
28
|
- **whole-task scope** (`--stage auto`, default): prep has already verified every Stage Map stage is `status:done` in `consumers.jsonl`, every done stage's `head_commit` is an ancestor of the task worktree HEAD (all stage branches merged), and the worktree is clean outside `.okstra/`. If any check failed the run never started (PrepareError); a started whole-task run is therefore a fully-merged, clean target.
|
|
29
|
-
- **whole-task
|
|
29
|
+
- **whole-task is a mutating phase, not a read-only one.** On entry, whole-task mode auto-merges (with `--no-ff`) the done stages not yet merged into the task branch to create an integration commit, then removes the cleanupable stage worktrees, registry stage-keys, and stage branches. If a merge conflict occurs it reports the conflicting files and aborts (the user resolves them manually and retries). A stage worktree with uncommitted changes remaining is preserved. Therefore the "fully-merged, clean target" the entry gate above refers to is the state after this auto-integration step completes, and whole-task final-verification must be treated as a mutating phase that creates the integration commit.
|
|
30
30
|
- **single-stage scope** (`--stage N`): prep verified stage N is `status:done` and its isolated stage worktree exists and is clean. Other stages' state is irrelevant. A single-stage run is a partial verification: it MUST NOT recommend plain `release-handoff`, but MAY recommend `release-handoff(stage-group)` when the verdict is `accepted` — the stage becomes PR-eligible for a stage-group handoff.
|
|
31
31
|
- the lead still captures `git status --short` from the injected worktree to confirm the analysis ran against the delivered work-tree state; an unexpected divergence (dirty tree outside `.okstra/`, missing worktree) is a `tool-failure`, not a silent proceed.
|
|
32
32
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
- **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above.
|
|
68
68
|
- Section heading contract (BLOCKING — validator scans for these literal English substrings):
|
|
69
69
|
- The final report MUST include section headings containing each of the following exact strings: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stage Exit Contract`, `Stage Validation`, `Dependency`, `Cross-Project Dependencies`, `Decision Drafts`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Implementation Design Preparation`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.) `validators/validate-run.py` enforces the planning headings it scans, while `schemas/final-report-v1.0.schema.json` plus `templates/reports/final-report.template.md` enforce and render the design-preparation heading from its required data block.
|
|
70
|
-
- Korean translations are allowed in parentheses (e.g. `### Recommended Option (
|
|
70
|
+
- Korean translations are allowed in parentheses (e.g. `### Recommended Option (Korean gloss)`), but the English keyword must be present verbatim in the heading line.
|
|
71
71
|
- The shape and ordering follow `final-report-template.md` sections 5.4 (`Implementation Plan Deliverables`) + 5.5 (`Stage Map`). `validators/validate-run.py` substring-matches the raw report text, so a Korean-only heading fails the gate — the cause of repeated observed failures.
|
|
72
72
|
- Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — not deferred to the `implementation` entry gate. It enforces: the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤8), the `depends-on` DAG, and the per-stage vertical-slice contract (S10). S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
|
|
73
73
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
- **Keep the table at exactly 5 columns** — do NOT add a column. `validators/validate-implementation-plan-stages.py` parses `stage | title | depends-on | step-count | exit-contract-summary` and silently skips any row that is not exactly 5 cells, so a 6th column would drop every stage and bypass S2–S11.
|
|
83
83
|
- **Multi-project plans only** (the plan's work spans more than one project — see the Project-boundary partition rule below): prefix each stage's `title` cell with a `[<project>]` tag (e.g. `[okstra] Add X`) so the project each stage belongs to is readable at a glance, and add exactly one line directly under the Stage Map table — `Cross-project parallelism: <which per-project stages run in parallel, which are sequenced, and the cross-project dependency that forces each sequencing>`. Single-project plans omit both the tag and the line.
|
|
84
84
|
- **Per-stage slice declaration (mandatory lines, directly under the `## 5.5.<i> Stage <i>:` heading, before `### Carry-In`):**
|
|
85
|
-
- `Slice value: <the one user-observable increment this stage delivers, end-to-end>` — describe WHAT starts working from the consumer's view (e.g. "X
|
|
85
|
+
- `Slice value: <the one user-observable increment this stage delivers, end-to-end>` — describe WHAT starts working from the consumer's view (e.g. "querying X returns Y"), NOT a layer name ("add a repository"). Validator S10a rejects a missing/empty value.
|
|
86
86
|
- `Acceptance: <the observable pass condition or the exact command>` — the signal that proves the slice is done; normally the same test command that the `RED:` step below flips to PASS. Validator S10b rejects a missing/empty value.
|
|
87
87
|
- **Test-case design (mandatory three lines unless the stage carries a `TDD exemption:`):** the plan, not the executor, decides which cases the stage's tests must cover, so a stage cannot ship with only one happy-path assertion. Declare all three:
|
|
88
88
|
- `Test case (success): <input → expected, the command/test name>` — the happy path: a valid input that proves the slice works end-to-end.
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
Validator S10d rejects a missing/empty line in any of the three categories (skipped only when a `TDD exemption:` line is present). The `RED:` step below must encode these cases, not a single assertion.
|
|
92
92
|
- **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
|
|
93
93
|
- `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
|
|
94
|
-
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change (
|
|
94
|
+
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change (no lower time bound; it may span several files that change together); for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`expected` = FAIL) — the RED step encodes the case set, not a single happy-path assertion; at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN **and** that the `RED:` step's `expected` reads FAIL / the `GREEN:` step's reads PASS; S10e rejects a `TDD exemption:` whose reason is not one of doc-only / config-only / pure-rename (both in `validators/validate-implementation-plan-stages.py`).
|
|
95
95
|
- **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
|
|
96
|
-
- `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL:
|
|
96
|
+
- `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. When the verification body is a test spec, author it with the project's own test framework (devDependency) invoked via a discovery override at `<task_root>/qa/scripts/` (jest: `--config <project config> --roots <task_root>/qa/scripts`) — never hand-roll `describe`/`expect` and never widen the project's own test config; for TypeScript specs also write `<task_root>/qa/scripts/tsconfig.json` extending the project tsconfig with the runner's `types` entry so editors resolve the file.
|
|
97
97
|
- `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
|
|
98
98
|
The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result gate (each script's `QA-RESULT`) is enforced by the verifier Tier3 + validate-run.
|
|
99
99
|
- `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
|
|
@@ -104,14 +104,14 @@
|
|
|
104
104
|
- **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-project dependency rows below).
|
|
105
105
|
- **Parallel-feasibility check (mandatory for every multi-project plan):** disjoint files (S9 below) is necessary but NOT sufficient for parallelism — a cross-project API/contract/schema/deploy-order dependency forces sequencing even when no file overlaps. For each pair of projects, explicitly determine and record (in the `Cross-project parallelism:` line) whether they are independent (run in parallel) or sequenced (and the exact dependency that forces the order).
|
|
106
106
|
- **Parallel-safety invariant (BLOCKING):** any two stages that are both `depends-on (none)` MUST predict disjoint file sets in their `Stage Exit Contract`. Two parallel `implementation` runs would otherwise edit the same file concurrently. Work touching a shared file must either go in one stage or be ordered with `depends-on`. Enforced by `validators/validate-implementation-plan-stages.py` check S9.
|
|
107
|
-
- **Cross-project dependency rows (`crossProjectDependencies`
|
|
108
|
-
- `direction: upstream-precondition` —
|
|
109
|
-
- `direction: downstream-carry` —
|
|
110
|
-
- **cross-repo
|
|
107
|
+
- **Cross-project dependency rows (`crossProjectDependencies` array — replaces the old `## Cross-Repo Carry` appendix):** a dependency on another project (a different repo / a different top-level independently-deployable module / a published package) is recorded not as the freeform `## Cross-Repo Carry` appendix but as an **`XP-NNN` row of the structured field `crossProjectDependencies`**. Put one XP row per project dependency and fill in whichever `direction` applies (rendered as `### Cross-Project Dependencies` §5.4; a single-project plan uses an empty array). The row fields replace the old three subsections with — `requiredWork` (the concrete work the other side must build) / `verificationSignal` (the signal this run will observe) / `linkedWork` (the stage/step of this plan that is blocked until the signal is met) / `howToStart` (the exact handoff in the other repo).
|
|
108
|
+
- `direction: upstream-precondition` — this run **waits on the other side's prerequisite work (precondition required)**: `requiredWork` = the concrete work the other side must implement first, `verificationSignal` = the signal this run must observe before proceeding (PR merged / endpoint live / version published), `linkedWork` = the stage/step of this plan that is blocked until the signal is met, `howToStart` = in the other repo, `okstra-brief-gen` (**cite this report's absolute path as Source Material** — the only permitted cross-`<PROJECT_ROOT>` read) → `okstra-run`.
|
|
109
|
+
- `direction: downstream-carry` — this run **produces what the other side will consume (the existing carry)**: `requiredWork` = the self-contained B-portion the other side will implement next (from the other side's perspective, a new `R-NNN`, proposed stages, and affected files), `verificationSignal` = the (this-run-delivered) signal the other side confirms before proceeding, `linkedWork` = the stage/step of this plan that delivers that signal, `howToStart` is the same as upstream. Do not hand over this run's already-`done` stages as if they were the other side's stages — include only the portion the other side still has to build.
|
|
110
|
+
- **A cross-repo dependency cannot be expressed with `depends-on`:** that gate only resolves commits inside a single repo's git graph (`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`), so it cannot point at a commit in another repo. Cross-repo work is split into a separate okstra run + XP row rather than a stage (the Different-repos rule). The other run is structurally independent, so it does not automatically recognise this run's completed stages as `done` (nor should it — that is this repo's work); the XP row is only a narrative input that seeds the other side's planning. This run does not write to the other repo's tree or its `.okstra/` (it emits only this report's XP rows). The old "Recognition caveat" wording has been moved into a render note (i18n `crossProjectRecognitionNote`) and is emitted automatically in the `### Cross-Project Dependencies` section.
|
|
111
111
|
- **Stage exit contract is the carry surface:** keep it as narrow as possible. Wider surface = more downstream coupling.
|
|
112
112
|
- dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
|
|
113
|
-
- **Cross-Project Dependencies (
|
|
114
|
-
- **recommendedNextSteps
|
|
113
|
+
- **Cross-Project Dependencies (conditionally required):** when the plan depends on work in another project / repo / published package, add (a) a `kind: cross-project` DM row to `dependencyMigrationRisk`, and (b) a matching `XP-NNN` row to `crossProjectDependencies`. An upstream-precondition row must have concrete `requiredWork` / `verificationSignal` / `howToStart` — `validators/validate-run.py` enforces that a DM `cross-project` ⇒ at least one `direction: upstream-precondition` XP row, and the schema enforces non-empty row fields. A cross-project dependency is recorded as this structured precondition, not as a soft Recommended Next Step. A single-project plan uses an empty array.
|
|
114
|
+
- **recommendedNextSteps policy:** keep the substance of cross-project preconditions/carries in `crossProjectDependencies`, and put in `§3 Recommended Next Steps` only a pointer to that section (`§5.4 Cross-Project Dependencies`) — no double recording.
|
|
115
115
|
- validation checklist (pre / mid / post) — each item is an exact command or observable outcome
|
|
116
116
|
- rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
|
|
117
117
|
- **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` cites the brief heading or file/line where the requirement came from. `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, or `blocked C-NNN`. If any row is `gap` or `blocked C-NNN`, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
- the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
|
|
121
121
|
- **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
|
|
122
122
|
- every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (the unified table is the single home for these — the "no separate `Open Questions` block" rule is in the shared `_common-contract.md` clarification policy)
|
|
123
|
-
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). majority-disagree
|
|
123
|
+
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through one report-writer self-fix pass before being promoted to a user clarification (`prompts/lead/plan-body-verification.md` "Self-fix round"). Only items not resolved by self-fix, or that are majority-`needs-user-input`, become `Blocks=approval` clarifications. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails a planner-fixable majority item promoted without a self-fix as `contract-violated`.
|
|
124
124
|
- **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
|
|
125
125
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
126
126
|
2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
|
|
@@ -142,5 +142,5 @@
|
|
|
142
142
|
6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
|
|
143
143
|
7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
|
|
144
144
|
8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
|
|
145
|
-
9. **Cross-project dependency check** —
|
|
146
|
-
10. **Decision-draft materialization check** — `decisionDrafts`
|
|
145
|
+
9. **Cross-project dependency check** — confirm you have not missed a dependency on another repo / another top-level deployable module / a published package. If `dependencyMigrationRisk` has a `kind: cross-project` row, confirm a matching `direction: upstream-precondition` `XP-NNN` row exists in `crossProjectDependencies`, and re-read as a reviewer whether its `requiredWork` is the concrete work the other side must actually build rather than an abstract phrase ("other side's work done") — validator S only checks existence, so concreteness is the self-review's responsibility. Confirm cross-repo work is split into a separate run + XP row instead of being crammed into one task's stages, and that the cross-project substance is not duplicated in `§3 Recommended Next Steps` but lives only in `§5.4 Cross-Project Dependencies`.
|
|
146
|
+
10. **Decision-draft materialization check** — when `decisionDrafts` is non-empty, confirm as a reviewer which stage's stepwise order contains the matching materialization step (creating `.okstra/decisions/<NNNN>-<slug>.md`) and that the number of drafts corresponds 1:1 with the materialization steps. The validator only checks the *existence* of the step, so the `<NNNN>-<slug>` correctness and count correspondence are the self-review's responsibility.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Purpose: realise the approved `implementation-planning` deliverable as actual source changes, with cross-model verification, while keeping the run reversible
|
|
4
4
|
- **Run-level fixed cost:** the verifier set, Phase 5.5 convergence, and the Phase 6 report-writer run exactly once per implementation run, over this run's single stage diff — never once per step.
|
|
5
|
+
- **Fix run (profile carries a "Fix-Run Carry" block):** the executor's scope is the carried blocking findings plus the previous routing recommendation — it MUST NOT re-execute plan steps the previous run completed. Verifiers apply the "Fix-run incremental scope" section of `_implementation-verifier.md`; the report writer applies "Fix-run incremental authoring" in `report-writer.md`. The full validation-command re-run is NOT reduced.
|
|
5
6
|
- Required workers:
|
|
6
7
|
- claude
|
|
7
8
|
- codex
|
|
@@ -46,4 +47,4 @@ The bulk of this profile's body is split into three sidecars so the lead's Phase
|
|
|
46
47
|
| `prompts/profiles/_implementation-verifier.md` | Phase 5, between Executor stage completion and the first verifier dispatch | Verifier roles, Two-tier command lookup, deny-list, discrepancy rule, Read-only command log, verifier-specific forbidden actions |
|
|
47
48
|
| `prompts/profiles/_implementation-deliverable.md` | Start of Phase 6 (after Phase 5.5 convergence completes, before report-writer dispatch prompt construction) | Required deliverable shape, Validation / TDD evidence rules, Verifier results structure, Self-review pass, Lead post-stage persistence |
|
|
48
49
|
|
|
49
|
-
**Phase 5 / 6
|
|
50
|
+
**Entering Phase 5 / 6 while the relevant sidecar is not in the lead's context is BLOCKING — the phase entry is refused.** After reading a sidecar, the lead must continue into the phase's follow-up action within a single turn (i.e. the sidecar's rules take effect starting from the very turn it is read).
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
- Expected output emphasis:
|
|
40
40
|
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 10-column schema from `validators/validate_improvement_report.py` (Cand ID `I-NNN`, Lens from whitelist, Title, Scope ⊆ scan-scope, Severity, Effort, Consensus, Source workers `<worker>:<id>` from {claude, codex, antigravity}, Recommended next-phase ∈ {requirements-discovery, implementation-planning, error-analysis}, Evidence as path:line list)
|
|
41
41
|
- `Consensus` cells in `## 5.9 Improvement Candidates` use the table enum exactly: `full`, `partial`, `contested`, `worker-unique`. Map convergence's `full-consensus` / `partial-consensus` labels to `full` / `partial` before writing the table.
|
|
42
|
-
- `## 7. Final Verdict` Verdict Token ∈ {`candidates-ready`, `no-candidates`, `blocked`}; Direction `routing`; Next Step "
|
|
42
|
+
- `## 7. Final Verdict` Verdict Token ∈ {`candidates-ready`, `no-candidates`, `blocked`}; Direction `routing`; Next Step "ask the user to select K candidates (see the ## 5.9 table)"
|
|
43
43
|
- `## 3. Recommended Next Steps` first entry summarises per-candidate routing and proposes new task-key names of the form `<task-group>/imp-<Cand-ID>`
|
|
44
44
|
- this report is authored free-form (improvement-discovery is not in the data.json schema enum); after the markdown is written, the report-writer runs `okstra inject-report-index <report.md> --report-language <en|ko>` to add the top-of-report Index + `I-NNN`/`C-NNN` scroll anchors. The run validator fails the report when the Index anchor is missing.
|
|
45
45
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
`okstra handoff local-checkout --project-root <project root> --project-id <id> --task-group <g> --task-id <t>`
|
|
29
29
|
Exit 1 means a precondition failed (MAIN worktree dirty, registry entry missing, or checkout failed): show the error verbatim and re-ask the action selection. On success the okstra task worktree no longer exists — the lead's cwd is gone, so EVERY subsequent step (final-report authoring included) MUST use absolute paths or run from the MAIN worktree. Then route to the final-report self-review pass.
|
|
30
30
|
- **stage-group mode step order** (overrides the default Q1→Q3 sequence): after Q1 picks `push + PR`, run (1) base-branch selection first — identical to Q2 below, because the dependency-closure check needs `origin/<base>`; (2) G2 stage confirmation (step 1g); (3) assemble (step 2g); then Q2b and Q3 as usual, with the collector branch as the PR head.
|
|
31
|
-
1g. **G2 — stage confirmation**: the stage selection already happened before the run (wizard `handoff_stage_pick`, or the CLI `--stages` flag) and is fixed in `HANDOFF_STAGES`. Display it as a one-line confirmation (`PR
|
|
31
|
+
1g. **G2 — stage confirmation**: the stage selection already happened before the run (wizard `handoff_stage_pick`, or the CLI `--stages` flag) and is fixed in `HANDOFF_STAGES`. Display it as a one-line confirmation (`PR target stage: <csv> — proceeding`) and proceed; do NOT re-ask the multi-select. Only if `HANDOFF_MODE` is `stage-group` but `HANDOFF_STAGES` is empty (defensive, should not happen) run `okstra handoff eligible --plan-run-root <plan-run-root> --approved-plan <approved plan path>` and ask the user to pick from the eligible stages.
|
|
32
32
|
2g. **assemble**: run `okstra handoff assemble --plan-run-root <...> --approved-plan <...> --project-root <project root> --project-id <id> --task-group <g> --task-id <t> --work-category <c> --stages <csv> --base <chosen-base>`. Exit 2 means a stage-vs-stage merge conflict: show the `conflicts` paths and stop (route: reshape the group or resolve manually). Exit 1 means an eligibility/closure violation: show the error verbatim and re-ask G2. On success the returned `branch` is the PR head branch for every subsequent step.
|
|
33
33
|
2. **PR base branch** (only when the user picked `push + PR`) — present four options and capture exactly one:
|
|
34
34
|
- `preprod`
|
|
35
35
|
- `main`
|
|
36
|
-
-
|
|
36
|
+
- `direct input` (free-form branch name; lead validates the name exists on origin via `git ls-remote --heads origin <name>` and re-asks on failure)
|
|
37
37
|
The chosen base MUST NOT equal the feature branch. If it does, re-ask.
|
|
38
38
|
2b. **Pre-merge conflict probe** (only when the user picked `push + PR`) — before the push/PR step, the lead MUST refresh the base ref and probe for merge conflicts against it:
|
|
39
39
|
- run `git fetch origin <chosen-base>` (read-only on the local working tree).
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
- `- PR creation skipped: <reason>` for any user-driven cancellation
|
|
86
86
|
- **Local Checkout Outcome**: one of
|
|
87
87
|
- `- Not run (user picked push + PR or skip).`
|
|
88
|
-
- `- Checked out <branch> into <main worktree path>; okstra task worktree <path> removed. Next:
|
|
88
|
+
- `- Checked out <branch> into <main worktree path>; okstra task worktree <path> removed. Next: to run a local test and then open a PR, re-enter release-handoff via okstra-run → push + PR.`
|
|
89
89
|
- **Stage Group** (stage-group mode only): selected stages, each stage's single-stage verification report path + quoted `Verdict Token` row, collector branch name, merge commit SHAs from assemble, and the dependency-closure verdict (from the assemble output / error).
|
|
90
90
|
- **Routing recommendation**: explicit `done` token, since release-handoff is the terminal lifecycle phase. If the run ended in `skip` or `cancel`, the recommendation MUST also state whether re-entry into release-handoff is appropriate.
|
|
91
91
|
- Self-review pass before finalising the report (`Claude lead` runs this):
|
|
@@ -23,23 +23,23 @@
|
|
|
23
23
|
- capture approval or confirmation points before the next phase starts
|
|
24
24
|
- **domain alignment check**: read `<PROJECT_ROOT>/.okstra/glossary.md` and `<PROJECT_ROOT>/.okstra/decisions/` titles if present. Absent okstra memory files are normal — do not error. Validate that every `terminology:*` entry under the brief's `Open Questions` has a canonical resolution before routing. Fuzzy or overloaded terms in the brief MUST be resolved to a single canonical term in this phase.
|
|
25
25
|
- Fan-out (multi-item / multi-domain requests only):
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
packet
|
|
26
|
+
- A single-item / single-domain request is not fanned out (preserving the current single-routing behaviour).
|
|
27
|
+
- When a request spans two or more domains, or has two or more independently-startable work items,
|
|
28
|
+
publish each item as a `runs/requirements-discovery/fan-out/unit-<NNN>.md` packet.
|
|
29
|
+
The packet follows the `templates/reports/fan-out-unit.template.md` format, and its frontmatter
|
|
30
30
|
`domain`(work-category 5-enum: bugfix / feature / refactor / ops / improvement),
|
|
31
|
-
`depends-on`(
|
|
32
|
-
`recommended-next-phase`(error-analysis | implementation-planning)
|
|
33
|
-
- `runs/requirements-discovery/fan-out/index.md
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- brief
|
|
38
|
-
|
|
39
|
-
fan-out
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
`depends-on`(an inline list of unit-ids within the same fan-out `[unit-001]`, or `[]` if none),
|
|
32
|
+
`recommended-next-phase`(error-analysis | implementation-planning) are filled in.
|
|
33
|
+
- in `runs/requirements-discovery/fan-out/index.md`, list the packets in depends-on topological order
|
|
34
|
+
as a numbered list (`1. unit-001`) (a generated view; explicitly do not hand-edit). The depends-on graph
|
|
35
|
+
must be a DAG — `validate_fanout` rejects a cycle as a validation failure, so if a cycle appears,
|
|
36
|
+
break the offending dependency to make it a DAG before finalizing the packet.
|
|
37
|
+
- an ordering edge seeded from the brief's `Related Task Graph` is preserved in each packet's `depends-on`
|
|
38
|
+
and in the final report's routing rationale. A `duplicates` / `related-to` edge is
|
|
39
|
+
not interpreted as fan-out ordering; use it only as a reference for avoiding duplicate work / scope alignment.
|
|
40
|
+
- in the final report, do not duplicate the decomposition result; keep only the one-line "fan-out: N packets → fan-out/index.md"
|
|
41
|
+
pointer. Packet execution is separate: the user starts each unit as a new task-key via
|
|
42
|
+
`okstra-run --task-brief <packet path>` (this phase does not directly start any downstream run).
|
|
43
43
|
- Decision-tree walk (bounded):
|
|
44
44
|
- When the brief's `Desired Outcome`, classification, or routing target depends on a chain of decisions, walk that chain one branch at a time. Each branch is one `Clarification Items` row, not a free-form interview.
|
|
45
45
|
- For every clarification row, put the single best answer and one-line rationale in `Expected form` as `Recommended: (a) ...`. Put other options and one-sentence consequences in the same cell as `Alternatives: (b) ... (c) ...` — the recommended answer is option `(a)` and alternatives continue from `(b)`, so the labels read `(a) (b) (c) …` in order.
|
|
@@ -63,5 +63,4 @@
|
|
|
63
63
|
- full implementation design unless it is required to decide the next phase
|
|
64
64
|
- **source code edits, plan authoring, builds, or deployments** — this run only classifies the work and routes it; deeper analysis and planning belong to subsequent phases
|
|
65
65
|
- **writes outside `<PROJECT_ROOT>/.okstra/`** — this phase only uses okstra's artifact root. Glossary additions land in `<PROJECT_ROOT>/.okstra/glossary.md` (via `okstra-brief-gen` Step 4.5); decision drafts land in `<PROJECT_ROOT>/.okstra/decisions/` (via `implementation-planning`).
|
|
66
|
-
-
|
|
67
|
-
편집·plan 작성은 여전히 non-goal 이며 다운스트림 phase 가 담당한다
|
|
66
|
+
- work-unit decomposition (fan-out) is in-scope for this phase — but each unit's *solution design*, source edits, and plan authoring remain non-goals and are handled by downstream phases
|
|
@@ -61,10 +61,12 @@ CODEX = {
|
|
|
61
61
|
PROVIDER_MAPPINGS = {"claude": CLAUDE, "antigravity": ANTIGRAVITY, "codex": CODEX}
|
|
62
62
|
|
|
63
63
|
# ROLE-keyed (not provider): 1:1 with run.py recommended_role_models. "lead" uses
|
|
64
|
-
# the claude provider; "report-writer"
|
|
64
|
+
# the claude provider; "report-writer" resolves via the claude provider too.
|
|
65
|
+
# report-writer defaults below the lead tier on purpose: it does not vote in
|
|
66
|
+
# convergence, and its wall-clock is dominated by sheer report generation.
|
|
65
67
|
ROLE_DEFAULTS = {
|
|
66
68
|
"lead": "opus", "claude": "opus", "codex": "gpt-5.6-sol",
|
|
67
|
-
"antigravity": "gemini-3.1-pro", "report-writer":
|
|
69
|
+
"antigravity": "gemini-3.1-pro", "report-writer": "sonnet",
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
|
|
@@ -30,6 +30,7 @@ from okstra_project import project_json_path, upsert_project_json
|
|
|
30
30
|
from okstra_project.state import slugify
|
|
31
31
|
from . import fix_cycles
|
|
32
32
|
from .analysis_packet import build_analysis_packet
|
|
33
|
+
from .stage_fix_carry import derive_stage_fix_carry
|
|
33
34
|
from .clarification_items import (
|
|
34
35
|
attached_user_responses_section,
|
|
35
36
|
clarification_response_with_sidecars,
|
|
@@ -1346,7 +1347,7 @@ def recommended_role_models() -> dict[str, str]:
|
|
|
1346
1347
|
"claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", default_model("claude")),
|
|
1347
1348
|
"codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", default_model("codex")),
|
|
1348
1349
|
"antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", default_model("antigravity")),
|
|
1349
|
-
"report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL",
|
|
1350
|
+
"report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", default_model("report-writer")),
|
|
1350
1351
|
}
|
|
1351
1352
|
|
|
1352
1353
|
|
|
@@ -1600,7 +1601,7 @@ def _write_instruction_set_sources(
|
|
|
1600
1601
|
instruction_set.mkdir(parents=True, exist_ok=True)
|
|
1601
1602
|
profile_rendered = profile_content
|
|
1602
1603
|
if inp.task_type == "implementation":
|
|
1603
|
-
profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}"
|
|
1604
|
+
profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}\n\n{{FIX_RUN_CONTEXT}}"
|
|
1604
1605
|
profile_tokens = (
|
|
1605
1606
|
"EXECUTOR_PROVIDER",
|
|
1606
1607
|
"EXECUTOR_DISPLAY_NAME",
|
|
@@ -1613,6 +1614,7 @@ def _write_instruction_set_sources(
|
|
|
1613
1614
|
"EXECUTOR_WORKTREE_STATUS",
|
|
1614
1615
|
"EXECUTOR_WORKTREE_NOTE",
|
|
1615
1616
|
"DESIGN_PREP_CONTEXT",
|
|
1617
|
+
"FIX_RUN_CONTEXT",
|
|
1616
1618
|
"PHASE_FORBIDDEN_ACTIONS",
|
|
1617
1619
|
)
|
|
1618
1620
|
for key in profile_tokens:
|
|
@@ -2038,6 +2040,10 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2038
2040
|
_publish_stage_run_claim(
|
|
2039
2041
|
inp, ctx, ctx_stage_map, stage_run_claim,
|
|
2040
2042
|
)
|
|
2043
|
+
carry = derive_stage_fix_carry(
|
|
2044
|
+
Path(ctx["RUN_DIR"]), ctx.get("EXECUTOR_WORKTREE_PATH", ""),
|
|
2045
|
+
)
|
|
2046
|
+
ctx["FIX_RUN_CONTEXT"] = carry.as_prompt_markdown() if carry else ""
|
|
2041
2047
|
|
|
2042
2048
|
if inp.render_only:
|
|
2043
2049
|
# render-only entry path (e.g. okstra-run skill, in-session takeover):
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Derive the fix-run carry for an implementation stage whose previous run
|
|
2
|
+
ended with one or more verifier FAIL verdicts.
|
|
3
|
+
|
|
4
|
+
Pure read-side: scans the stage run directory's final-report data.json files
|
|
5
|
+
(the JSON SSOT written by the report writer) and the stage worktree HEAD.
|
|
6
|
+
Returns None when the upcoming run is not a fix run, so callers can treat
|
|
7
|
+
"no carry" and "first run on this stage" identically.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
_REPORT_RE = re.compile(r"final-report-implementation-(\d+)\.data\.json$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class StageFixCarry:
|
|
22
|
+
prev_report_path: str
|
|
23
|
+
prev_report_seq: str
|
|
24
|
+
prev_head_commit: str
|
|
25
|
+
failed_verifiers: list[str]
|
|
26
|
+
blocking_findings: list[str]
|
|
27
|
+
routing_recommendation: str
|
|
28
|
+
|
|
29
|
+
def as_prompt_markdown(self) -> str:
|
|
30
|
+
lines = [
|
|
31
|
+
"## Fix-Run Carry (this run fixes a prior FAIL)",
|
|
32
|
+
"",
|
|
33
|
+
f"- Previous report (data.json SSOT): `{self.prev_report_path}`",
|
|
34
|
+
f"- Previous run HEAD (fix-diff base): `{self.prev_head_commit}`",
|
|
35
|
+
f"- Failed verifiers: {', '.join(self.failed_verifiers)}",
|
|
36
|
+
"- Carried blocking findings:",
|
|
37
|
+
]
|
|
38
|
+
lines.extend(f" - {finding}" for finding in self.blocking_findings)
|
|
39
|
+
if self.routing_recommendation:
|
|
40
|
+
lines.append(
|
|
41
|
+
f"- Previous routing recommendation: {self.routing_recommendation}"
|
|
42
|
+
)
|
|
43
|
+
lines.extend([
|
|
44
|
+
"",
|
|
45
|
+
"Scope rules for this fix run live in the implementation sidecars "
|
|
46
|
+
"(`_implementation-verifier.md` § Fix-run incremental scope, "
|
|
47
|
+
"`report-writer.md` § Fix-run incremental authoring).",
|
|
48
|
+
"Lead duties: transcribe this block verbatim into every CLI "
|
|
49
|
+
"executor/verifier prompt (CLI processes cannot read this file), "
|
|
50
|
+
"and pass the previous data.json path above to the report-writer "
|
|
51
|
+
"dispatch so it authors incrementally.",
|
|
52
|
+
])
|
|
53
|
+
return "\n".join(lines)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def derive_stage_fix_carry(
|
|
57
|
+
stage_run_dir: Path, worktree_path: str,
|
|
58
|
+
) -> StageFixCarry | None:
|
|
59
|
+
report = _latest_report(Path(stage_run_dir))
|
|
60
|
+
if report is None:
|
|
61
|
+
return None
|
|
62
|
+
try:
|
|
63
|
+
data = json.loads(report.read_text(encoding="utf-8"))
|
|
64
|
+
except (OSError, json.JSONDecodeError):
|
|
65
|
+
return None
|
|
66
|
+
impl = data.get("implementation")
|
|
67
|
+
if not isinstance(impl, dict):
|
|
68
|
+
return None
|
|
69
|
+
failed = [
|
|
70
|
+
row for row in impl.get("verifierResults") or []
|
|
71
|
+
if isinstance(row, dict) and row.get("verdict") == "FAIL"
|
|
72
|
+
]
|
|
73
|
+
if not failed:
|
|
74
|
+
return None
|
|
75
|
+
head = _git_head(worktree_path)
|
|
76
|
+
if not head:
|
|
77
|
+
return None
|
|
78
|
+
return StageFixCarry(
|
|
79
|
+
prev_report_path=str(report),
|
|
80
|
+
prev_report_seq=_REPORT_RE.search(report.name).group(1),
|
|
81
|
+
prev_head_commit=head,
|
|
82
|
+
failed_verifiers=[str(row.get("verifier", "")) for row in failed],
|
|
83
|
+
blocking_findings=[
|
|
84
|
+
str(row.get("discrepancy") or row.get("verifier") or "")
|
|
85
|
+
for row in failed
|
|
86
|
+
],
|
|
87
|
+
routing_recommendation=str(impl.get("routingRecommendation") or ""),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _latest_report(stage_run_dir: Path) -> Path | None:
|
|
92
|
+
reports_dir = stage_run_dir / "reports"
|
|
93
|
+
if not reports_dir.is_dir():
|
|
94
|
+
return None
|
|
95
|
+
candidates = sorted(
|
|
96
|
+
(p for p in reports_dir.iterdir() if _REPORT_RE.search(p.name)),
|
|
97
|
+
key=lambda p: _REPORT_RE.search(p.name).group(1),
|
|
98
|
+
)
|
|
99
|
+
return candidates[-1] if candidates else None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _git_head(worktree_path: str) -> str:
|
|
103
|
+
if not worktree_path:
|
|
104
|
+
return ""
|
|
105
|
+
try:
|
|
106
|
+
out = subprocess.run(
|
|
107
|
+
["git", "-C", worktree_path, "rev-parse", "HEAD"],
|
|
108
|
+
capture_output=True, text=True, check=False,
|
|
109
|
+
)
|
|
110
|
+
except OSError:
|
|
111
|
+
return ""
|
|
112
|
+
return out.stdout.strip() if out.returncode == 0 else ""
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
],
|
|
147
147
|
"additionalProperties": false,
|
|
148
148
|
"properties": {
|
|
149
|
-
"finalConclusion": { "type": "string", "minLength": 1 },
|
|
149
|
+
"finalConclusion": { "type": "string", "minLength": 1, "description": "Conclusion SSOT for the whole report: at most 3 sentences. Other prose blocks reference this instead of restating it." },
|
|
150
150
|
"verdictToken": { "$ref": "#/$defs/VerdictToken" },
|
|
151
151
|
"direction": { "$ref": "#/$defs/Direction" },
|
|
152
152
|
"approvalRequired": { "type": "boolean" },
|
|
@@ -176,14 +176,14 @@
|
|
|
176
176
|
|
|
177
177
|
"rationale": {
|
|
178
178
|
"type": "object",
|
|
179
|
-
"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.",
|
|
179
|
+
"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. Prose budget: at most 2 sentences per field; reference verdictCard.finalConclusion and row IDs instead of restating them.",
|
|
180
180
|
"required": ["motivation", "problem", "approach", "justification"],
|
|
181
181
|
"additionalProperties": false,
|
|
182
182
|
"properties": {
|
|
183
|
-
"motivation": { "type": "string", "minLength": 1, "description": "왜 이 작업을 하는가 — 목표·맥락." },
|
|
184
|
-
"problem": { "type": "string", "minLength": 1, "description": "왜 이게 문제인가 — 현재 상태의 결함을 증거로." },
|
|
185
|
-
"approach": { "type": "string", "minLength": 1, "description": "그래서 어떤 작업이 필요한가 — 택한 방향." },
|
|
186
|
-
"justification": { "type": "string", "minLength": 1, "description": "왜 이게 합리적 선택인가 — 대안 대비 근거." }
|
|
183
|
+
"motivation": { "type": "string", "minLength": 1, "description": "왜 이 작업을 하는가 — 목표·맥락. ≤2 sentences." },
|
|
184
|
+
"problem": { "type": "string", "minLength": 1, "description": "왜 이게 문제인가 — 현재 상태의 결함을 증거로. ≤2 sentences." },
|
|
185
|
+
"approach": { "type": "string", "minLength": 1, "description": "그래서 어떤 작업이 필요한가 — 택한 방향. ≤2 sentences." },
|
|
186
|
+
"justification": { "type": "string", "minLength": 1, "description": "왜 이게 합리적 선택인가 — 대안 대비 근거. ≤2 sentences." }
|
|
187
187
|
}
|
|
188
188
|
},
|
|
189
189
|
|