okstra 0.143.0 → 0.145.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.
Files changed (46) hide show
  1. package/README.md +4 -1
  2. package/docs/architecture.md +18 -2
  3. package/docs/cli.md +39 -2
  4. package/docs/project-structure-overview.md +19 -6
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/prompts/coding-preflight/overview.md +1 -1
  8. package/runtime/prompts/lead/convergence.md +11 -3
  9. package/runtime/prompts/lead/okstra-lead-contract.md +7 -1
  10. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  11. package/runtime/prompts/profiles/_common-contract.md +1 -1
  12. package/runtime/prompts/profiles/_implementation-verifier.md +48 -2
  13. package/runtime/prompts/profiles/change-impact-analysis.md +24 -0
  14. package/runtime/prompts/profiles/feature-analysis.md +24 -0
  15. package/runtime/prompts/profiles/forbidden-actions.json +18 -0
  16. package/runtime/prompts/profiles/project-analysis.md +24 -0
  17. package/runtime/prompts/wizard/prompts.ko.json +44 -1
  18. package/runtime/python/okstra_ctl/analysis_inputs.py +369 -0
  19. package/runtime/python/okstra_ctl/clarification_items.py +74 -1
  20. package/runtime/python/okstra_ctl/mutation_probe.py +1263 -0
  21. package/runtime/python/okstra_ctl/render.py +77 -4
  22. package/runtime/python/okstra_ctl/render_final_report.py +13 -4
  23. package/runtime/python/okstra_ctl/report_views.py +134 -3
  24. package/runtime/python/okstra_ctl/run.py +118 -0
  25. package/runtime/python/okstra_ctl/run_context.py +34 -2
  26. package/runtime/python/okstra_ctl/schema_excerpt.py +12 -4
  27. package/runtime/python/okstra_ctl/self_mock_signals.py +183 -0
  28. package/runtime/python/okstra_ctl/user_response.py +309 -3
  29. package/runtime/python/okstra_ctl/wizard.py +545 -32
  30. package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -0
  31. package/runtime/python/okstra_ctl/workflow.py +22 -0
  32. package/runtime/schemas/final-report-v1.0.schema.json +849 -3
  33. package/runtime/skills/okstra-run/SKILL.md +13 -1
  34. package/runtime/templates/reports/change-impact-analysis-input.template.md +58 -0
  35. package/runtime/templates/reports/feature-analysis-input.template.md +59 -0
  36. package/runtime/templates/reports/final-report.template.md +220 -0
  37. package/runtime/templates/reports/i18n/en.json +8 -0
  38. package/runtime/templates/reports/i18n/ko.json +8 -0
  39. package/runtime/templates/reports/project-analysis-input.template.md +58 -0
  40. package/runtime/templates/reports/report.js +84 -5
  41. package/runtime/templates/reports/user-response.template.md +19 -1
  42. package/runtime/validators/detect_self_mock.py +220 -0
  43. package/runtime/validators/validate-report-views.py +61 -7
  44. package/runtime/validators/validate-run.py +518 -0
  45. package/runtime/validators/validate_analysis_report.py +864 -0
  46. package/src/commands/execute/render-bundle.mjs +3 -0
package/README.md CHANGED
@@ -190,11 +190,13 @@ To start a task outside a Claude Code session:
190
190
  --project-id <id> \
191
191
  --task-group <group> \
192
192
  --task-id <id> \
193
- --task-type <requirements-discovery|improvement-discovery|error-analysis|implementation-planning|implementation|final-verification|release-handoff> \
193
+ --task-type <requirements-discovery|improvement-discovery|project-analysis|change-impact-analysis|error-analysis|implementation-planning|implementation|final-verification|release-handoff> \
194
194
  --base-ref <branch|tag|sha> \
195
195
  --task-brief ./brief.md
196
196
  ```
197
197
 
198
+ `feature-analysis` is intentionally omitted from the standalone shell example. Start it with `/okstra-run`: the wizard collects its required feature target and passes that value through the internal Node render boundary. Standalone `okstra.sh` accepts neither `--analysis-target` nor `--evidence-inputs`.
199
+
198
200
  This starts a new `claude` process in the lead role. For the complete argument list, see `okstra.sh --help` or [`docs/cli.md`](docs/cli.md).
199
201
 
200
202
  Notable flags added in 0.7.0 / 0.8.0:
@@ -217,6 +219,7 @@ Major workflow changes added to `main` after 0.8.0:
217
219
  - **Artifact-home rule (`.okstra/`)** — `<project>/.okstra/` is the only project artifact root owned by okstra. Anything outside this root is not okstra memory and may be read only when explicitly cited in Source Material or Reporter Confirmations. Writing outside the root requires the same explicit requested path. Internal equivalents are `glossary.md` for terminology and `decisions/<NNNN>-<slug>.md` for decision records, evaluated during `implementation-planning`.
218
220
  - **Self-contained HTML final-report view** — After Phase 7 writes `final-report-<task-type>-<seq>.md`, `okstra render-views` automatically creates a sibling self-contained HTML view in the same `reports/` directory, with inline CSS/JavaScript and no external URLs. Its `Export user response` button serializes `## 1. Clarification Items` responses to `runs/<task-type>/user-responses/user-response-<task-type>-<seq>.md` for the next phase. View generation never changes the source Markdown.
219
221
  - **`improvement-discovery` task type (sidetrack entry point)** — Within a codebase scope and priority-lens allowlist, multi-worker consensus produces N improvement candidates, with a default of eight and a hard cap of 12. This is a sidetrack entry point outside `PHASE_SEQUENCE`; the user selects candidates and starts each under a new task ID with `requirements-discovery`, `implementation-planning`, or `error-analysis`. Lens enum SSOT: [`scripts/okstra_ctl/improvement_lenses.py`](scripts/okstra_ctl/improvement_lenses.py). Output section: `## 5.9 Improvement Candidates` (11-column table). Validator: [`validators/validate_improvement_report.py`](validators/validate_improvement_report.py).
222
+ - **Read-only analysis task types (independent sidetracks)** — `project-analysis` maps the current project's components, dependencies, entry points, data stores, external systems, and feature index. `feature-analysis` traces one existing feature through flows, domain rules, state changes, integrations, and test coverage. `change-impact-analysis` maps the blast radius of a proposed change across preserved behavior, dependencies, tests, and operations. These are independent sidetracks outside `PHASE_SEQUENCE`, not lifecycle phases. No edits, tests, builds, migrations, or deployments are allowed against the target project. Start analysis runs through `/okstra-run`; its wizard owns target and evidence collection before calling the internal Node render command. The final report remains immutable: the HTML `Analysis Review` records accept, revision, or reject in a user-response sidecar. A revision request prioritizes a same-task, same-type full rerun; that rerun reanalyzes the whole confirmed scope and resolves every affected report ID instead of patching only the disputed rows. Input details: [`docs/cli.md`](docs/cli.md#analysis-sidetrack-task-types).
220
223
 
221
224
  <a id="ops-commands"></a>
222
225
  ### 3.5 Operations commands
@@ -134,7 +134,7 @@ Runtime entry points are consolidated in Python packages. Bash and skills only c
134
134
  ### Runtime assets (templates + lead resources)
135
135
 
136
136
  - `prompts/launch.template.md` — lead prompt template.
137
- - `prompts/profiles/*.md` — six task-type profiles (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`).
137
+ - `prompts/profiles/*.md` — ten task-type profiles: the six lifecycle profiles (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`) plus `improvement-discovery`, `project-analysis`, `feature-analysis`, and `change-impact-analysis` sidetracks.
138
138
  - `templates/project-docs/task-index.template.md` · `templates/reports/final-report.template.md` · `templates/reports/settings.template.json` — runtime render inputs.
139
139
  - `<PROJECT_ROOT>/.okstra/project.json` — project self-registration. Created/verified automatically on the first okstra.sh run; when `--project-root` is omitted, PROJECT_ROOT is resolved through ancestors / `git toplevel`.
140
140
 
@@ -428,6 +428,9 @@ Each task type enforces phase-specific allowed and forbidden actions. A run crea
428
428
  | `implementation` | Modify source code according to the approved `implementation-planning` final report. **One run executes exactly one stage** (selected with `--stage <auto\|N>`) | commit list, diff summary, out-of-plan edits block, validation/TDD evidence, rollback verification, verifier results (Antigravity/Codex/Claude), `carry/stage-<N>.json` evidence sidecar | `final-verification` | Yes (limited to the approved plan's file list; `git push`/publish/deploy/real migration prohibited) |
429
429
  | `final-verification` | Check completed work for residual defects and regression risk, then make a release judgment | acceptance verdict, residual risk, follow-up routing (`error-analysis`/`implementation-planning`/`release-handoff`) | `pending-release-handoff` (enters `release-handoff` only when the verdict is `accepted`; otherwise reroutes to `error-analysis` or `implementation-planning`) | No (read-only tests only) |
430
430
  | `release-handoff` | Deliver `accepted` changes as a commit, push, or PR according to the user's chosen method | user menu responses (H1 action / H2 PR base / H3 message handling), executed git/gh command log, commit SHA list, PR URL | `done-or-follow-up` | Yes—but execute **only the mutating commands selected by the user in the menu**. `git push --force*`, direct push to the base branch, `--no-verify`, `gh release`, and publish/deploy are prohibited. The source code itself must not be changed; package the existing `implementation` diff unchanged. |
431
+ | `project-analysis` | Map the current project structure and feature index | components, dependencies, entry points, data stores, external systems, feature index | `pending-routing-decision` | No (strictly read-only; tests are also prohibited) |
432
+ | `feature-analysis` | Trace one confirmed existing feature | flows, domain rules, state changes, external interactions, test coverage | `pending-routing-decision` | No (strictly read-only; tests are also prohibited) |
433
+ | `change-impact-analysis` | Map the impact of one proposed change | preserved behavior, impact items, dependency blast radius, test and operational impact | `pending-routing-decision` | No (strictly read-only; tests are also prohibited) |
431
434
 
432
435
  Common constraints:
433
436
 
@@ -485,6 +488,16 @@ The stage-group interaction order is: **G1 select base → G2 confirm stages (se
485
488
 
486
489
  A sidetrack entry point that is not a formal member of `PHASE_SEQUENCE`. It supports codebase-discovery scenarios without breaking the one-way lifecycle. The lens allowlist and candidate cap are consolidated in the single source of truth `scripts/okstra_ctl/improvement_lenses.py`. `validators/validate_improvement_report.py` checks eleven contract items against the final report; one of them is the shape of the `## 5.9 Improvement Candidates` table, whose eleven columns run from `Cand ID` through `Evidence` (the two counts are independent and happen to coincide). Two bidirectional grilling points—an enhanced budget of 8 in `okstra-brief-gen` Step 4 and the lead's Phase 1.5 reflect-back budget of 12—align the user's and AI's understanding.
487
490
 
491
+ ### Read-only analysis sidetracks
492
+
493
+ `project-analysis`, `feature-analysis`, and `change-impact-analysis` are independent entry points outside `PHASE_SEQUENCE`. They share the normal task identity and report pipeline but never route into one another automatically. All three are read-only with respect to the target project: workers may inspect the confirmed scope but may not edit files, run tests or builds, execute migrations, or deploy.
494
+
495
+ `analysis_inputs.py` is the shared resolution boundary for both the wizard and `prepare_task_bundle()` (`scripts/okstra_ctl/analysis_inputs.py`). It owns the three-type allowlist, the permitted evidence relationships, report identity checks, review eligibility, source-commit freshness, and `PF-NNN` feature-index target resolution. Prepare freezes the result in `run-manifest.evidenceInputs` and `run-manifest.analysisTarget`; `validators/validate_analysis_report.py` requires the report's `analysisCommon.evidenceInputs` and resolved scope to match those snapshots exactly. The stored freshness value is `exact` when the evidence source commit is current and `stale` otherwise.
496
+
497
+ The self-contained report view presents an `Analysis Review` control. The browser's Export action downloads the Markdown sidecar; it cannot write into the project filesystem. The user must save or move that download into the canonical `runs/<task-type>/user-responses/user-response-<task-type>-<seq>.md` location before a later run can consume it. The downloaded sidecar contains a block named `ANALYSIS REVIEW` (Markdown heading `## ANALYSIS REVIEW`) without mutating the original report. It records `accepted`, `revision-requested`, or `rejected`; only accepted reports are offered automatically as later evidence. An explicitly selected unreviewed report is marked `user-unverified`, while revision-requested and rejected reports are refused as evidence.
498
+
499
+ A revision request supplies affected report IDs and a reason. The wizard prioritizes the same task type on the same task, and carries that report into a full rerun. The rerun reanalyzes the whole confirmed scope rather than only the disputed rows, then writes `analysisReviewResolution` under `analysisCommon` for every affected ID. `validators/validate_analysis_report.py` checks the source report identity, task type, run sequence, exact affected-ID coverage, and each resolution outcome. Report verdict (`analysis-complete`, `analysis-partial`, or `blocked`), review status, evidence freshness, and direct user verification (`user-unverified`) remain separate axes.
500
+
488
501
  ### requirements-discovery fan-out
489
502
 
490
503
  For mixed or multi-item requests, requirements-discovery splits the request into packets by domain (the five-value work-category enum) and publishes them to `runs/requirements-discovery/fan-out/unit-*.md`. Each packet becomes a new task key through `okstra-run --task-brief <path>`. The dependency topological order is recorded in `index.md`, and okstra-schedule-gen owns the integrated schedule after task creation. okstra-brief-gen is not involved in this path. Validation: `validators/validate_fanout.py` (validate-run hook).
@@ -504,7 +517,7 @@ The complete specification for `okstra`'s three storage areas (stable task root
504
517
 
505
518
  `okstra` is brief-first. The brief is the canonical source material that preserves external input and okstra augmentations. Any additional material workers need—reports, code snippets, logs, and so on—must be included inline or by path in the brief's `Evidence and Source Materials` section.
506
519
 
507
- Briefs are accepted as direct input **only for entry phases**: users provide a brief path for `requirements-discovery`, `error-analysis`, and `improvement-discovery`. Downstream phases (implementation-planning / implementation / final-verification) automatically carry in the task manifest's `taskBriefPath` (the okstra-run wizard does not ask for it; if it is unregistered, a fallback picker recommends switching to an entry phase). `release-handoff` has no brief; prepare generates an input document that cites verification reports.
520
+ Briefs are accepted as direct input **only for entry phases**: users provide a brief path for `requirements-discovery`, `error-analysis`, `improvement-discovery`, `project-analysis`, `feature-analysis`, and `change-impact-analysis`. Downstream phases (implementation-planning / implementation / final-verification) automatically carry in the task manifest's `taskBriefPath` (the okstra-run wizard does not ask for it; if it is unregistered, a fallback picker recommends switching to an entry phase). `release-handoff` has no brief; prepare generates an input document that cites verification reports.
508
521
 
509
522
  A brief is a **translation layer**: it converts external input—an issue-tracker ticket, requirements document, or user message—into an okstra-readable format while preserving the original verbatim and clearly distinguishing okstra additions as labelled augmentation. The output of the `okstra-brief-gen` skill is the source of truth. For each analysis phase, `prepare_task_bundle()` extracts the necessary frontmatter, task-specific brief sections, reference expectations, carried-in clarification, and directive into `instruction-set/analysis-packet.md`. This compact packet is the analysis workers' primary input; the original brief and profile/material files are fallback evidence opened only to verify evidence or fill omissions.
510
523
 
@@ -529,6 +542,9 @@ Default templates:
529
542
  - `templates/reports/quick-input.template.md`
530
543
  - `templates/reports/task-brief.template.md`
531
544
  - `templates/reports/error-analysis-input.template.md`
545
+ - `templates/reports/project-analysis-input.template.md`
546
+ - `templates/reports/feature-analysis-input.template.md`
547
+ - `templates/reports/change-impact-analysis-input.template.md`
532
548
  - `templates/reports/implementation-planning-input.template.md`
533
549
  - `templates/reports/implementation-input.template.md`
534
550
  - `templates/reports/final-verification-input.template.md`
package/docs/cli.md CHANGED
@@ -21,6 +21,8 @@
21
21
  - [`--clarification-response`](#--clarification-response)
22
22
  - [`--resume-clarification`](#--resume-clarification)
23
23
  - [`--project-root`](#--project-root)
24
+ - [`--analysis-target`](#--analysis-target)
25
+ - [`--evidence-inputs`](#--evidence-inputs)
24
26
  - [`--directive`](#--directive)
25
27
  - [`--fix-cycle`](#--fix-cycle)
26
28
  - [`--workers`](#--workers)
@@ -59,6 +61,8 @@ Base command for initial entry with full arguments:
59
61
  scripts/okstra.sh [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--lead-runtime claude-code|codex] [--lead-model <model>] [--claude-model <model>] [--codex-model <model>] [--antigravity-model <model>] [--report-writer-model <model>] [--executor claude|codex|antigravity] [--critic off|claude|codex|antigravity] [--related-tasks taskA,taskB] [--work-category bugfix|feature|refactor|ops|improvement|unknown] [--base-ref <branch|tag|sha>] [--clarification-response <previous-final-report>] [--approved-plan <plan-path>] [--approve] --project-id <project-id> --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
60
62
  ```
61
63
 
64
+ Analysis input ownership is narrower than the base shell command. The `/okstra-run` wizard collects `--analysis-target` and `--evidence-inputs` values and passes them internally to `node bin/okstra render-bundle`. `scripts/okstra.sh` does not accept either flag. Because `feature-analysis` requires a target, start that task type with `/okstra-run`; the two option sections below document the internal Node render inputs, not standalone shell options.
65
+
62
66
  Short form for a later phase when an existing task-manifest.json is available:
63
67
 
64
68
  ```bash
@@ -133,13 +137,46 @@ For standard values and phase-specific responsibilities, see [Task type](#--task
133
137
  - Validator: `validators/validate_improvement_report.py` enforces the 11-part contract for an `improvement-discovery` final report.
134
138
  - Because an `improvement-discovery` run is not in `PHASE_SEQUENCE`, the `--task-key` short form does not automatically populate `nextRecommendedPhase` for it.
135
139
 
140
+ #### Analysis sidetrack task types
141
+
142
+ The three read-only analysis types are independent sidetracks outside `PHASE_SEQUENCE`:
143
+
144
+ | task type | Purpose |
145
+ |---|---|
146
+ | `project-analysis` | Map the current project's components, dependencies, entry points, data stores, external systems, and feature index. |
147
+ | `feature-analysis` | Trace one existing feature through flows, domain rules, state changes, external interactions, and test coverage. |
148
+ | `change-impact-analysis` | Map the blast radius of a proposed change across preserved behavior, dependencies, tests, and operations. |
149
+
150
+ Each one starts from a brief and produces a report only. The target project is strictly read-only: no edits, tests, builds, migrations, or deployments. A run ends at `pending-routing-decision`; it does not advance the normal phase sequence.
151
+
152
+ ### `--analysis-target`
153
+
154
+ At the `node bin/okstra render-bundle` boundary, `--analysis-target` is required only for `feature-analysis`. It accepts either a free-text feature boundary or a `PF-NNN` ID from a selected `project-analysis` feature index. A `PF-NNN` value must match exactly one feature in the selected project-context evidence. `project-analysis`, `change-impact-analysis`, and non-analysis task types reject this render input.
155
+
156
+ ### `--evidence-inputs`
157
+
158
+ At the `node bin/okstra render-bundle` boundary, `--evidence-inputs` accepts a comma-separated list of prior analysis final-report paths. Relative paths resolve from the target project root, selection order is preserved, and duplicate paths are rejected. The allowed relationships are:
159
+
160
+ | Consumer and source | Stored relation |
161
+ |---|---|
162
+ | `feature-analysis` ← `project-analysis` | `project-context` |
163
+ | `change-impact-analysis` ← `project-analysis` | `project-context` |
164
+ | `change-impact-analysis` ← `feature-analysis` | `feature-baseline` |
165
+
166
+ `project-analysis` accepts no evidence inputs. `feature-analysis` and `change-impact-analysis` may omit evidence, but every supplied report must match one of the relationships above.
167
+
168
+ Only `accepted` reports appear in automatic evidence choices. An explicitly selected, otherwise valid unreviewed report is recorded as `user-unverified`; `revision-requested` and `rejected` reports are rejected as evidence. A revision request instead makes the wizard prioritize a same-task, same-type full rerun. The selection is snapshotted in `run-manifest.evidenceInputs` with source task/run identity, relation, review status, source commit, and freshness.
169
+
170
+ Analysis reports expose four independent meanings. The report verdict is `analysis-complete`, `analysis-partial`, or `blocked`. The `Analysis Review` sidecar status is `accepted`, `revision-requested`, or `rejected`. Evidence freshness is current (`exact` in the stored snapshot) or `stale`. Direct user verification of an explicitly selected unreviewed report is `user-unverified`. These are separate axes: for example, a complete report can still be rejected by its reviewer, and accepted evidence can become stale after the source commit changes.
171
+
136
172
  ### `--task-brief`
137
173
 
138
174
  The path to the task brief that serves as the basis for analysis.
139
175
  Relative paths are resolved from the target project root.
140
176
 
141
177
  This argument does not apply to `release-handoff`: a brief is input to an entry phase
142
- (requirements-discovery / error-analysis / improvement-discovery), while
178
+ (requirements-discovery / error-analysis / improvement-discovery / project-analysis /
179
+ feature-analysis / change-impact-analysis), while
143
180
  release-handoff preparation automatically creates an input document that cites the verification report
144
181
  at `<task_root>/release-handoff-input.md` and uses it in place of a brief.
145
182
  A non-empty `--task-brief` is rejected immediately for release-handoff.
@@ -687,7 +724,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
687
724
  | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, then prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order, which matters because `validate-run` is the step that usually fails and retrying it otherwise repeats the three steps before it at full token and wall-clock cost. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) the Codex lead adapter runs automatically after its report-writer completes, so a Claude-led and a Codex-led run finalize identically. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the four steps individually |
688
725
  | `okstra render-views <final-report.md>` | The Phase 7 `render-views` step, runnable on its own: deterministically create a human-facing self-contained sibling `*.html` view from one final-report Markdown file after token substitution. The source Markdown is unchanged. The Node delegation wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies form-control placement, absence of external URLs, stale source digests, and Response ID parity |
689
726
  | `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
690
- | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, and improvement-discovery. Downstream phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
727
+ | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
691
728
  | `okstra token-usage ...` | Wrap the installed `okstra-token-usage.py` to collect and substitute run token usage. Session JSONL is incrementally scanned by default through a byte-cursor cache at `$OKSTRA_HOME/cache/token-usage/`; `--no-cache` bypasses the cache and forces a full rescan as an accuracy fallback |
692
729
 
693
730
  The convergence state lifecycle is `groups v1.0 → work v1.0 → final v1.3`; round-plan, round-results, and optional critic-results v1.0 artifacts provide the auditable transitions between those endpoints.
@@ -243,7 +243,8 @@ Important modules:
243
243
  | `incremental_carry.py` | carry merge for an incremental re-run — merges the previous run's plan-item verdicts that this run does not re-verify into the current data.json with a `carriedForwardFromSeq` tag. On `schemaVersion` drift it exits non-zero with `CarryError` to force a full fallback. CLI: `okstra incremental-carry` |
244
244
  | `build_tools.py` | allowlist SSOT for deciding whether a plan's command cell invokes the project build toolchain (`npm`/`pytest`/`cargo`/`gradle`/… behind transparent leaders like `sudo`/`env`). The planning worktree has no dependencies installed, so `validators/validate-run.py` uses this to warn (advisory) when a toolchain stage declares no install precondition. Intentionally an allowlist, not a denylist, so unknown tokens go undetected rather than firing on `grep`/`sed` in every plan |
245
245
  | `stage_citations.py` | shared grammar SSOT for reading the Stage Map stage numbers a prose cell cites (`Stages 1, 2, and 3`, ranges, etc.). One definition serves two readers that must not drift — the coverage check in `validators/validate-run.py` proving every stage traces to a requirement, and `incremental_scope.py`'s back-trace resolving which stages an answered clarification touches |
246
- | `self_mock_signals.py` | self-mock signal SSOT — language-keyed regexes (`SIGNALS`), the `EXT_TO_LANG` extension map, and `selfmock_path_key` (the one path-normalization the coverage check and the waiver matcher both share). The signals are each ported from a `prompts/coding-preflight/languages/<lang>.md` "Self-mock signals to refuse" bullet with the source `doc_keyword` retained so a drift guard fails when doc and module diverge. Patterns stay deliberately narrow (only the "stub the subject's own method, then assert the stub" shape and reaching into the subject's privates; subject identity is never inferred beyond the literal `sut` token). Both the static detector `validators/detect_self_mock.py` and the drift guard MUST import from here; four documented shapes needing subject identity no regex has are left to the mutation gate |
246
+ | `self_mock_signals.py` | self-mock signal SSOT — language-keyed regexes (`SIGNALS`), the `EXT_TO_LANG` extension map, and the waiver-matching mechanics both gates share — `selfmock_path_key` (the one path-normalization), `waiver_entry_key` (the `(file, line, <discriminator>)` triple, with the hand-typed line coerced to `int`) and `partition_waived_entries` (the split into still-failing vs waived). Gate A passes the discriminator `signal`, gate B `mutant`; one definition means the two cannot disagree about whether a waiver matches a finding. The signals are each ported from a `prompts/coding-preflight/languages/<lang>.md` "Self-mock signals to refuse" bullet with the source `doc_keyword` retained so a drift guard fails when doc and module diverge. Patterns stay deliberately narrow (only the "stub the subject's own method, then assert the stub" shape and reaching into the subject's privates; subject identity is never inferred beyond the literal `sut` token). The static detector `validators/detect_self_mock.py`, the drift guard and `mutation_probe.py` MUST import from here; four documented shapes needing subject identity no regex has are left to the mutation gate (`mutation_probe.py`) |
247
+ | `mutation_probe.py` | gate B of the self-mock gate — the tool-agnostic mutation probe. `ADAPTERS` maps an `EXT_TO_LANG` language key to an adapter (`ts_js` → Stryker, `rust` → cargo-mutants, `java`/`kotlin` → PIT, which reports `unsupported` because its SCM scoping is a Maven-only goal and the report↔path mapping is unverified). `run_probe` owns everything that must not differ between tools: production-source selection, the refusal to run on an empty target set, the requirement that the diff name EVERY changed source, the adapter result-shape check and the user-acknowledged waiver application; adapters only parse. `evaluate` counts a mutant only when it covers a line the diff added or modified, and records the pre-cap `survivedTotal` so a trimmed report cannot be fully waived to PASS. Anything that stops a real inspection — no adapter, tool not installed, unreadable report, unknown outcome word, no conclusive trial, a diff that misses a changed source — answers `unsupported(<reason>)`, never `PASS`. `classify_reason` is the 3-class SSOT (capability-gap / nothing-to-verify / integrity-inspection, unknown → integrity) read by BOTH the cross-language merge here and the blocking decision in `validators/validate-run.py` |
247
248
  | `run_context.py` | Per-task mutex, run context and run-input persistence; `consumers_mutex` helper for atomic `consumers.jsonl` writes |
248
249
  | `path_hints.py` | Compact path-hint persistence + legacy context hydration — stores `run-context` / `active-run-context` in the schemaVersion `2.0` `identity` + `pathHints` compact schema, and hydrates the legacy flat path keys (`RUN_MANIFEST_RELATIVE_PATH`, `TEAM_STATE_PATH`, etc.) in memory the moment the host-side reader reads them |
249
250
  | `consumers.py` | Append-only `consumers.jsonl` writer + reader — records which `implementation` runs consumed which `implementation-planning` stage |
@@ -292,6 +293,8 @@ Important modules:
292
293
  | `dispatch_core.py` | Backend-neutral worker dispatch core — worker execution/collection logic shared by any lead runtime (Claude/Codex/external); gates selected initial prompts through the shared cross-task contract before launch |
293
294
  | `codex_dispatch.py` | Codex lead CLI-worker dispatcher — the `okstra codex-dispatch` backend. Reads the run manifest to run the Codex-side supported worker subset, applies the same cross-task initial-prompt gate, and performs token-usage substitution, view render, follow-up, and validation |
294
295
  | `analysis_packet.py` | assembles the compact analysis-worker input packet for a task run from worker-owned profile sections; report/lead procedure stays outside the packet |
296
+ | `analysis_inputs.py` | shared input boundary for `project-analysis`, `feature-analysis`, and `change-impact-analysis` — validates evidence-report identity and review status, enforces the type-to-type relation allowlist, computes `exact`/`stale` freshness, and resolves free-text or `PF-NNN` feature targets for both wizard and prepare paths |
297
+ | `user_response.py` | parses clarification/approval responses and the analysis-review sidecar; `parse_analysis_review` validates accepted, revision-requested, and rejected decisions plus their affected IDs and reason |
295
298
  | `context_cost.py` | read-side context-cost estimator for a prepared okstra task bundle (the `okstra context-cost` backend) |
296
299
  | `schema_excerpt.py` | generates a task-type-scoped excerpt of the final-report schema — a schema reduction to inject into the worker/lead prompt |
297
300
  | `work_categories.py` | requirements-discovery work-category (domain) **SSOT** (`is_valid_category`) — the work-category allowlist is defined only here |
@@ -343,6 +346,7 @@ Token/cost accounting:
343
346
  | `launch.template.md` | Lead prompt template rendered for each run |
344
347
  | `profiles/_common-contract.md` | Shared phase contract |
345
348
  | `profiles/<task-type>.md` | Phase profiles (single language — runtime always loads from `profiles/`, never a translated mirror) |
349
+ | `project-analysis.md`, `feature-analysis.md`, `change-impact-analysis.md` | Read-only sidetrack profiles for project mapping, one-feature behavior tracing, and proposed-change impact mapping |
346
350
  | `wizard/prompts.ko.json` | Korean wizard prompt single source of truth |
347
351
 
348
352
  ### 4.7 `templates/`
@@ -352,6 +356,8 @@ Token/cost accounting:
352
356
  | `templates/reports/final-report.template.md` | Jinja2 final-report Markdown template |
353
357
  | `templates/reports/report.css`, `report.js` | Inline assets for self-contained HTML report view |
354
358
  | `templates/reports/*.template.md` | Inputs, schedule, user-response, settings templates |
359
+ | `project-analysis-input.template.md`, `feature-analysis-input.template.md`, `change-impact-analysis-input.template.md` | Brief input templates for the three analysis sidetracks |
360
+ | `user-response.template.md`, `report.js` | Analysis Review sidecar block and the browser control that exports accept/revision/reject without changing the source report |
355
361
  | `templates/project-docs/task-index.template.md` | Project task index template |
356
362
  | `templates/worker-prompt-preamble.md` | Initial analysis audience procedure and output contract |
357
363
  | `templates/implementation-worker-preamble.md` | Shared implementation executor/verifier procedure, including coding-preflight and worktree rules |
@@ -376,10 +382,11 @@ Optional (v1.0 backward-compatible) top-level keys:
376
382
  | `validate-run.py` | Run/final-report contract validation |
377
383
  | `validate-brief.py`, `validate-brief.sh` | Brief frontmatter/body contract validation |
378
384
  | `validate-report-views.py` | HTML view validation (form-control placement / no external URLs / stale source digest / Response ID parity) |
385
+ | `validate_analysis_report.py` | Cross-field validation for the three read-only analysis reports: frozen target/evidence snapshots, current-code evidence, review-source identity, and exact affected-ID resolution coverage on revision reruns |
379
386
  | `validate-schedule.py` | Schedule section/order/code validation |
380
387
  | `validate-implementation-plan-stages.py` | enforces the Stage Map structure — checks the S1–S8 rules (`## 5.5 Stage Map` + `## 5.5.<i> Stage <i>` sections, ≤ 8 steps per stage, etc.) |
381
388
  | `validate_improvement_report.py` | enforces the 11-item contract of the improvement-discovery final-report. Automatically invoked by `validate-run.py` when `task_type == "improvement-discovery"` |
382
- | `detect_self_mock.py` | static self-mock detector — scans the changed TEST files for SUT-stub signals (patterns imported from the SSOT `scripts/okstra_ctl/self_mock_signals.py`, never redefined here), matching each file as one whole-file string so multi-line signals are caught. Writes a `qa/self-mock[-stage-<N>].json` sidecar and prints `QA-RESULT: PASS|FAIL` as its last line (exit 0 = no hits, exit 1 = at least one hit). The sidecar records `scannedFiles`/`skippedFiles` so the gate can prove every changed test file was actually scanned (a run that skips them cannot pass on empty input). An optional `--waivers <path>` moves hits matching `(file,line,signal)` from `staticDetect.hits` to `staticDetect.waived` (each carrying the user's `reason`/`acknowledgedBy`) and records the file as `waiverSource`. Its verdict feeds the fail-closed `_validate_selfmock` gate in `validate-run.py` (implementation / final-verification): a diff that touches test files with no readable PASS sidecar blocks the run; a `waived` entry missing `reason`/`acknowledgedBy`, or a `waiverSource` that is not the task's own `qa/self-mock-waivers.json`, also blocks |
389
+ | `detect_self_mock.py` | self-mock detector — runs BOTH gates and writes the run's sidecar. Gate A (static) scans the changed TEST files for SUT-stub signals (patterns imported from the SSOT `scripts/okstra_ctl/self_mock_signals.py`, never redefined here), matching each file as one whole-file string so multi-line signals are caught. Writes a `qa/self-mock[-stage-<N>].json` sidecar and prints `QA-RESULT: PASS|FAIL` as its last line (exit 0 = no hits, exit 1 = at least one hit). The sidecar records `scannedFiles`/`skippedFiles` so the gate can prove every changed test file was actually scanned (a run that skips them cannot pass on empty input). An optional `--waivers <path>` moves hits matching `(file,line,signal)` from `staticDetect.hits` to `staticDetect.waived` (each carrying the user's `reason`/`acknowledgedBy`) and records the file as `waiverSource`. Gate B (mutation) runs in the same call: `--changed-file` takes the stage's WHOLE changed set (each adapter selects its own production sources out of it), `--diff` and `--worktree` scope it, and `scripts/okstra_ctl/mutation_probe.py` writes the result into the sidecar's `mutation` block; the received set is recorded as `changedFiles` so the gate can prove gate B was not handed an empty input. `overall` and the exit code follow BOTH gates — a mutation FAIL with a clean static scan still exits 1. The same `--waivers` file feeds both (gate A reads its `signal` entries, gate B its `mutant` ones). Its verdict feeds the fail-closed `_validate_selfmock` gate in `validate-run.py` (implementation / final-verification): a diff that touches test files with no readable PASS sidecar blocks the run; a `waived` entry missing `reason`/`acknowledgedBy`, or a `waiverSource` that is not the task's own `qa/self-mock-waivers.json`, also blocks |
383
390
  | `validate-workflow.sh` | End-to-end fixture workflow validation |
384
391
  | `lib/*.sh` | Shared shell validator helpers and fixtures |
385
392
 
@@ -454,11 +461,13 @@ they are not published user skills.
454
461
 
455
462
  1. Resolve project root and verify/upsert `project.json`.
456
463
  2. Resolve profile, required workers, model assignments, executor provider.
457
- 3. Compute task/run paths and persist run context under `runs/<task-type>/manifests/`.
464
+ 3. Resolve task identity segments, work category, and the run sequence input needed for path allocation.
458
465
  4. Provision or reuse the task-key worktree, or the selected implementation stage worktree for stage-isolated runs.
459
- 5. Materialize `instruction-set/` files and lead prompt snapshot.
460
- 6. Persist run inputs, team state, task manifest, task index, run manifest, timeline, discovery pointers.
461
- 7. Record the run in `~/.okstra/{active,recent}.jsonl` and project index.
466
+ 5. For an analysis sidetrack, resolve the immutable source commit from the provisioned worktree's `HEAD`, then resolve evidence reports, freshness, and the feature target through `analysis_inputs.py`.
467
+ 6. Compute task/run paths and persist run context under `runs/<task-type>/manifests/`.
468
+ 7. Materialize `instruction-set/` files and lead prompt snapshot.
469
+ 8. Persist run inputs, team state, task manifest, task index, run manifest, timeline, discovery pointers.
470
+ 9. Record the run in `~/.okstra/{active,recent}.jsonl` and project index.
462
471
 
463
472
  ### 5.2 Worktree model
464
473
 
@@ -497,6 +506,8 @@ Current report pipeline:
497
506
  6. Token usage substitution fills usage/cost cells.
498
507
  7. `scripts/okstra-render-report-views.py` emits the self-contained `.html` view, and run validation checks the final artifacts.
499
508
 
509
+ For the three analysis sidetracks, the HTML view also exports an immutable-source `## ANALYSIS REVIEW` sidecar. A revision rerun carries that sidecar, reanalyzes the whole confirmed scope, and records one `analysisReviewResolution` row for every affected ID before `validate_analysis_report.py` accepts the result.
510
+
500
511
  The Markdown is derived, not the authoring source. The schema is the contract.
501
512
 
502
513
  ---
@@ -580,6 +591,8 @@ Project-local `<PROJECT_ROOT>/.claude/settings.local.json` is provisioned as a s
580
591
  | `final-verification` | Read-only acceptance verification | `release-handoff` if accepted |
581
592
  | `release-handoff` | User-selected commit/PR handoff | done or follow-up |
582
593
 
594
+ The independent analysis sidetracks do not appear in this phase sequence. `project-analysis` maps the current project, `feature-analysis` traces one existing feature, and `change-impact-analysis` maps a proposed change's impact. Each returns to `pending-routing-decision` and remains read-only, including no test or build execution.
595
+
583
596
  ### 7.5 Report and follow-up
584
597
 
585
598
  Final report artifacts live under `runs/<task-type>/reports/`. Human responses from the HTML view are saved as `runs/<task-type>/user-responses/user-response-<task-type>-<seq>.md` and can be carried into the next run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.143.0",
3
+ "version": "0.145.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.143.0",
3
- "builtAt": "2026-08-02T11:16:25.811Z",
2
+ "package": "0.145.0",
3
+ "builtAt": "2026-08-02T12:19:03.119Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -63,7 +63,7 @@ Per-file checks miss cross-cutting issues; each commit can be individually clean
63
63
 
64
64
  - [ ] **Domain-literal sweep:** `grep -rn` every domain enum value / predicate you added or touched in WHERE clauses, filters, or branches. The same literal at 2+ I/O sites is a *candidate* scattered decision — ask: would these sites change together when the business rule changes? Same decision → consolidate into one named constant or query builder in the domain layer and make every site reference it. Different decisions that merely share a value → leave them separate; coupling incidental duplication is worse than the repetition. (The identifier grep above does NOT catch this — sweep *values*, not just names.)
65
65
  - [ ] **Stand-alone name test for exports:** for each exported identifier, look at its siblings — can a caller pick the right one from the names alone? If a comment must explain which to use, the name fails; encode the distinguishing fact in it (e.g., the input shape: `parseRows` vs `parseRowsFromFlatItems`).
66
- - [ ] **Self-mock sweep:** for every test file you added or edited, `grep` it for the SUT-stubbing patterns of this language (full list in `languages/*.md` → "Self-mock signals to refuse") — e.g. `spyOn(sut`, `sut.<method> = jest.fn`, `spyk(sut`, `Mockito.spy(`, `@Spy` paired with `@InjectMocks`, `patch(`-ing the class under test, `mockall::mock!` of the unit itself, plus private-reach hacks (`(sut as any).`, `ReflectionTestUtils.invokeMethod(sut`). Any hit where the stubbed/replaced target **is the unit under test** — not an injected collaborator — is a refused self-mock: delete the stub and exercise the real method, or the test only proves its own wiring and survives even if the real implementation is deleted. Mocking injected collaborators at the boundary stays fine; this sweep targets only stubs on the SUT itself. If a method on the SUT feels too painful to leave real, that's a design signal (extract it to a collaborator), not a license to stub it.
66
+ - [ ] **Self-mock sweep:** for every test file you added or edited, `grep` it for the SUT-stubbing patterns of this language (full list in `languages/*.md` → "Self-mock signals to refuse") — e.g. `spyOn(sut`, `sut.<method> = jest.fn`, `spyk(sut`, `Mockito.spy(`, `@Spy` paired with `@InjectMocks`, `patch(`-ing the class under test, `mockall::mock!` of the unit itself, plus private-reach hacks (`(sut as any).`, `ReflectionTestUtils.invokeMethod(sut`). Any hit where the stubbed/replaced target **is the unit under test** — not an injected collaborator — is a refused self-mock: delete the stub and exercise the real method, or the test only proves its own wiring and survives even if the real implementation is deleted. Mocking injected collaborators at the boundary stays fine; this sweep targets only stubs on the SUT itself. If a method on the SUT feels too painful to leave real, that's a design signal (extract it to a collaborator), not a license to stub it. Enforced by `validators/detect_self_mock.py` (static); absent `qa/self-mock-*.json` sidecar BLOCKS at `validate-run.py`.
67
67
  - [ ] **No documented forks:** two deliberate variants of one capability must not survive as parallel implementations with a comment explaining the delta. Re-read both bodies and check the deltas are genuinely parametric: if they reduce to a few orthogonal options, collapse into one implementation taking explicit option parameters that encode them. If encoding the delta would take more than ~3 options, or add more branching than the duplication it removes, they are two capabilities — keep two implementations with distinct honest names and delete the "variant of" framing. Either way the comment-documented fork dies. "The divergence is documented" stays a refused rationalization, and a two-capabilities verdict must come from reading the bodies, not from reluctance to refactor.
68
68
 
69
69
  ## Boundaries
@@ -48,7 +48,7 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
48
48
  | `enabled` | `true` | If `false`, skip the convergence loop and use the existing consensus/divergence method |
49
49
  | `maxRounds` | phase-aware: `1` for `requirements-discovery`, `2` otherwise (range 1–3) | Maximum number of re-verification rounds. Discovery's routing/missing-input outputs gain little from a second round; other phases (especially `error-analysis`) keep `2`. Lead resolves the effective value when the manifest omits the key and records it in `config.effectiveMaxRounds` of the convergence state artifact. |
50
50
  | `verificationMode` | `"lightweight"` | `"lightweight"` or `"full-reanalysis"` |
51
- | `adversarial` | phase-aware: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning`, `false` otherwise | When `true`, Phase 5.5 runs in **adversarial mode** (see §"Adversarial Verification Mode"): verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is forced to `"full-reanalysis"` scoped to the finding's cited evidence. Resolved by `scripts/okstra_ctl/render.py` `_build_convergence_block` and recorded in `config.adversarial` of the convergence state artifact. |
51
+ | `adversarial` | phase-aware: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`, `false` otherwise | When `true`, Phase 5.5 runs in **adversarial mode** (see §"Adversarial Verification Mode"): verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is forced to `"full-reanalysis"` scoped to the finding's cited evidence. Resolved by `scripts/okstra_ctl/render.py` `_build_convergence_block` and recorded in `config.adversarial` of the convergence state artifact. |
52
52
 
53
53
  **Auto-disable rule (BLOCKING).** Convergence requires ≥2 analyser workers to produce a meaningful consensus tally. When the active profile's `Required workers:` block (see `prompts/profiles/*.md`) resolves to fewer than 2 analyser workers — e.g. `release-handoff` (zero analyser workers, lead-only) — the lead MUST treat `convergence.enabled` as `false` for that run regardless of manifest configuration, skip Phases 5.5 and the plan-body verification round ([plan-body-verification](./plan-body-verification.md)), and record `finalState: "converged"` with `totalRounds: 0`, `round2SkippedReason: "auto-disabled"`, an empty `roundHistory`, and an explanatory note in `config` (e.g. `"autoDisabled": "fewer-than-two-analysers"`). The plan-body round inherits the same rule via its `gating=false` advisory path.
54
54
 
@@ -160,7 +160,15 @@ Use each finding as a guide but reanalyze the original code/data yourself. High
160
160
 
161
161
  ## Adversarial Verification Mode
162
162
 
163
- Active only when `config.adversarial == true` (default for `requirements-discovery`, `error-analysis`, and `implementation-planning`; see §"Configuration"); when `false`, every rule in this section is inert and the collaborative behaviour elsewhere in this contract applies unchanged. In adversarial mode the verifier's job inverts: instead of confirming a peer's finding, the verifier **tries to break it**, and the burden of proof sits on the claim — a finding survives only if refutation attempts fail.
163
+ Active only when `config.adversarial == true` (default for `requirements-discovery`, `error-analysis`, `implementation-planning`, `project-analysis`, `feature-analysis`, and `change-impact-analysis`; see §"Configuration"); when `false`, every rule in this section is inert and the collaborative behaviour elsewhere in this contract applies unchanged. In adversarial mode the verifier's job inverts: instead of confirming a peer's finding, the verifier **tries to break it**, and the burden of proof sits on the claim — a finding survives only if refutation attempts fail.
164
+
165
+ ### Read-only analysis task contract
166
+
167
+ For `project-analysis`, `feature-analysis`, and `change-impact-analysis`, every analysis worker independently analyses the full confirmed target. Provider or model diversity is an independent evidence source, never a reason to split the target into disjoint worker assignments. Only the `project-analysis` first exploration pass may divide navigation by component; every worker then returns to the whole confirmed target before producing findings.
168
+
169
+ A single evidence-backed refutation makes the affected finding `contested` while that refutation remains unresolved. Lead MUST NOT use majority voting to override it and MUST NOT promote a lead-only finding into confirmed facts. The report writer records the disagreement and the lead may only retain the claim with explicit counter-evidence through `analysisReviewResolution`; a `still-unresolved` item cannot appear in `analysisCommon.confirmedFacts`. **Enforcement:** `validators/validate_analysis_report.py` rejects a still-unresolved reviewed ID that appears in confirmed facts, and the convergence-state validator preserves the engine's `contested` classification.
170
+
171
+ If every required analysis worker produces a non-result, the run verdict is `blocked`; Lead synthesis is not a worker result. A partial worker failure stays in `executionStatus`, but it does not by itself change the deterministic `analysis-complete` / `analysis-partial` scope verdict. **Enforcement:** `validators/validate_analysis_report.py` recomputes these verdict conditions from structured `data.json`.
164
172
 
165
173
  ### Scoped full-reanalysis
166
174
 
@@ -492,7 +500,7 @@ Save it to `runs/<task-type>/state/convergence-<task-type>-<seq>.json`.
492
500
  Schema rules:
493
501
 
494
502
  - `schemaVersion`: literal string `"1.3"` for all new runs — both adversarial and collaborative. Historical readers accept `"1.0"` / `"1.1"` / `"1.2"` unchanged and never rewrite those artifacts during validation. v1.3 adds the strict coverage-critic ledger and rejects unknown top-level fields; work-state remains v1.0.
495
- - `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis` / `implementation-planning`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
503
+ - `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
496
504
  - `config.effectiveMaxRounds`: the integer the lead actually used after resolving the phase-aware default (`1` for `requirements-discovery`, `2` otherwise). MUST equal `config.maxRounds` when the manifest explicitly set it.
497
505
  - `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). MAY be empty when the discovering worker tagged the finding `unknown`.
498
506
  - `findings[].rounds[].votes.<worker>.verdict`: enum, one of `agree | disagree | supplement | verification-error`. Lower-case tokens; map upper-case AGREE/DISAGREE/SUPPLEMENT verdicts emitted by workers to their lower-case form and map the input alias `unverifiable` to persisted `verification-error`. The latter represents either a terminal non-result dispatch or a completed dispatch that could not verify a particular finding (§"Worker failure handling in reverify"). Every vote has a non-empty `explanation`.
@@ -198,6 +198,12 @@ Extract from the compact intake files: task key, task type, work category, workf
198
198
 
199
199
  If previous run reports exist, use as historical context only. If discovery metadata or current artifacts conflict with a newer user instruction, prefer the user instruction. If `reference-expectations.md` explicitly says expectations were not provided (you can confirm this without reading the file if the brief's "Expected state" section is empty), treat that as missing information and say `I don't know` rather than inventing expected states.
200
200
 
201
+ ### Phase 1.5 — Analysis scope confirmation (BLOCKING)
202
+
203
+ For `project-analysis`, `feature-analysis`, and `change-impact-analysis`, Lead MUST use the run manifest's immutable pre-dispatch `analysisScopeConfirmation` snapshot as the structured reporter-confirmation evidence before Phase 4 worker dispatch. Its `status` MUST be `complete`; its `taskBriefPath` and `briefSha256` bind that status to the exact brief bytes captured when the run manifest was created. A later edit to the live brief, clarification prose, or inferred consent cannot substitute for this snapshot. `project-analysis` confirms which areas remain shallow; `feature-analysis` confirms the exact feature target and covered flows; `change-impact-analysis` confirms the proposed change, preserved behavior, and dependency boundary.
204
+
205
+ If that snapshot is incomplete, missing, or malformed, Lead MUST follow the shared Reporter Confirmation Required / Clarification Items contract, stop before dispatch, and publish a `blocked` report. It MUST NOT silently widen the target. **Enforcement:** `scripts/okstra_ctl/render.py` records the brief path, reporter-confirmation status, and brief byte digest in the run manifest before the lead can dispatch workers; `validators/validate_analysis_report.py` validates only that immutable snapshot, rejects required-worker execution before a complete snapshot, and recomputes the analysis verdict; `validators/validate-run.py` runs that check only after schema validation succeeds.
206
+
201
207
  ## Phase 2 — Phase 5: Prompt preparation, teammate setup, execution, completion poll
202
208
 
203
209
  These phases are governed by [team-contract](./team-contract.md). It is the canonical source for:
@@ -284,7 +290,7 @@ Convergence is enabled by default. Configure via task-manifest.json:
284
290
  - `convergence.enabled`: true/false (default: true)
285
291
  - `convergence.maxRounds`: 1–3 — **phase-aware default**: `1` for `requirements-discovery`, `2` for all other task types
286
292
  - `convergence.verificationMode`: `"lightweight"` | `"full-reanalysis"` (default: `"lightweight"`; the adversarial phases below force `"full-reanalysis"`)
287
- - `convergence.adversarial`: true/false — **phase-aware default**: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning`, `false` otherwise. When `true`, Phase 5.5 runs in adversarial mode (verifiers refute findings; burden of proof on the claim). See [convergence](./convergence.md) "Adversarial Verification Mode".
293
+ - `convergence.adversarial`: true/false — **phase-aware default**: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`, `false` otherwise. When `true`, Phase 5.5 runs in adversarial mode (verifiers refute findings; burden of proof on the claim). See [convergence](./convergence.md) "Adversarial Verification Mode".
288
294
 
289
295
  When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5 and put it in the grouped input at `config.effectiveMaxRounds`.
290
296
 
@@ -18,5 +18,5 @@ Load the applicable coding conventions for every language the diff will touch, t
18
18
  - **Resource selection — read the routed pack, never inline it here.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Detect each touched file's language and framework from its extension or project manifest (`package.json`, `Cargo.toml`, `pyproject.toml`, `pom.xml`, `build.gradle*`, `prisma/schema.prisma`), then read that pack's resources via the Read tool by absolute path. Always read `overview.md` (the router) + `clean-code.md`, then select per the router's three ordered stages — Stage 1 language → `languages/<lang>.md`, Stage 2 framework → `frameworks/<fw>.md` (e.g. `frameworks/node-server.md` for server-side Node), Stage 3 architecture → `architectures/<arch>.md` (e.g. `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex). Each stage is a list of rules; include EVERY matching resource (a change set can touch multiple languages/frameworks/architectures) — do not stop at the first match. These files are runtime resources, not Skill-tool skills, so always read them by path.
19
19
  - **Declared architecture style — an authoritative Stage 3 input, and it binds.** Before selecting resources, read `<PROJECT_ROOT>/.okstra/project.json` and take `architecture.style`. A declared `hexagonal` selects `architectures/hexagonal.md` even when none of Stage 3's layout signals matched, so the declaration — not the directory shape — decides. A declared `layered` has no pack resource; its invariant applies from this line: dependencies run one direction only — an upper layer may import a lower one, never the reverse — and a variation point is extracted onto a layer boundary. A declared style makes this overlay binding rather than advisory, and which rule binds follows the style: under `hexagonal` the overlay's otherwise-advisory concrete-adapter item is blocking, so a service dependency you add or modify goes through a port instead of a concrete implementation and that placement violation is fixed before the write rather than recorded as a note; under `layered` what binds is the direction invariant just stated — your own judgement over the import list of every file the diff touches, plus extracting a variation point onto a layer boundary — while the concrete-adapter item stays advisory, since `layered` has no ports to route it through. An absent field, a `none` style, or an unreadable `project.json` changes nothing — Stage 3 stays detection-driven and its overlay stays advisory, leaving the language-agnostic principles below as the only always-binding layer. The verifier re-grades the same diff under the same declaration (`_implementation-verifier.md` → Static design & test-quality review), so a placement violation missed here returns as a verdict `FAIL`.
20
20
  - **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.
21
- - **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
+ - **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. Self-mocking (1) — Enforced by `validators/detect_self_mock.py` (static); absent `qa/self-mock-*.json` sidecar BLOCKS at `validate-run.py`.
22
22
  - **Graceful degradation (codex / antigravity executor runtimes, or any runtime where the resolved coding-preflight pack 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: resource-unavailable → applied <project rules + agnostic principles>` in the final report. Never claim a resource read that did not happen.
@@ -9,7 +9,7 @@ profile document.
9
9
  - Worker interaction model (shared — read before inferring behaviour from the roster):
10
10
  - the per-profile `Required workers:` block is a **roster**, not a behaviour contract. Each role's interaction mode changes across operating phases of the same run.
11
11
  - **Phase 4 / 5 (independent analysis)**: analyser workers (`claude`, `codex`, `antigravity` when opted in) produce findings independently and have no access to one another's outputs. `report-writer` does not analyse.
12
- - **Phase 5.5 (convergence — peer review by workers)**: workers peer-review each other's findings across up to `effectiveMaxRounds` rounds; the lead mediates but does not vote. See `prompts/lead/convergence.md` for the round protocol (replay of findings, `AGREE` / `DISAGREE` / `SUPPLEMENT` verdicts), queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`). For `requirements-discovery`, `error-analysis`, and `implementation-planning` this phase runs in **adversarial mode** (`convergence.adversarial=true`): verifiers try to refute each finding against its cited evidence and the burden of proof sits on the claim — see that skill's §"Adversarial Verification Mode".
12
+ - **Phase 5.5 (convergence — peer review by workers)**: workers peer-review each other's findings across up to `effectiveMaxRounds` rounds; the lead mediates but does not vote. See `prompts/lead/convergence.md` for the round protocol (replay of findings, `AGREE` / `DISAGREE` / `SUPPLEMENT` verdicts), queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`). For `requirements-discovery`, `error-analysis`, `implementation-planning`, `project-analysis`, `feature-analysis`, and `change-impact-analysis` this phase runs in **adversarial mode** (`convergence.adversarial=true`): verifiers try to refute each finding against its cited evidence and the burden of proof sits on the claim — see that skill's §"Adversarial Verification Mode".
13
13
  - Do NOT conclude "no peer review happens" from the roster alone — every profile that lists ≥2 analyser workers runs convergence by default (`convergence.enabled=true` in `task-manifest.json`).
14
14
  - **provider-unavailable fallback (tolerance).** A worker dispatch can fail to produce a result for two distinct reasons, and both take the same recovery path. (1) **Pane budget:** the dispatch is rejected with `no room for another tmux split` (or an equivalent teammate-pane creation failure). (2) **Sandbox CLI-start failure (non-tmux path):** an external CLI worker wrapper exits non-zero within seconds with empty stdout and its live-log shows `operation not permitted` (e.g. `agy`'s `listen tcp 127.0.0.1:0: bind` under a seatbelt sandbox around a non-tmux subagent's Bash tool). In either case the lead retries that worker in-process without `run_in_background`; if it was an external CLI worker, the lead instead **substitutes** an in-process `claude` analysis. Either way the lead records the substitution as provider unavailable in the run log and the convergence notes. Completed external-CLI worker trace panes are reclaimed automatically by the `SubagentStop` hook, but okstra cannot directly reclaim the teammate panes the harness creates, so this fallback is the last line of defence against pane-budget exhaustion and sandbox-blocked CLI workers. (This is a prompt instruction, not a code-enforced gate.)
15
15
  - Tooling — read-only MCP availability (shared):
@@ -40,7 +40,7 @@ Verifier obtains the QA command set from exactly two declared sources, in order
40
40
 
41
41
  ### Execution rule
42
42
 
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).
43
+ Tier 1 commands run verbatim first. Then every Tier 2 entry runs once. Then the Tier 3 stage conformance script (below) runs once. Then the self-mock detector (below) runs once whenever the diff changed a test file. 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
45
  ### Tier 3 — stage conformance scripts
46
46
 
@@ -84,6 +84,52 @@ also remain contract violations.
84
84
  - **Read-only command log.** Record the `runCommand` exact line + its exit code in the Read-only command log. Tier 3 external non-PASS evidence MUST remain visible with status `ADVISORY`. Unlike Tiers 1·2, a conformance script MAY mutate the **replica datastore** (exercising integrated state is its whole purpose) — but only the `qaEnv` replica target, never a shared/staging/prod store. The `runCommand` itself is still subject to the same source/lockfile mutation deny-list as Tier 2 (`--fix`, `npm install` without `ci`, etc.); a denied token aborts with `contract-violated`.
85
85
  - **No manifest / no entry for this stage.** If the manifest file is absent, or it has no entry whose `stageKey` matches this run's stageKey, the verifier records `conformance: no manifest entry for <stageKey>` and proceeds (forcing the *declaration* of conformance entries is the job of planning Step 11 + the `validate-run.py` diff-surface cross-check, not the verifier).
86
86
 
87
+ ### Self-mock detection (changed test files)
88
+
89
+ A green suite does not prove a test exercises the unit it names — a test that stubs its own SUT passes forever, including after the real implementation is deleted. The static detector is the machine half of the **Self-mocking** blocking check below, and running it is the verifier's own duty: it is never delegated to the executor and never inferred from the executor's evidence.
90
+
91
+ - **Trigger.** This run's diff changed at least one **test** file. Enumerate the changed files with `git diff --name-only <base>...HEAD` from the worktree cwd — the same enumeration the static review's Scope rule uses — then keep only the paths the gate itself treats as tests: `*.spec.*`, `*.test.*`, a `test_`-prefixed basename, a `_test.` suffixed basename, or any path segment `test/` or `tests/`. Pass nothing else; non-test files are excluded. Exclude `tests/fixtures/self_mock/**` as well — those are the detector's own deliberately self-mocked fixtures, which `validate-run.py` also excludes from the trigger, so feeding them in would manufacture a `FAIL` the gate then blocks on. No changed test file → no run and no sidecar; the gate is vacuous by design.
92
+ - **Run the detector once, in the worktree cwd**, one `--test-file` per changed test file:
93
+ ```bash
94
+ python3 ~/.okstra/lib/validators/detect_self_mock.py \
95
+ --test-file <changed test file> [--test-file <changed test file> ...] \
96
+ --changed-file <changed file> [--changed-file <changed file> ...] \
97
+ --sidecar <task_root>/qa/self-mock-<stage-name>.json \
98
+ --stage-name <stage-name> \
99
+ --waivers <task_root>/qa/self-mock-waivers.json \
100
+ --diff <task_root>/qa/self-mock-<stage-name>.diff \
101
+ --worktree <this stage's worktree root>
102
+ ```
103
+ `--changed-file` / `--diff` / `--worktree` feed **gate B** (the mutation probe) and are separate from `--test-file`, which feeds gate A. `--changed-file` takes **every** path in the stage's diff — production sources included, not only the test files — because each mutation adapter selects its own production sources out of that set; hand it only the test files and every adapter finds nothing to mutate, which records a vacuous PASS while gate B is silently dead. Write `--diff` first with `git diff <base>...HEAD > <task_root>/qa/self-mock-<stage-name>.diff` — the same `<base>` and the same range every other `git diff` in this file uses: it is what scopes surviving mutants to the lines this stage added or modified, and without it gate B reports `unsupported(diff-unavailable)` rather than guessing.
104
+ `<stage-name>` is literally `stage-<N>` for this run's injected Stage number (`stage-3` — not the bare number, not the stageKey), and `<task_root>/qa` is the `TASK_QA_PATH` token, the same directory Tier 3's manifest and `result-*.json` live in. A whole-task run with no stage writes `<task_root>/qa/self-mock.json` and omits `--stage-name`. Any other filename or directory is invisible to the gate and reads exactly like "the detector never ran". Pass `--waivers` **unconditionally**: an absent waiver file is the normal case and the detector treats it as "no waivers", so there is no branch to decide and no file for you to create.
105
+ - **Write the result sidecar (BLOCKING deliverable).** The detector writes `<task_root>/qa/self-mock-<stage-name>.json` itself:
106
+ ```json
107
+ {
108
+ "stageName": "stage-<N>",
109
+ "overall": "PASS",
110
+ "ranAt": "<UTC ISO8601>",
111
+ "scannedFiles": ["<test file the detector read>"],
112
+ "skippedFiles": ["<test file it received but could not read>"],
113
+ "changedFiles": ["<every --changed-file path you passed>"],
114
+ "staticDetect": { "status": "PASS", "hits": [], "waived": [], "waiverSource": "<the --waivers path, or null>" },
115
+ "mutation": { "status": "unsupported(stryker:tool-not-declared)", "tool": "stryker", "survived": [], "survivedTotal": 0, "waived": [], "waiverSource": "<the --waivers path, or null>" }
116
+ }
117
+ ```
118
+ `overall` is exactly one of `PASS` / `FAIL`. `scannedFiles` + `skippedFiles` together are the detector's own record of **every** `--test-file` path it received: it read the first list, and could not read the second (extension with no signal set, or no file on disk). Which list a path lands in is the **detector's** decision, never yours — a Go/Ruby/C# test, a JSON fixture under `tests/`, and a test file this stage deleted are all legitimate `skippedFiles` entries and none of them is a defect. The verifier MUST NOT hand-write, edit, or "correct" this file — the detector's own output is the evidence, and a hand-authored sidecar is a `contract-violated` outcome. Its absence is not a passive skip: **Enforced:** `validators/validate-run.py` `_validate_selfmock` fails any report whose §5.7.3 diff summary lists a changed test file while this sidecar is absent, unreadable, malformed, or carries `overall != PASS`.
119
+ **Enforced (coverage, gate B):** the same gate fails the report when a file from §5.7.3 is missing from `changedFiles`, or when that field is absent — that is how "gate B saw this stage" stays distinguishable from "gate B was handed nothing". Pass **every** path in the diff summary to `--changed-file`, production sources included.
120
+ **Enforced (coverage, gate A):** the same gate fails the report when a changed test file from §5.7.3 appears in **neither** `scannedFiles` nor `skippedFiles` — that means you never passed it, and a PASS over a narrower input says nothing about the file left out. So pass **every** file the trigger enumeration kept, in the same repo-relative spelling the diff summary uses, and let the detector sort them. Pre-filtering by language, or dropping a path because the stage deleted the file, is the one way to trip this check.
121
+ - **Suspected false positive → report it, never waive it (self-check safety).** The signal set is regex-based, so it will occasionally accuse a test that is not self-mocked. The escape hatch is `<task_root>/qa/self-mock-waivers.json`, a JSON array of `{"file": "<repo-relative path, as in --test-file>", "line": <hit line>, "signal": "<signal name>", "reason": "<why this hit is not a self-mock>", "acknowledgedBy": "<the user who accepted it>"}`. A waived hit moves out of `staticDetect.hits` into `staticDetect.waived` and stops counting toward the verdict, so it is the one input that can talk the gate out of a finding — and the finding is about **the code this run is verifying**, which is why the acknowledgement must come from outside the run.
122
+ **The verifier MUST NOT create, edit, extend, or re-order that file.** It is the user's acknowledgement channel, not yours; writing an entry into it is self-certification of your own finding and is a `contract-violated` outcome exactly like hand-editing the sidecar. You also MUST NOT point `--waivers` at any other file, and MUST NOT copy a waiver entry into the sidecar by hand. **Enforced (source):** the detector records the `--waivers` argument verbatim as `staticDetect.waiverSource`, and `_validate_selfmock` fails any report whose sidecar carries a non-empty `waived` read from anywhere other than this task's `<task_root>/qa/self-mock-waivers.json` — so redirecting the flag at a file you wrote yourself blocks the run instead of clearing it, and every applied waiver is left in the task bundle to review.
123
+ What you do instead: keep the verdict `FAIL`, and record the suspected false positive in your worker result under the hit's citation — the `path:line`, the signal name, why you believe it is not a self-mock, and the exact JSON object the user would add. The user reviews it, adds the entry with their own `acknowledgedBy`, and the next detector run picks it up through `--waivers`. **Enforced:** `validators/validate-run.py` `_validate_selfmock` fails the report when any `staticDetect.waived` entry is missing a non-empty `reason` or a non-empty `acknowledgedBy` — so an unacknowledged or unexplained waiver blocks the run instead of clearing it, and a matching-but-unacknowledged waiver reaches that gate rather than being silently dropped by the detector.
124
+ - **Read-only command log.** Record the exact command line, its exit code (`0` = PASS, `1` = FAIL), and the detector's last stdout line `QA-RESULT: PASS|FAIL`, together with every `SELF-MOCK <file>:<line> <signal>` line it printed. When the sidecar's `staticDetect.waived` is non-empty, list each waived hit with its `reason` and `acknowledgedBy` so the report shows what the run was excused from and on whose authority. A `FAIL` sets the verifier verdict to `FAIL` with each hit cited `path:line` + signal name and the recommended fix recorded (delete the stub and exercise the real method, or stub injected collaborators only) — the same verdict machinery as the **Self-mocking** blocking check below, which the detector cites for but does not replace: a self-mock the detector's signal set does not cover is still the verifier's finding to make by reading the diff.
125
+ - **Gate B (mutation) runs inside the same detector call.** The detector invokes the mutation probe itself over `--changed-file` and writes the `mutation` block (`status` / `tool` / `survived` / `waived`); your duty is to pass the three flags above, never to author or edit that block by hand. Gate B is a **real gate now** — the `mutation` block is no longer a `pending-phase-2` placeholder, and running the probe over the supported languages in this diff is MANDATORY, which is what the `--changed-file` / `--diff` / `--worktree` flags above accomplish. `status` is `PASS`, `FAIL`, or `unsupported(<reason>)`, and the reason's **class** decides what happens:
126
+ - **Capability gap** (`no-adapter:<lang>`, `tool-not-declared`, `diff-scope-unavailable`, `no-production-sources`, `no-changed-sources`) or **nothing to verify** (`no-mutants-generated`, `diff-adds-no-line`) — gate B legitimately had no tool or nothing to check. Non-blocking; this is the normal case in a repo without mutation tooling.
127
+ - **Integrity / inspection failure** (`diff-incomplete`, `diff-unavailable`, `report-unavailable`, `report-unparsed`, `adapter-malformed-status`, `no-conclusive-mutants`, or any reason not listed above) — **this BLOCKS the run.** It means the stage was never actually inspected: most often a `--diff` you built from a different `<base>` than the `--changed-file` list, or one written before your last edit, so the diff does not cover the changed sources. Rebuild the diff from the same `<base>` and re-run the detector; do not treat it as a skip.
128
+ **Enforced:** `validators/validate-run.py` `_validate_selfmock` blocks on `mutation.status == "FAIL"`, on a missing or malformed `mutation` block, and on any `unsupported(...)` in the integrity/inspection class; the other classes stay excluded from the verdict and are kept in the sidecar for audit.
129
+ - **Suspected false-positive MUTANT → report it, never waive it (self-check safety).** A surviving mutant can be a false accusation too — an unreachable branch, a mutation with no observable behaviour. The escape hatch is the SAME `<task_root>/qa/self-mock-waivers.json` gate A uses, so the user manages one file: a static entry is keyed `{"file", "line", "signal", ...}` and a mutation entry `{"file": "<repo-relative path>", "line": <survivor line>, "mutant": "<mutator name as the detector printed it>", "reason": "<why this mutant is not a real gap>", "acknowledgedBy": "<the user who accepted it>"}`. The `mutant` field is what marks it as gate B's; entries without it are gate A's and never clear a mutant. A waived mutant moves out of `mutation.survived` into `mutation.waived`, and once every survivor on a changed line is waived the mutation verdict is `PASS`.
130
+ **The verifier MUST NOT create, edit, extend, or re-order that file** — the same rule as gate A, for the same reason: it is the user's acknowledgement channel, and writing an entry into it is self-certification of your own finding, a `contract-violated` outcome exactly like hand-editing the sidecar. You also MUST NOT point `--waivers` anywhere else. **Enforced (fields + source):** the probe only MATCHES waivers and carries an unacknowledged one straight through, so `_validate_selfmock` fails any report whose `mutation.waived` entry is missing a non-empty `reason` or `acknowledgedBy`, or whose `mutation.waiverSource` is not this task's own `qa/self-mock-waivers.json`.
131
+ What you do instead: keep the verdict `FAIL`, and record the suspected false positive in your worker result under the mutant's citation — the `path:line`, the mutator name, its `Survived`/`NoCoverage` status, why you believe it is not a real coverage gap, and the exact JSON object the user would add. The user reviews it, adds the entry with their own `acknowledgedBy`, and the next detector run picks it up through the same `--waivers` flag. When it is `FAIL`, cite each `MUTANT-SURVIVED <file>:<line> <mutator> (<status>)` line the detector printed: `Survived` means the test ran that line and asserted nothing about it, `NoCoverage` means no test reached it at all — which is what a stubbed subject looks like from the outside.
132
+
87
133
  ### Missing-tier handling
88
134
 
89
135
  If a tier is empty or absent, verifier records the single line `qa-command not configured: <category>` per missing category (`lint` / `format` / `typecheck` / `test`; and `db-test` **only when the diff touches DB/IO/SQL**, where a missing `db-test` is escalated to a blocking finding per the DB real-execution gate below) in the worker result and proceeds — silent omission is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_missing_qa_categories_recorded` for the four unconditional categories; `db-test` is left to the DB gate below because its requirement depends on whether the diff touches DB/IO/SQL. Without the note, "the category passed" and "the category never ran" read identically in the report. Verifier MUST NOT auto-detect or invent a command in this case; the user/operator must declare it in `project.json.qaCommands` or in the plan.
@@ -98,7 +144,7 @@ Tier 3 external-advisory discrepancies are excluded from this promotion: preserv
98
144
 
99
145
  ### Read-only command log (per verifier)
100
146
 
101
- The worker result MUST contain a `Read-only command log` block listing every command executed during the verifier run with its exact invocation and exit code, in execution order — including the Tier 3 conformance `runCommand` (or the exemption/waiver skip note when no script ran). No source-mutating command may appear in this block; the only permitted mutation is a Tier 3 conformance script writing to its `qaEnv` replica datastore, which is logged like any other command. This log is copied into the final report's verifier result section verbatim.
147
+ The worker result MUST contain a `Read-only command log` block listing every command executed during the verifier run with its exact invocation and exit code, in execution order — including the Tier 3 conformance `runCommand` (or the exemption/waiver skip note when no script ran). No source-mutating command may appear in this block; the only permitted mutations are a Tier 3 conformance script writing to its `qaEnv` replica datastore and the self-mock detector writing its own `<task_root>/qa/self-mock-*.json` sidecar — both are artifact-directory writes, both are logged like any other command, and neither touches the worktree source, so the verifier runs them without hesitation. This log is copied into the final report's verifier result section verbatim.
102
148
 
103
149
  ### Verifier evidence is independent of executor evidence
104
150
 
@@ -0,0 +1,24 @@
1
+ # Change Impact Analysis Profile
2
+
3
+ - Purpose: assess the read-only impact of a proposed change, including preserved behavior, affected dependencies, and constraints that a later planning phase must resolve
4
+ - Required workers:
5
+ - claude
6
+ - codex
7
+ - report-writer
8
+ - Optional workers (opt-in via `--workers`):
9
+ - antigravity
10
+ {{INCLUDE:_common-contract.md}}
11
+ - Phase 1.5 questions:
12
+ - What user intent and preserved behavior define the proposed change boundary?
13
+ - Which direct and transitive dependencies need impact analysis?
14
+ - Which constraints or open decisions must be passed to implementation-planning?
15
+ - Required report blocks:
16
+ - preserved behavior and impacted items
17
+ - dependency propagation with test and operational impact
18
+ - constraints and open decisions for implementation-planning
19
+ - Cross-verification mode:
20
+ - Phase 5.5 convergence runs in adversarial mode (`convergence.adversarial=true`).
21
+ - Non-goals:
22
+ - source or configuration edits
23
+ - tests, builds, migrations, or deployments
24
+ - implementation alternatives, file change specifications, or execution plans
@@ -0,0 +1,24 @@
1
+ # Feature Analysis Profile
2
+
3
+ - Purpose: analyse a confirmed feature target's behavior, rules, state changes, external calls, and test coverage scope without designing or changing an implementation
4
+ - Required workers:
5
+ - claude
6
+ - codex
7
+ - report-writer
8
+ - Optional workers (opt-in via `--workers`):
9
+ - antigravity
10
+ {{INCLUDE:_common-contract.md}}
11
+ - Phase 1.5 questions:
12
+ - What confirmed target and user intent bound this analysis?
13
+ - Which normal, alternate, and failure flows must be described?
14
+ - Which behavior must be preserved while the feature is analysed?
15
+ - Required report blocks:
16
+ - confirmed target and scope boundary
17
+ - normal, alternate, and failure flows
18
+ - rules, state transitions, external calls, and test coverage scope
19
+ - Cross-verification mode:
20
+ - Phase 5.5 convergence runs in adversarial mode (`convergence.adversarial=true`).
21
+ - Non-goals:
22
+ - source or configuration edits
23
+ - tests, builds, migrations, or deployments
24
+ - implementation alternatives, file change specifications, or execution plans
@@ -14,6 +14,24 @@
14
14
  "free external data fetch beyond the brief's Source Material or Phase 1.5 resolved scope",
15
15
  "interpreting user phrases like `다음 단계 진행해` as authorisation to enter another phase"
16
16
  ],
17
+ "project-analysis": [
18
+ "source or configuration edits",
19
+ "tests, builds, migrations, or deployments",
20
+ "starting any other lifecycle phase inside this run"
21
+ ],
22
+ "feature-analysis": [
23
+ "source or configuration edits",
24
+ "tests, builds, migrations, or deployments",
25
+ "starting any other lifecycle phase inside this run"
26
+ ],
27
+ "change-impact-analysis": [
28
+ "source or configuration edits",
29
+ "tests, builds, migrations, or deployments",
30
+ "starting any other lifecycle phase inside this run",
31
+ "implementation alternatives",
32
+ "file change specifications",
33
+ "stepwise execution plans"
34
+ ],
17
35
  "error-analysis": [
18
36
  "source code edits, refactors, or fix attempts",
19
37
  "implementation design or planning artifacts",