okstra 0.172.0 → 0.173.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 +8 -6
- package/docs/architecture/storage-model.md +11 -0
- package/docs/architecture.md +16 -14
- package/docs/cli.md +36 -5
- package/docs/performance-improvement-plan-v2.md +6 -5
- package/docs/project-structure-overview.md +21 -13
- package/docs/task-process/README.md +5 -3
- package/docs/task-process/error-analysis.md +2 -2
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation-option-selection.md +70 -0
- package/docs/task-process/implementation-planning.md +23 -15
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +30 -6
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +3 -0
- package/runtime/bin/okstra.sh +2 -0
- package/runtime/prompts/duties/direction-selection-worker.md +44 -0
- package/runtime/prompts/duties/planning-worker.md +12 -4
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +5 -5
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +20 -3
- package/runtime/prompts/lead/report-writer.md +27 -5
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +3 -3
- package/runtime/prompts/profiles/final-verification.md +3 -3
- package/runtime/prompts/profiles/forbidden-actions.json +7 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +35 -0
- package/runtime/prompts/profiles/implementation-planning.md +50 -38
- package/runtime/prompts/profiles/implementation.md +2 -1
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +1 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +6 -0
- package/runtime/python/okstra_ctl/exact_coverage.py +128 -0
- package/runtime/python/okstra_ctl/fix_cycles.py +3 -1
- package/runtime/python/okstra_ctl/implementation_direction.py +836 -0
- package/runtime/python/okstra_ctl/implementation_options.py +479 -0
- package/runtime/python/okstra_ctl/plan_items.py +51 -3
- package/runtime/python/okstra_ctl/render.py +1 -0
- package/runtime/python/okstra_ctl/render_final_report.py +1 -0
- package/runtime/python/okstra_ctl/report_contract.py +45 -13
- package/runtime/python/okstra_ctl/report_html/render.py +4 -2
- package/runtime/python/okstra_ctl/report_html/router.py +4 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +32 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +25 -10
- package/runtime/python/okstra_ctl/report_views.py +148 -12
- package/runtime/python/okstra_ctl/run.py +350 -2
- package/runtime/python/okstra_ctl/scope_provenance.py +15 -9
- package/runtime/python/okstra_ctl/user_response.py +75 -0
- package/runtime/python/okstra_ctl/wizard.py +144 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
- package/runtime/python/okstra_ctl/workflow.py +29 -7
- package/runtime/schemas/final-report-v2.0.schema.json +1428 -137
- package/runtime/templates/reports/final-report-v2.template.md +4 -0
- package/runtime/templates/reports/final-verification-input.template.md +1 -1
- package/runtime/templates/reports/html/base.template.html +3 -2
- package/runtime/templates/reports/html/i18n/en.json +21 -1
- package/runtime/templates/reports/html/i18n/ko.json +21 -1
- package/runtime/templates/reports/html/macros/forms.html +21 -2
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +49 -0
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +36 -2
- package/runtime/templates/reports/i18n/en.json +13 -0
- package/runtime/templates/reports/implementation-input.template.md +4 -2
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -4
- package/runtime/templates/reports/improvement-discovery-input.template.md +1 -1
- package/runtime/templates/reports/md/tasks/implementation-option-selection.template.md +13 -0
- package/runtime/templates/reports/md/tasks/implementation-planning.template.md +17 -0
- package/runtime/templates/reports/report.js +111 -4
- package/runtime/templates/reports/task-brief.template.md +9 -3
- package/runtime/templates/reports/user-response.template.md +25 -4
- package/runtime/templates/worker-prompt-preamble.md +8 -0
- package/runtime/validators/validate-implementation-plan-stages.py +106 -1
- package/runtime/validators/validate-report-views.py +2 -2
- package/runtime/validators/validate-run.py +135 -25
- package/runtime/validators/validate_improvement_report.py +5 -1
- package/src/commands/execute/codex-run.mjs +1 -0
- package/src/commands/execute/render-bundle.mjs +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Implementation Planning Profile
|
|
2
2
|
|
|
3
|
-
- Purpose:
|
|
3
|
+
- Purpose: turn an upstream-selected direction into an executable plan; legacy reruns may retain candidate comparison
|
|
4
4
|
- Required workers:
|
|
5
5
|
- claude
|
|
6
6
|
- codex
|
|
@@ -16,36 +16,43 @@
|
|
|
16
16
|
- never plan around an unconfirmed `intent-inference` augmentation as if it were a settled requirement. Treat the inference as settled ONLY when a `[CONFIRMED …]` marker sits on the matching `intent-check:` row after the precondition runs; absent the marker it stays a `Blocks=approval` clarification item per the precondition's `skipped` branch.
|
|
17
17
|
- `conversion-block:` rows are handled by the precondition; planning around an untranslated reporter phrase is forbidden until it is resolved.
|
|
18
18
|
- Worker planning procedure:
|
|
19
|
+
- route by the planning input contract before analysis. A run carrying `selected-direction.json` uses the selected-direction procedure. Only a legacy rerun without that snapshot uses candidate comparison.
|
|
20
|
+
- **Selected-direction planning procedure** — perform these steps in order and no others:
|
|
21
|
+
1. Read `selected-direction.json` and the original requirements ledger end-to-end.
|
|
22
|
+
2. Concretize the selected direction into files, interfaces, stages, validation, and rollback without changing its mechanism or boundaries.
|
|
23
|
+
3. Link every planned file and stage bidirectionally to the original requirements.
|
|
24
|
+
4. If current evidence requires changing the selected direction, emit `direction-invalidated` and stop planning.
|
|
19
25
|
- identify requirement gaps and affected interfaces with file:line evidence, resolving codebase-answerable ambiguity before returning findings
|
|
20
|
-
- compare at least two feasible options unless the brief carries a confirmed decision; record concrete trade-offs and current-pattern evidence for every option
|
|
21
|
-
- propose stages with real dependency edges, validation signals, rollback order, and change-locality evidence; do not serialize independent work
|
|
22
26
|
- surface migration, deployment, cross-project, and approval risks without drafting final-report headings or schema rows
|
|
23
|
-
-
|
|
27
|
+
- Legacy candidate-comparison-only responsibilities:
|
|
28
|
+
- **Legacy candidate-comparison procedure** — only a legacy rerun without `selected-direction.json` compares feasible Option Candidates, preserves their trade-offs and Recommended Option, then produces stages, validation, rollback, and requirement coverage. This is the only branch that generates candidates, assigns candidate scores, recommends a direction, or awaits a user candidate choice.
|
|
29
|
+
- **Spec-settled short-circuit** — when the brief already carries a decision-complete design the reporter has confirmed, do not re-litigate it. Preserve that design as the Recommended Option and its already-weighed alternatives as the remaining Option Candidates.
|
|
30
|
+
- **Variation-point candidate scoring** — when `variationPointAnalysis` records two or more current implementations, include an extraction Option Candidate and score it against the non-extracted Option Candidate in the trade-off matrix. Penalize candidates that branch on resource identity inside a service or widen a unit's responsibility. This comparison preserves the established legacy `implementation-option` selection contract.
|
|
31
|
+
- Pre-planning context exploration (mandatory before direction realization or legacy option drafting):
|
|
24
32
|
- read the task brief, related-task briefs, and any cited spec / design doc end-to-end
|
|
25
33
|
- inspect the current state of every file the task names (or the closest matching files if names are stale) — record current responsibilities, public interfaces, and known coupling points
|
|
26
34
|
- skim recent commits touching those files (`git log -- <path>`) to surface in-flight work or contested areas
|
|
27
|
-
- **sibling exploration (variation-point evidence)**: read the `Related Task Graph` sibling / `related-to` tasks' done artifacts *and the code they actually landed* — a done report is a claim, the diff is the fact. When a sibling already implements the same behavior for another resource,
|
|
35
|
+
- **sibling exploration (variation-point evidence)**: read the `Related Task Graph` sibling / `related-to` tasks' done artifacts *and the code they actually landed* — a done report is a claim, the diff is the fact. When a sibling already implements the same behavior for another resource, register the existing implementation in `variationPointAnalysis.evidence` and verify that the chosen realization's `extractionDecision` addresses the present variation point without adding a second parallel implementation alongside it. Absent an explicit graph edge, still surface a same-behavior implementation you saw in the files or `git log` output already inspected above — an unrecorded edge does not make the duplication less real.
|
|
28
36
|
- **codebase-first ambiguity resolution**: any ambiguity that can be answered by `Read` / `Grep` MUST be resolved that way and recorded with file:line evidence. Only ambiguities that genuinely require a human decision are escalated as `Clarification Items` rows. Writing a clarification row for something the code already answers is a defect of this phase.
|
|
29
37
|
- **directive-first ambiguity resolution** (the same rule, pointed at the user instead of the code): any ambiguity the run's directive, the brief, the carried-in `user-responses/` sidecars, or the user's in-session instruction already answers MUST be resolved that way and recorded with the quoted instruction. Writing a clarification row for something the user already decided is the same defect as writing one for something the code already answers — and it costs more, because the row withholds approval until a whole separate answer cycle closes it. When an instruction points at a document, treat every item in that document as decided, including the ones the document itself flagged as needing a decision (shared rule: `_common-contract.md` "User instruction outranks the material it points at").
|
|
30
38
|
- flag any requirement that is ambiguous, contradictory, or missing success criteria — register each one as a row in the report's `## 1. Clarification Items` table with `Blocks=approval` instead of guessing
|
|
31
39
|
- read `<PROJECT_ROOT>/.okstra/glossary.md` and `<PROJECT_ROOT>/.okstra/decisions/` titles if present. Absent okstra memory files are the normal state — do not error. Treat the brief's `terminology:*` resolutions from `requirements-discovery` (if any) as authoritative; if missing, resolve any remaining fuzzy term as a `Blocks=approval` clarification row.
|
|
32
|
-
- **spec-settled short-circuit**: when the brief already carries a decision-complete design the reporter has confirmed (a `requirements-discovery` outcome whose approach sits behind a `[CONFIRMED …]` marker, or a verbatim reporter-approved plan in `Source Material`), do NOT re-litigate the settled decision. Present that design as the `Recommended Option`, and record the alternatives it already weighed as the remaining Option Candidate(s) tagged `(considered & rejected upstream — <why>)` rather than manufacturing fresh competing options to fill the slot. The Stage Map, validation, gates, and the ≥2-candidate shape all still apply — this short-circuits re-deliberation, not the plan's structure.
|
|
33
40
|
- Primary focus areas:
|
|
34
41
|
- requirement gaps
|
|
35
42
|
- affected components and boundaries
|
|
36
43
|
- config files and deployment manifests that must change to reach the expected target state
|
|
37
|
-
-
|
|
44
|
+
- direction realization consequences and trade-offs already fixed by the selected snapshot or legacy choice
|
|
38
45
|
- hidden dependency or migration risk
|
|
39
46
|
- validation and rollout approach
|
|
40
|
-
- Design principles applied
|
|
41
|
-
- **Isolation & single responsibility**: each unit touched should have one clear purpose, well-defined interface, and be independently testable.
|
|
42
|
-
- **Files that change together live together**: split by responsibility, not by technical layer.
|
|
47
|
+
- Design principles applied to realization review:
|
|
48
|
+
- **Isolation & single responsibility**: each unit touched should have one clear purpose, well-defined interface, and be independently testable. Reject a realization that widens a unit's responsibility without requirement evidence.
|
|
49
|
+
- **Files that change together live together**: split by responsibility, not by technical layer. Reject a realization that scatters one logical change across unrelated layers.
|
|
43
50
|
- **Follow established patterns**: in existing codebases, conform to current conventions. Targeted cleanup of a file you are already modifying is acceptable; unrelated refactors are not.
|
|
44
|
-
- **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and
|
|
45
|
-
- **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement. The test is a *present* caller, not a plausible one — an abstraction whose only justification is a requirement nobody has stated is this rule's target, while a behavior with two implementations already on the table belongs to `Variation-point extraction` above. **Enforced:** the §5.5.9 plan-body verification round raises
|
|
51
|
+
- **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and make an evidence-backed `extractionDecision` that preserves the chosen direction's architecture boundaries. Reject a realization that branches on resource identity inside a service (one `if` / `switch` arm per implementation): adding the next implementation then means editing that same call site again, which is the closed-for-extension shape this principle exists to catch. This does not contradict YAGNI below: YAGNI drops *speculative* variation (a second implementation nobody named), while a behavior with two implementations already on the table is a present fact, not a forecast. **Enforced:** the `variationPointAnalysis` bullet under `Required deliverable shape` names the schema / validator / `P-Var-*` enforcement points.
|
|
52
|
+
- **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement. The test is a *present* caller, not a plausible one — an abstraction whose only justification is a requirement nobody has stated is this rule's target, while a behavior with two implementations already on the table belongs to `Variation-point extraction` above. **Enforced:** the §5.5.9 plan-body verification round raises a hidden direction expansion on selected-direction `P-Dir-1`, and retains legacy `P-Opt-*` `DISAGREE(e)` semantics. One phase later the `implementation` verifier's Static design gate fails the stage on a caller-less identifier.
|
|
46
53
|
- **Project review-rule preflight**: a pack applies when either source names it — the task brief's `Source Material` / `Reporter Confirmations` cites its exact `SKILL.md` path, or `<PROJECT_ROOT>/.okstra/project.json` lists it under `reviewRulePacks` (the project's standing standard, applying whether or not the brief mentions it). The two sources are a union. Read only those files and the `references/*.md` files they directly name. Do not search parent directories or host skill catalogs. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
|
|
47
54
|
- Expected output emphasis:
|
|
48
|
-
- feasible
|
|
55
|
+
- feasible realization of the selected direction or legacy choice
|
|
49
56
|
- dependency and risk visibility
|
|
50
57
|
- recommended execution order
|
|
51
58
|
- one `endStateCoverage` row per brief end-state id, whose `coveredBy` names the `R-NNN` row that carries it. The two tables are a chain, not duplicates: `endStateCoverage` proves no reporter requirement was dropped, `requirementCoverage` proves each one reaches a stage.
|
|
@@ -61,10 +68,10 @@
|
|
|
61
68
|
- Approval gate (phase-specific addendum to shared authority rule):
|
|
62
69
|
- The YAML frontmatter `approved: true|false` field is the only authorised approval gate. report-writer always emits `approved: false`. The user clears it either by (a) editing the frontmatter line to `approved: true` directly, or (b) invoking the next phase with `--approve` so the CLI flips the frontmatter on the user's behalf. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
|
|
63
70
|
- Cross-verification mode:
|
|
64
|
-
- Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk /
|
|
71
|
+
- Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / plan item) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode".
|
|
65
72
|
- §5.5.9 plan-body verification runs with an **adversarial posture** (`prompts/lead/plan-body-verification.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is majority-based for kinds `b`/`c`/`e`, but a single `DISAGREE` blocks on its own for the concrete, safety-critical kind `a` (path/symbol mismatch) — and `f` on `P-Req-*` items. `P-Var-*` items are excepted from the kind-`a` exception: a variation-point defect takes a majority. Rollback ordering (`d`) is advisory and never blocks the gate — a rollback is executed by a human, not by okstra's workers or verifiers. A majority also needs ≥2 participating votes, so a lone dissent whose peer returned a non-result does not block on a majority-gated kind (see that contract's §"Adversarial plan-body posture").
|
|
66
73
|
- **Incremental re-verification scope (clarification re-runs):** when the lead's `okstra incremental-scope` decision is `mode == "incremental"` (procedure in `prompts/launch.template.md` §"Clarification Response Carried In"), workers re-analyze ONLY the stages listed in `reverify_stages` (the downstream closure of the impacted stages). Workers MUST NOT re-open, re-score, or re-judge any stage in `carry_stages` — those stages' prior plan-item verdicts are carried forward verbatim, and a worker never overwrites a carried verdict with its own judgement. When the decision is `mode == "full"` (the default), every stage is re-analyzed as usual.
|
|
67
|
-
- **Single incremental-scope decision:** the lead calls `okstra incremental-scope` exactly once for the re-run, passing the answered `C-NNN` ids through `--answered-clarifications`, changed design-preparation IDs through `--prep-items`, and any lead-resolved stage numbers through `--impacted`; the CLI unions all three before applying the existing dependency closure and cutoff. The clarification ids are resolved to stages by the CLI from the prior report's own `planItems[].clarificationId` and `blocked C-NNN` coverage links — the lead does not map answers to stage numbers. An answer that changes the selected
|
|
74
|
+
- **Single incremental-scope decision:** the lead calls `okstra incremental-scope` exactly once for the re-run, passing the answered `C-NNN` ids through `--answered-clarifications`, changed design-preparation IDs through `--prep-items`, and any lead-resolved stage numbers through `--impacted`; the CLI unions all three before applying the existing dependency closure and cutoff. The clarification ids are resolved to stages by the CLI from the prior report's own `planItems[].clarificationId` and `blocked C-NNN` coverage links — the lead does not map answers to stage numbers. An answer that changes the selected planning payload, Stage Map, or execution approach is not a local impact: pass every CSV empty so the same call returns `mode == "full"`. A clarification id that traces to no stage, unknown PREP IDs, or invalid `stageRefs` also return an explicit full decision instead of being guessed. When the user pinned a scope at the wizard (`REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` in `prompts/launch.template.md` §"Clarification Response Carried In" step 0), that pin is an input to this same single call — `full` supplies the `--full-reason`, and pinned stage numbers join `--impacted` — never a second call or a bypass of the CLI's closure and cutoff.
|
|
68
75
|
- **Stage-aware carry:** for an incremental decision, pass its `carry_stages` and `reverify_stages` CSVs unchanged to `okstra incremental-carry`. The helper carries the prior whole stage rows and their owned PREP / `P-Prep-*` artifacts; overlap, cross-scope ownership, scope leaks, or canonical conflicts return `CarryError`. On that error, discard the partial merge and run full re-verification.
|
|
69
76
|
{{INCLUDE:_coverage-critic.md}}
|
|
70
77
|
- Non-goals:
|
|
@@ -77,12 +84,12 @@
|
|
|
77
84
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
78
85
|
{{INCLUDE:_clarification-recommendation.md}}
|
|
79
86
|
- **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. **Enforced:** `validators/validate-run.py` `_validate_clarification_evidence_note` requires the note to exist and to be either a concrete `<path>:<line>` or the `none — <reason>` form. The check proves the note exists, not that the lookup happened — a false `none` is caught by review, but it is now an attributable claim rather than a silent omission.
|
|
80
|
-
- **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a
|
|
87
|
+
- **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a discarded plan detail blocked a plan with the same weight as a business decision. A row earns `Blocks=approval` only when, **absent an answer, the `implementation` run would produce wrong or unsafe code**. Apply the test to the row's consequence, not its topic:
|
|
81
88
|
- admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
|
|
82
|
-
- NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything
|
|
89
|
+
- NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything outside the chosen realization, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
|
|
83
90
|
- A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
|
|
84
91
|
- Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
|
|
85
|
-
-
|
|
92
|
+
- For a selected-direction plan, the plan-ready schema branch requires `planningContract`, `outcome`, `selectedDirectionRef`, `directionRealization`, `stageMap`, `stages`, `designPreparation`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `coverageSummary`, `variationPointAnalysis`, and `planBodyVerification`. Its `direction-invalidated` branch contains no execution fields.
|
|
86
93
|
- Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
|
|
87
94
|
- Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
|
|
88
95
|
- **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the Markdown scan in `collect_validation_errors` live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` — they gate historical v1 Markdown only. The v2 AI-handoff template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
|
|
@@ -91,8 +98,9 @@
|
|
|
91
98
|
- The first `stepwiseExecution` row's `action` starts with `RED:` and its `expected` reads FAIL; some later row's `action` starts with `GREEN:` and its `expected` reads PASS. **Enforced (S10c):** `collect_data_validation_errors` in `validators/validate-implementation-plan-stages.py`, run from `validate-run.py` `_append_stage_data_failures`.
|
|
92
99
|
- `tddExemption` waives both rules above, and only for `doc-only`, `config-only`, or `pure-rename` work. An empty or arbitrary reason waives nothing. **Enforced (S10e):** same function — the schema alone cannot reject it, because it types the field as a plain string and keys its conditional on the property merely being present.
|
|
93
100
|
- `stageMap[].dependsOn` must form a DAG (no self-dependency, no unknown stage, no cycle), each row's `stepCount` must equal its stage's actual `stepwiseExecution` row count, and two `(none)`-dependency stages must not name the same file in their `exitContract` — they run as concurrent implementation runs in separate worktrees. **Enforced (S8/S4/S9):** same function.
|
|
94
|
-
-
|
|
95
|
-
-
|
|
101
|
+
- Legacy candidate-comparison-only deliverable:
|
|
102
|
+
- The plan lives in `data.json` under `implementationPlanning`, and `schemas/final-report-v2.0.schema.json` requires every one of these keys: `optionCandidates`, `tradeoffMatrix`, `recommendedOption`, `stageMap`, `stages`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `planBodyVerification`, `crossProjectDependencies`, `decisionDrafts`, `skippedAdrCandidates`, `variationPointAnalysis`, `userNarrative`. A missing block fails schema validation; there is nothing to satisfy by naming a heading.
|
|
103
|
+
- Legacy candidate-comparison requires at least two implementation options. **Each option must include**:
|
|
96
104
|
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
|
|
97
105
|
- **Two-tier change description.** Each `fileStructure` row carries `summary` **and** optional `details`, and they are not interchangeable:
|
|
98
106
|
- `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.OptionCandidate.fileStructure.items.summary` caps it at 120 characters, so a dense identifier dump fails schema validation.
|
|
@@ -103,9 +111,12 @@
|
|
|
103
111
|
- estimated blast radius (units, configs, deployment manifests, data migrations)
|
|
104
112
|
- trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
|
|
105
113
|
- recommended option with rationale tied to the design principles above
|
|
114
|
+
- `recommendedOption.testSeams` is a mandatory array with one row per boundary a test injects at and replaces: `boundary`, `injectedAs`, and `replacedInTest`. An empty array is legal only with a defensible reason. `schemas/final-report-v1.0.schema.json` requires the array and all three row fields; the §5.5.9 `P-Var-*` round rejects a seam whose construction or wiring point is not actually replaceable.
|
|
115
|
+
- Record non-blocking working assumptions inside the Recommended Option. A required answer belongs in a `Blocks=approval` clarification instead. Omit the assumptions only when there are none.
|
|
116
|
+
- The YAML frontmatter carries `implementation-option:` directly under `approved:` so the user can select an Option Candidate after planning.
|
|
117
|
+
- Required deliverable shape (final report, in addition to the standard sections):
|
|
118
|
+
- In the selected-direction branch, `directionRealization` is the sole design payload. Its `fileStructure`, interfaces, blast radius, test seams, assumptions, and invariants concretize the snapshot without introducing another option or recommendation.
|
|
106
119
|
- **Variation-point analysis (`variationPointAnalysis`, mandatory — every plan emits the block, rendered as §5.5.11):** declare `hasMultipleImplementations`, and when it is `true`, one `points[]` row per varying behavior carrying `behavior`, the two or more `implementations` that serve it, `evidence` (a `path:line`, or the sibling task / stage that already implements it), and an `extractionDecision` of `extract` / `interfaceKind` / `coveredBy` (the Stage Map stage that builds the interface) / `rationale`. **A `false` declaration is not an omission — it is a claim**, so it carries a written `noVariationRationale` and an empty `points` array; the two are mutually exclusive, because declared points would be silently dropped from verification under a `false` header. A project whose `.okstra/project.json` sets `architecture.style: hexagonal` extracts a point as a port (`interfaceKind: "port"`), never as a shared helper. **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.VariationPointAnalysis` / `$defs.VariationPoint` make the block required and pin the row shape; `validators/validate-run.py` `_validate_variation_point_analysis` rejects a `false` declaration with no rationale, a `false` declaration carrying points, a `true` declaration with no point, an `extract: true` decision naming no `interfaceKind` or no `coveredBy`, and a hexagonal project extracting as anything but a port; and every point becomes a `P-Var-<N>` plan item judged in §5.5.9 (`prompts/lead/plan-body-verification.md`) — a plan declaring no variation point is still verified, through the lone `P-Var-0`.
|
|
107
|
-
- **Test seams (`recommendedOption.testSeams`, mandatory array):** one row per boundary a test injects at and replaces — `boundary` (what the seam sits on), `injectedAs` (the concrete construction / wiring point a test substitutes at), `replacedInTest` (what the test puts there instead). An **empty array is legal but is itself a claim**: "this plan needs no seam." Its absence is what forces self-mocks — with no declared injection point the implementation ends up mocking the unit against its own re-implementation, and those tests pass regardless of whether the behavior is right. A row whose `injectedAs` names no construction or wiring point a test can actually replace is a seam on paper only. **Enforced:** `schemas/final-report-v1.0.schema.json` makes `testSeams` required on `recommendedOption` and requires all three row fields; the §5.5.9 `P-Var-*` round DISAGREEs when a declared seam (or an `extractionDecision.coveredBy`) is not actually injectable.
|
|
108
|
-
- **Working Assumptions (non-blocking)**: inside the `Recommended Option` section, an `Assumptions:` labelled list recording each assumption the plan proceeds on **without** user confirmation but that does NOT block approval — a sensible default the reviewer can still veto (e.g. "assuming the existing retry policy stays; not re-tuning it here"). This is distinct from `## 1. Clarification Items`, which gates approval: anything that must be answered before coding is a clarification row, never an assumption. Omit the list only when there are genuinely none. (No new scanned heading — it lives under the existing `Recommended Option`. The `Authority & permissions` class from the shared contract is explicitly NOT recorded here — that class is suppressed, not surfaced.)
|
|
109
120
|
- **Stage Map (mandatory — always emitted, even when N=1):** a table of all stages with `stage | title | depends-on | step-count | exit-contract-summary`. `depends-on` is `(none)` or a comma-separated stage number list. Stages with `depends-on (none)` can be implemented in parallel by two simultaneous `implementation` runs.
|
|
110
121
|
- **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.
|
|
111
122
|
- **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.
|
|
@@ -160,13 +171,14 @@
|
|
|
160
171
|
- `contract:<rule>` — an artifact okstra's own phase contract mandates, so it has no brief line to cite. The allowlist is exactly `decision-record-step` (the §5.4 Decision Drafts materialization step) and `glossary-step` (the glossary proposal step); the SSOT is `scripts/okstra_ctl/scope_provenance.py`. Never widen this form to launder work the brief did not ask for.
|
|
161
172
|
An item you can give none of these three sources to is **not a requirement and not a stage**. Its only admissible outlet is a `## 1. Clarification Items` row with `Blocks=approval`, carrying the recommendation format from `_clarification-recommendation.md`. Do not fold it into an option, a stage, or a step "while we are in here" — that is the scope expansion this rule exists to stop. This makes concrete the planning-input rule that any change beyond what `Requirement Summary` explicitly demands is out of scope by default. **Enforced:** `validators/validate-run.py` `_validate_requirement_provenance` (source resolution) and `_validate_stage_has_requirement` (no stage without a requirement).
|
|
162
173
|
- **The reach of this gate — do not over-trust it.** What is mechanically enforced is the *form* of each source, that a cited `brief:` id is one the brief actually declares (or, on a pre-end-state brief, that the heading literally exists), that a `derived:` chain terminates without cycling, and that no stage is uncited. What is **not** enforced is whether the cited source genuinely demands the requirement. The id form closes the older loophole — a brief no longer offers generic headings any invented work could be hung on — but it leaves one open: attaching a requirement the cited `EB-NNN` does not actually ask for still parses clean, because no machine reads that id's sentence and compares it to your row. The gate's value is that it forces every item to name a specific reporter line and makes fabrication explicit and auditable — judging whether that line actually demands the item remains a reviewer / `DISAGREE(f)` responsibility, and passing this gate is never evidence that the scope is justified.
|
|
163
|
-
- **Stage citation format — enumerate, never range (scale gate):** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the
|
|
174
|
+
- **Stage citation format — enumerate, never range (scale gate):** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the selected plan, step 4` cites nothing. **A range cites only its two endpoints:** `Stages 1-3` cites 1 and 3, and stage 2 stays uncited — write every stage out. Range syntax (`-`, `to`, `through`) is still parsed, so `Stages 7-8` is a valid two-stage citation; what it cannot do is stand in for an interior nobody named. **Enforced:** `validators/validate-run.py` `_validate_stage_has_requirement` via `okstra_ctl.stage_citations.enumerated_stage_numbers` fails the plan when any Stage Map stage is cited by no coverage row.
|
|
164
175
|
- **Why enumeration is the scale gate.** The number of stages a plan carries is not bounded by any threshold — a genuinely large requirement may need many, and okstra does not guess a ratio. What IS bounded is how cheaply a plan can *claim* coverage of them: one `Stages 1-64` cell used to satisfy the reverse check for the whole map while the planner confirmed nothing, so scale grew for free. Enumeration prices it — every stage you claim costs you the act of naming it and asking whether this requirement is really satisfied there. A plan that cannot bring itself to type the numbers is telling you the stages are not all needed. The typing is the confirmation, so do not batch it mechanically: a row listing `Stages 1, 2, 3, ..., 12` you did not check one by one is the same rubber stamp with more characters.
|
|
165
176
|
- Because that reader only sees prose, it still cannot tell a citation from a mention: `Stage 1 (superseded by Stage 2)` counts Stage 1 as cited. Cite the stages a requirement is actually satisfied by, not stages merely mentioned.
|
|
166
|
-
- **Requirement Coverage (mandatory, §5.5.8):**
|
|
167
|
-
|
|
177
|
+
- **Requirement Coverage (mandatory, §5.5.8):** selected-direction plans preserve the original requirement IDs and link each row to `stageRefs`, `stepRefs`, `validationRefs`, and `fileRefs`; exact forward and reverse coverage is enforced by `validate_selected_direction_plan`. Legacy candidate-comparison plans retain one `R-NNN` row per concrete requirement and the existing Option Candidate plus Stage/Step `coveredBy` semantics. The exact `P-Req-*` queue comes from `scripts/okstra_ctl/plan_items.py` in both branches.
|
|
178
|
+
- **Legacy compatibility details:** assign `R-001`, `R-002`, ... in source order. `Source` uses the existing `brief:` / `derived:` / `contract:` grammar. A `covered` row names the specific Option Candidate and Stage/Step. A gap, blocked clarification, or unaccepted deviation keeps the gate non-passing. `validators/validate-run.py` retains `_validate_requirement_coverage_covered_by`, `_validate_requirement_deviations`, `_validate_gate_blocked_by`, and `_independent_coverage_blockers` enforcement for this branch.
|
|
179
|
+
- **Review-rule compliance plan:** when a project-local review rule pack is found, the chosen realization MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
|
|
168
180
|
- the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
|
|
169
|
-
-
|
|
181
|
+
- Selected-direction plans omit `implementation-option:` because `selectedDirectionRef` already fixes the direction; the legacy-only selector rule is owned by the legacy deliverable section above.
|
|
170
182
|
- **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.**
|
|
171
183
|
- 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)
|
|
172
184
|
- **Exact plan-item queue (BLOCKING).** Run `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, place the persisted `items[]` verbatim in the verifier prompt, then run `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. Do not freely summarise, select, omit, reorder, or renumber the queue. Prompt headings use the compact `subject` and include the lossless `payload`. For every item, ask:
|
|
@@ -177,39 +189,39 @@
|
|
|
177
189
|
```
|
|
178
190
|
|
|
179
191
|
An `AGREE` note records the counterexample considered and its exclusion reason. If the judgement needs unavailable external material, record `verification-error`, not `DISAGREE`. **Enforced:** `validators/validate-run.py` `_validate_plan_item_extraction_completeness` compares the exact deterministic set, independently rejecting missing, unexpected, and duplicate plan-item IDs, including `P-Prep-*`.
|
|
180
|
-
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead
|
|
192
|
+
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead runs a worker peer-review round on the persisted queue. Selected-direction plans begin with `P-Dir-1`; legacy candidate-comparison plans begin with `P-Opt-*`; both continue with the shared execution items. The fixed order remains initial verification → one planner self-fix → targeted re-verification → user gate. Gate recomputation, extraction completeness, approval-context reconciliation, and self-fix limits remain enforced by `validators/validate-run.py`; verdict details and dissent format are owned by `prompts/lead/plan-body-verification.md`.
|
|
181
193
|
- **Approval decision state.** Every activity-contract-v1 approval row carries `approvalContext` with its classification, linked plan item IDs, `user-decision-required` activity IDs, release condition, and classification-appropriate recommended disposition. Every structured option carries its gate `disposition`; `/okstra-user-response` copies that exact value into the sidecar rather than replacing it with `answer`. A resolved row also carries the user's non-empty raw text and `user-decision-evaluated` check references.
|
|
182
194
|
- `open → answered` when the raw user response is recorded
|
|
183
195
|
- `answered → resolved` only after the selected disposition is applied and its checks pass
|
|
184
196
|
- `answered → open` when application or checking fails
|
|
185
197
|
- `open → obsolete` only when a plan change removes the question
|
|
186
198
|
`open` and `answered` block approval; only `resolved` and `obsolete` are non-blocking. **Enforced:** `validators/validate-run.py` `_validate_approval_context` plus run-prep `scripts/okstra_ctl/run.py` `_validate_approved_plan`.
|
|
187
|
-
- **Terminal approval evidence.** A terminal row preserves a pre-correction dissent classification only when the plan-body state sidecar's superseded votes prove it; the validator does not trust `approvalContext.classification` by itself. Every cited `user-decision-required` / `user-decision-evaluated` activity
|
|
188
|
-
- **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
|
|
199
|
+
- **Terminal approval evidence.** A terminal row preserves a pre-correction dissent classification only when the plan-body state sidecar's superseded votes prove it; the validator does not trust `approvalContext.classification` by itself. Every cited `user-decision-required` / `user-decision-evaluated` activity includes exactly the row's `C-NNN` in `evidenceRefs` and exactly the linked `approvalContext.planItemIds` set in `planItemIds`. A resolved correctness-critical row also binds `resolution.checkRefs` to a later successful evaluation, its zero-exit commands and plan-body state result path, one explicit `plan-body-verification:round-N` token, and matching successful state/report verdicts. That human-response round does not increment `selfFixRoundsApplied`; the automatic cap remains one rewrite. After correcting an independent coverage-only blocker, retain its `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs` and the matching state-sidecar plan item's `clarificationId`; that item must have no historical blocking dissent and must participate in the historical `coverage-gap` round. A run-wide `coverage-gap` without the item-level `C-NNN` link is insufficient. `obsolete` is invalid while a current linked question, disagreement, or coverage blocker remains active, including a current `user-decision`. **Enforced:** `validators/validate-run.py` `_read_approval_history`, `_activity_matches_approval_context`, `_validate_correctness_resolution`, `_historical_coverage_clarification_ids`, and `_validate_approval_context`.
|
|
200
|
+
- **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 chosen realization introduces against the three criteria:
|
|
189
201
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
190
202
|
2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
|
|
191
203
|
3. **Real trade-off** — were there named alternatives, and was one picked for specific reasons?
|
|
192
204
|
If **all three** hold, record a decision draft as a `decisionDrafts[]` row (rendered in §5.4 `### Decision Drafts`, one `#### <number>-<slug>` subsection each). Each row carries `number` (= `(max existing in <PROJECT_ROOT>/.okstra/decisions/ + 1)` zero-padded to ≥4 digits), `slug` (kebab-case), `status: Proposed`, and the `context` / `decision` / `consequences` / `alternativesConsidered` fields (`alternativesConsidered` names the rejected alternatives and why). If any of the three criteria is missing, do NOT raise a draft — instead record a `skippedAdrCandidates[]` row (`topic` + `reason: <criterion that failed>`, rendered as a `skipped adr-candidate: … — reason: …` line under the same section) so the next reader knows the candidate was evaluated and intentionally dropped.
|
|
193
205
|
The decision files are NOT written by this phase. The approved plan's stepwise execution order MUST include the step `Create <PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md from the §5.4 Decision Drafts subsection <number>-<slug>` (materializing the structured fields into the file's `## Status / ## Context / ## Decision / ## Consequences / ## Alternatives Considered` shape) so the `implementation` run commits the file inside okstra's subtree. `validators/validate-run.py` enforces this: a non-empty `decisionDrafts` whose stages carry no stepwise step referencing `.okstra/decisions/` is `contract-violated`.
|
|
194
206
|
- **Glossary proposals**: if a term or definition should become okstra institutional memory, add the step `Update <PROJECT_ROOT>/.okstra/glossary.md: <term> = <definition>` to the stepwise execution order. Use no other project-memory path.
|
|
195
|
-
- No-placeholder rule (plan failures — reject any
|
|
207
|
+
- No-placeholder rule (plan failures — reject any realization detail or step that contains these):
|
|
196
208
|
- "TBD", "TODO", "implement later", "fill in details", "add appropriate error handling", "handle edge cases", "write tests for the above" without actual test code
|
|
197
|
-
- "similar to
|
|
198
|
-
- references to types, functions, flags, or files that no other step or
|
|
209
|
+
- "similar to another plan/task" without repeating the concrete content (readers may consume sections out of order)
|
|
210
|
+
- references to types, functions, flags, or files that no other step or realization detail defines
|
|
199
211
|
- steps that describe *what* to do without showing *how* (commands, code, or exact diffs are required for any code-touching step)
|
|
200
212
|
- Self-review pass before finalising the report (the Okstra lead runs this; do not delegate it):
|
|
201
|
-
1. **Spec coverage** — for every requirement in the task brief, point to the
|
|
213
|
+
1. **Spec coverage** — for every requirement in the task brief, point to the chosen realization and stages/steps that satisfy it in Requirement Coverage. Follow the selected-direction or legacy compatibility row shape declared above. List gaps explicitly as `gap` or `blocked C-NNN`. A deliberate alternative is `documented-deviation` only when its decision references resolve and its approval disposition is user-confirmed or names an open approval blocker; a publishable gate permits only `covered` or an accepted deviation.
|
|
202
214
|
2. **Placeholder scan** — search the report for the patterns in the No-placeholder rule above and fix inline.
|
|
203
|
-
3. **Internal consistency** —
|
|
215
|
+
3. **Internal consistency** — the chosen realization's file list, interfaces, stages, and validation must agree on paths, names, and signatures. A symbol called `clearLayers()` in one field and `clearFullLayers()` in the steps is a bug.
|
|
204
216
|
4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
|
|
205
|
-
5. **Scope check** — if the
|
|
206
|
-
6. **Review-rule preflight check** — when a project review rule pack applies (cited by the brief, or declared in `project.json` `reviewRulePacks` — see the preflight rule above), map each relevant rule to the
|
|
217
|
+
5. **Scope check** — if the plan now spans multiple independent subsystems, split it into separate planning runs rather than shipping an oversized plan. Then walk the plan in the expansion direction: for every stage, name the Requirement Coverage row that demanded it, and for every requirement row, read its `Source` cell as a skeptic — does the cited brief heading actually exist, and does a `derived:` rationale state a real technical consequence rather than a preference? Move anything that fails to a `Blocks=approval` clarification row.
|
|
218
|
+
6. **Review-rule preflight check** — when a project review rule pack applies (cited by the brief, or declared in `project.json` `reviewRulePacks` — see the preflight rule above), map each relevant rule to the chosen realization. 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.
|
|
207
219
|
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.
|
|
208
220
|
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.
|
|
209
221
|
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`.
|
|
210
222
|
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.
|
|
211
|
-
11. **Variation-point & seam check** — read `variationPointAnalysis` as a skeptic. Is `hasMultipleImplementations` honest against the brief and the sibling code you inspected during pre-planning, or was `false` chosen because it is the cheaper field to fill? For every point with `extract: true`, confirm the `extractionDecision` names a real interface (a `port` for a hexagonal project, not a shared helper) and a `coveredBy` stage that exists in the Stage Map — an interface no stage builds is a decision nobody executes. Then read the
|
|
223
|
+
11. **Variation-point & seam check** — read `variationPointAnalysis` as a skeptic. Is `hasMultipleImplementations` honest against the brief and the sibling code you inspected during pre-planning, or was `false` chosen because it is the cheaper field to fill? For every point with `extract: true`, confirm the `extractionDecision` names a real interface (a `port` for a hexagonal project, not a shared helper) and a `coveredBy` stage that exists in the Stage Map — an interface no stage builds is a decision nobody executes. Then read the chosen realization's `testSeams`: each `injectedAs` must name a construction or wiring point a test can actually substitute at, not a symbol the test would have to re-implement — a seam nothing can be injected into leaves the executor writing self-mocks. An empty `testSeams` array is only acceptable when you can defend it in one sentence; the validator accepts it either way, so this is the check that catches an unfilled field posing as a decision.
|
|
212
224
|
12. **Approval blast-radius check (BLOCKING).** Every `Blocks=approval` clarification row must be reachable *from* the plan, not only *into* it: at least one `planItems[]` entry carrying that id as `clarificationId`, or one `requirementCoverage` row blocked on it in `status` or `approvalDisposition`. Item 7 covers only rows this run promoted from a majority-disagree plan item; a blocker raised any other way can still withhold approval while recording nothing it affects. The cost lands on the re-run: `okstra incremental-scope` resolves impacted stages from exactly these two links and treats an id that traces to no stage as grounds to re-verify every stage, so one unlinked blocker turns an incremental re-run into a full one.
|
|
213
|
-
- **The link must resolve to a stage, not merely exist.** `incremental-scope` reads the stage number out of a `P-Step-<stage>.<step>` / `P-Prep-S<stage>-<kind>` plan-item id, or out of a `Stage N` citation in the blocked coverage row's `coveredBy`. Every other plan-item prefix (`P-Req-*`, `P-Val-*`, `P-Opt-*`, `P-Dep-*`, `P-Rb-*`)
|
|
225
|
+
- **The link must resolve to a stage, not merely exist.** `incremental-scope` reads the stage number out of a `P-Step-<stage>.<step>` / `P-Prep-S<stage>-<kind>` plan-item id, or out of a `Stage N` citation in the blocked coverage row's `coveredBy`. Every other plan-item prefix (`P-Dir-1`, `P-Req-*`, `P-Val-*`, `P-Opt-*`, `P-Dep-*`, `P-Rb-*`) carries no stage, so a blocker linked only that way MUST also have its coverage row cite the stage in `coveredBy`. Writing the blocked row's `coveredBy` as prose with no `Stage N` in it — `No stage.`, `Partly covered — …` — satisfies nothing: the row passes the link check and the re-run still re-verifies everything.
|
|
214
226
|
- What to write when no stage covers the requirement yet: name the stage the answer will change, not the stage that satisfies the requirement today. A `Blocks=approval` row is admissible only when, absent an answer, `implementation` would produce wrong or unsafe code (see the admissibility rule above) — so some stage's code is at stake by construction. If you genuinely cannot name one, the row fails the admissibility test and belongs in `## 5. Missing Information and Risks` with `Blocks=none`, not in the approval gate.
|
|
215
227
|
**Enforced:** `validators/validate-run.py` `_validate_approval_clarification_backtrace` — one failure for a missing link, a separate one for a link that resolves to no stage.
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
- the run brief MUST cite `--approved-plan <path>` pointing to a `final-report.md` produced by a prior `implementation-planning` run located under `runs/implementation-planning/.../reports/final-report.md`
|
|
24
24
|
- that file's YAML frontmatter MUST carry `approved: true`. report-writer emits `approved: false` by default; the user flips it to `true` to authorise this run. Free-form approvals such as "lgtm" / "go ahead" / paraphrased confirmations are NOT accepted; set the plan file's frontmatter to `approved: true` before invoking implementation, or pass `--approve` so the CLI flips it (`okstra_ctl.run._apply_cli_approval`).
|
|
25
25
|
- The `--approve` flag is meaningful ONLY with `--task-type implementation` and `--approved-plan <path>`; any other use raises `PrepareError`. Idempotent — re-running with `approved: true` already set appends an audit line but does NOT re-toggle.
|
|
26
|
-
-
|
|
26
|
+
- determine the plan branch from the sibling data.json `implementationPlanning.planningContract`. For `selected-direction`, the authoritative scope is `selectedDirectionRef`, its validated snapshot, `directionRealization`, and the selected stage; the plan MUST be `plan-ready` with exact coverage, and both an `implementation-option:` frontmatter field and `--implementation-option` are forbidden. A direction change routes to `implementation-option-selection`; a detail-only plan correction routes to `implementation-planning`.
|
|
27
|
+
- for the legacy candidate-comparison branch, the authoritative scope is the Option Candidate named by the YAML frontmatter `implementation-option:` field. **If `implementation-option:` is empty, fall back to the plan's `Recommended Option`** (this is a soft fallback, not a hard block). The chosen option's step list becomes the authoritative scope. Any deviation MUST be justified in the final report AND routed to a new `implementation-planning` run; never silently expand scope. If the chosen option name does not match any heading under `Option Candidates`, record it as a deviation.
|
|
27
28
|
- Stage worktree (provisioned by `okstra-ctl` at this implementation run's prep time):
|
|
28
29
|
- Status: `{{EXECUTOR_WORKTREE_STATUS}}` (one of: `created` | `reused` | `skipped-in-worktree` | `skipped-not-git`)
|
|
29
30
|
- Working tree path: `{{EXECUTOR_WORKTREE_PATH}}` — when status is `created` or `reused`, this is this run's isolated stage worktree rooted at `~/.okstra/worktrees/<project>/<task-group>/<task-id>/stage-<N>/`. When skipped, this is the caller's `project_root`.
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
- Report assembly instructions:
|
|
53
53
|
- current branch — `schemaVersion: 2.0`: author the structured data contract below and let the independent renderers produce AI handoff Markdown and task-specific human HTML.
|
|
54
54
|
- v1 legacy branch: when validating or rerendering an existing schema-v1 report, preserve its `## 5.9 Improvement Candidates` table and legacy Markdown contract; do not rewrite that historical data into v2 implicitly.
|
|
55
|
-
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 11-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-
|
|
55
|
+
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 11-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-option-selection, error-analysis}, Expected behavior after, Evidence as path:line list). `Expected behavior after` states, in one observable sentence, what becomes different once the candidate is applied — it is the seed of the downstream brief's `EB-NNN` / `EO-NNN`. A candidate you cannot write this cell for is a preference, not a finding: drop it rather than filling the cell with a restatement of the title.
|
|
56
56
|
- `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.
|
|
57
57
|
- Verdict Token — **branch-specific, and the two branches do not share a vocabulary.** On the current v2 branch use the shared analysis enum: `analysis-complete` when every resolved lens was examined, `analysis-partial` when one could not be, `blocked` when the scan itself could not run. `schemas/final-report-v2.0.schema.json` admits only those three for `verdictCard.verdictToken` / `finalVerdict.verdictToken`, so a v2 report carrying `candidates-ready` fails Phase 7. **Finding no candidates is not a verdict**: it is an empty `candidates[]` plus a `lensCoverage[]` row per lens with `status: no-candidate` and its evidence-backed rationale — the verdict stays `analysis-complete`. `candidates-ready` / `no-candidates` belong to the v1 legacy `## 7. Final Verdict` Markdown alone, where `validators/validate_improvement_report.py` enforces them. Both branches: Direction `routing`; Next Step "ask the user to select K candidates (see the ## 5.9 table)".
|
|
58
58
|
- `## 3. Recommended Next Steps` first entry summarises per-candidate routing and proposes new task-key names of the form `<task-group>/imp-<Cand-ID>`
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- state the reporter's rejection criteria, missing routing inputs, and the evidence boundary behind each recommendation
|
|
24
24
|
- Primary focus areas:
|
|
25
25
|
- classify the work as bugfix, feature, improvement, refactor, or ops
|
|
26
|
-
- determine whether `error-analysis` or `implementation-
|
|
26
|
+
- determine whether `error-analysis` or `implementation-option-selection` is the next safe step. Direct `implementation-planning` or `implementation` handoff is never a valid routing target — implementation requires direction selection followed by an approved `implementation-planning` report
|
|
27
27
|
- capture the reporter's **rejection criteria** — the delivered outcome that would make this work wrong or unacceptable — as a routing input. Consume it from the brief's `Desired Outcome` / `Out of Scope` / `Source Material` when present; when it is absent AND it would change the classification (e.g. bugfix vs feature) or the next-phase choice, raise it as one `decision` clarification row with `Evidence checked: none — reporter intent`. Never infer it — this is a reporter-intent signal, the mirror of improvement-discovery's `Anti-goals`
|
|
28
28
|
- record the rejection criteria in `requirementsDiscovery.rejectionCriteria` with the
|
|
29
29
|
`source` that produced it. When it was absent and would not have changed the
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
The packet follows the `templates/reports/fan-out-unit.template.md` format, and its frontmatter
|
|
44
44
|
`domain`(work-category 5-enum: bugfix / feature / refactor / ops / improvement),
|
|
45
45
|
`depends-on`(an inline list of unit-ids within the same fan-out `[unit-001]`, or `[]` if none),
|
|
46
|
-
`recommended-next-phase`(error-analysis | implementation-
|
|
46
|
+
`recommended-next-phase`(error-analysis | implementation-option-selection) are filled in.
|
|
47
47
|
Each packet MUST carry a non-empty `## Requirement Provenance` section whose every bullet is `brief:EB-001` / `brief:PB-001` / `brief:EO-001` — an end-state id the brief declares — or `contract:<rule>`. When the brief pins ids, citing a heading is rejected: every brief carries the same generic headings, so a heading cannot say WHICH reporter line demanded this unit, and a fan-out unit becomes the brief for a whole downstream task. Only a brief authored before the end-state sections existed still takes the older `brief:<heading>` form, and there the heading must literally exist in it. `derived:` is not admissible in a packet — cross-packet derivation cannot be resolved from a single packet, so each unit anchors directly on the brief. A unit you cannot source that way is not a work item: raise it as a clarification row instead of publishing a packet for it. **Enforced:** `validators/validate_fanout.py` `_check_provenance`.
|
|
48
48
|
- in `runs/requirements-discovery/fan-out/index.md`, list the packets in depends-on topological order
|
|
49
49
|
as a numbered list (`1. unit-001`) (a generated view; explicitly do not hand-edit). The depends-on graph
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
- write canonical-term resolutions in a new `Domain Alignment` subsection of the final report; actual glossary writes happen via `okstra-brief-gen` Step 4.5 on a subsequent run
|
|
78
78
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
79
79
|
- if any blocking input is missing 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)
|
|
80
|
-
- prefer concrete questions whose answers map directly to a routing decision (`bugfix` vs `feature`, `error-analysis` vs `implementation-
|
|
80
|
+
- prefer concrete questions whose answers map directly to a routing decision (`bugfix` vs `feature`, `error-analysis` vs `implementation-option-selection`, etc.). State each option in plain language with one sentence describing what choosing it would mean for the next phase.
|
|
81
81
|
{{INCLUDE:_clarification-recommendation.md}}
|
|
82
82
|
- **Codebase-first ambiguity resolution (defect rule)**: any ambiguity that can be answered by `Read` / `Grep` / file inspection MUST be resolved that way and recorded with file:line evidence. Writing a clarification row for something the codebase already answers is a defect of this phase. **Boundary — facts only, never intent:** this rule governs questions of *fact* ("what does this code do", "where is this called", "does this field exist"). It never governs questions of *intent* ("should we do this at all", "is this in scope", "which outcome does the reporter want"). Scope and intent are not the kind of question a codebase can answer, so inspecting files never discharges them — raise them as clarification rows. Resolving a scope question by inference and building on that inference is the mirror defect, and a more expensive one.
|
|
83
83
|
- **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, external authority). A row with `none` that *could* have been answered by the codebase is a defect.
|
|
@@ -90,9 +90,17 @@
|
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"task_type_text": {
|
|
93
|
-
"label": "Task type? (입력 가능: requirements-discovery, improvement-discovery, project-analysis, feature-analysis, change-impact-analysis, error-analysis, implementation-planning, implementation, final-verification, release-handoff)",
|
|
93
|
+
"label": "Task type? (입력 가능: requirements-discovery, improvement-discovery, project-analysis, feature-analysis, change-impact-analysis, error-analysis, implementation-option-selection, implementation-planning, implementation, final-verification, release-handoff)",
|
|
94
94
|
"echo_template": "task-type: {value}"
|
|
95
95
|
},
|
|
96
|
+
"selected_direction_pick": {
|
|
97
|
+
"label": "상세 계획의 입력으로 사용할 확정 구현 방향 보고서를 선택하세요 (같은 task의 최신 3개)",
|
|
98
|
+
"echo_template": "selected-direction: {value}",
|
|
99
|
+
"errors": {
|
|
100
|
+
"none": "같은 task에서 선택할 implementation-option-selection 최종 보고서를 찾을 수 없습니다.",
|
|
101
|
+
"unknown": "목록에 없는 selected-direction 경로입니다: {value}"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
96
104
|
"brief_keep": {
|
|
97
105
|
"label": "기존 brief 경로 [{existing_brief_path}] 를 유지할까요?",
|
|
98
106
|
"echo_template": "brief: {value}",
|
|
@@ -57,6 +57,12 @@ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
|
|
|
57
57
|
"Brief consumption",
|
|
58
58
|
"Worker diagnosis procedure",
|
|
59
59
|
),
|
|
60
|
+
"implementation-option-selection": (
|
|
61
|
+
"Brief consumption",
|
|
62
|
+
"Worker direction-selection procedure",
|
|
63
|
+
"Pre-selection context exploration",
|
|
64
|
+
"Option evaluation rules",
|
|
65
|
+
),
|
|
60
66
|
"implementation-planning": (
|
|
61
67
|
"Brief consumption",
|
|
62
68
|
"Worker planning procedure",
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Pure exact requirement coverage calculation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExactCoverageError(ValueError):
|
|
11
|
+
"""Raised when coverage input cannot define a valid comparison."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ExactCoverageResult:
|
|
16
|
+
coverage_percent: float
|
|
17
|
+
scope_precision_percent: float
|
|
18
|
+
covered_count: int
|
|
19
|
+
total_count: int
|
|
20
|
+
unmapped_commitments: tuple[str, ...]
|
|
21
|
+
contradicted_requirements: tuple[str, ...]
|
|
22
|
+
verdict: Literal["exact", "under", "over", "contradicted"]
|
|
23
|
+
|
|
24
|
+
def as_report_summary(self) -> dict[str, object]:
|
|
25
|
+
return {
|
|
26
|
+
"coveragePercent": self.coverage_percent,
|
|
27
|
+
"scopePrecisionPercent": self.scope_precision_percent,
|
|
28
|
+
"coveredCount": self.covered_count,
|
|
29
|
+
"totalCount": self.total_count,
|
|
30
|
+
"unmappedCommitments": list(self.unmapped_commitments),
|
|
31
|
+
"contradictedRequirements": list(self.contradicted_requirements),
|
|
32
|
+
"coverageVerdict": self.verdict,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def calculate_exact_coverage(
|
|
37
|
+
original_requirement_ids: Sequence[str],
|
|
38
|
+
coverage_status_by_id: Mapping[str, str],
|
|
39
|
+
commitment_requirement_ids: Mapping[str, Sequence[str]],
|
|
40
|
+
) -> ExactCoverageResult:
|
|
41
|
+
original_ids = tuple(original_requirement_ids)
|
|
42
|
+
if not original_ids:
|
|
43
|
+
raise ExactCoverageError("original requirement denominator is empty")
|
|
44
|
+
if len(set(original_ids)) != len(original_ids):
|
|
45
|
+
raise ExactCoverageError("original requirement ids contain duplicates")
|
|
46
|
+
|
|
47
|
+
original_id_set = set(original_ids)
|
|
48
|
+
if set(coverage_status_by_id) != original_id_set:
|
|
49
|
+
raise ExactCoverageError("coverage rows must match original requirement ids")
|
|
50
|
+
allowed_statuses = {"covered", "not-covered", "contradicted"}
|
|
51
|
+
if any(status not in allowed_statuses for status in coverage_status_by_id.values()):
|
|
52
|
+
raise ExactCoverageError("coverage status is invalid")
|
|
53
|
+
if not commitment_requirement_ids:
|
|
54
|
+
raise ExactCoverageError("implementation commitment denominator is empty")
|
|
55
|
+
|
|
56
|
+
unmapped = tuple(
|
|
57
|
+
commitment_id
|
|
58
|
+
for commitment_id, requirement_ids in commitment_requirement_ids.items()
|
|
59
|
+
if not requirement_ids
|
|
60
|
+
or any(requirement_id not in original_id_set for requirement_id in requirement_ids)
|
|
61
|
+
)
|
|
62
|
+
contradicted = tuple(
|
|
63
|
+
requirement_id
|
|
64
|
+
for requirement_id in original_ids
|
|
65
|
+
if coverage_status_by_id[requirement_id] == "contradicted"
|
|
66
|
+
)
|
|
67
|
+
covered_count = sum(
|
|
68
|
+
coverage_status_by_id[requirement_id] == "covered"
|
|
69
|
+
for requirement_id in original_ids
|
|
70
|
+
)
|
|
71
|
+
coverage_percent = round(covered_count / len(original_ids) * 100, 2)
|
|
72
|
+
traceable_count = len(commitment_requirement_ids) - len(unmapped)
|
|
73
|
+
scope_precision_percent = round(
|
|
74
|
+
traceable_count / len(commitment_requirement_ids) * 100,
|
|
75
|
+
2,
|
|
76
|
+
)
|
|
77
|
+
verdict: Literal["exact", "under", "over", "contradicted"]
|
|
78
|
+
if contradicted:
|
|
79
|
+
verdict = "contradicted"
|
|
80
|
+
elif covered_count != len(original_ids):
|
|
81
|
+
verdict = "under"
|
|
82
|
+
elif unmapped:
|
|
83
|
+
verdict = "over"
|
|
84
|
+
else:
|
|
85
|
+
verdict = "exact"
|
|
86
|
+
|
|
87
|
+
return ExactCoverageResult(
|
|
88
|
+
coverage_percent=coverage_percent,
|
|
89
|
+
scope_precision_percent=scope_precision_percent,
|
|
90
|
+
covered_count=covered_count,
|
|
91
|
+
total_count=len(original_ids),
|
|
92
|
+
unmapped_commitments=unmapped,
|
|
93
|
+
contradicted_requirements=contradicted,
|
|
94
|
+
verdict=verdict,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def calculate_plan_exact_coverage(
|
|
99
|
+
original_requirement_ids: Sequence[str],
|
|
100
|
+
coverage_status_by_id: Mapping[str, str],
|
|
101
|
+
stage_requirement_ids: Mapping[int, Sequence[str]],
|
|
102
|
+
file_requirement_ids: Mapping[str, Sequence[str]],
|
|
103
|
+
) -> tuple[ExactCoverageResult, tuple[int, ...], tuple[str, ...]]:
|
|
104
|
+
"""Apply exact coverage to the executable stage and file-change scope."""
|
|
105
|
+
commitments = {
|
|
106
|
+
**{
|
|
107
|
+
f"stage:{stage}": requirement_ids
|
|
108
|
+
for stage, requirement_ids in stage_requirement_ids.items()
|
|
109
|
+
},
|
|
110
|
+
**{
|
|
111
|
+
f"file:{path}": requirement_ids
|
|
112
|
+
for path, requirement_ids in file_requirement_ids.items()
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
result = calculate_exact_coverage(
|
|
116
|
+
original_requirement_ids,
|
|
117
|
+
coverage_status_by_id,
|
|
118
|
+
commitments,
|
|
119
|
+
)
|
|
120
|
+
unmapped_stages = tuple(
|
|
121
|
+
stage for stage, requirement_ids in stage_requirement_ids.items()
|
|
122
|
+
if not requirement_ids
|
|
123
|
+
)
|
|
124
|
+
unmapped_files = tuple(
|
|
125
|
+
path for path, requirement_ids in file_requirement_ids.items()
|
|
126
|
+
if not requirement_ids
|
|
127
|
+
)
|
|
128
|
+
return result, unmapped_stages, unmapped_files
|
|
@@ -27,7 +27,9 @@ _LOCK_FILENAME = ".fix-cycles.lock"
|
|
|
27
27
|
# 완료(release-handoff) task 에 fix-cycle 로 재진입할 수 있는 entry phase 들.
|
|
28
28
|
# prepare(run.py) 의 게이트와 wizard 의 감지 술어가 공유하는 SSOT.
|
|
29
29
|
FIX_CYCLE_ENTRY_PHASES = (
|
|
30
|
-
"requirements-discovery", "error-analysis", "implementation-
|
|
30
|
+
"requirements-discovery", "error-analysis", "implementation-option-selection",
|
|
31
|
+
"implementation-planning",
|
|
32
|
+
)
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def fix_cycles_path(task_root: Path) -> Path:
|