okstra 0.189.3 → 0.189.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/architecture.md +1 -1
- package/docs/cli.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/lead/report-writer.md +8 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/python/okstra_ctl/render.py +24 -7
- package/runtime/python/okstra_ctl/report_assembly.py +10 -4
- package/runtime/python/okstra_ctl/report_html/common.py +241 -113
- package/runtime/python/okstra_ctl/report_html/context_links.py +121 -0
- package/runtime/python/okstra_ctl/report_html/models.py +11 -5
- package/runtime/python/okstra_ctl/report_html/render.py +54 -13
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +11 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +13 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +9 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +29 -2
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -9
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +15 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +7 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +9 -1
- package/runtime/python/okstra_ctl/report_translation.py +58 -1
- package/runtime/python/okstra_ctl/run.py +6 -0
- package/runtime/schemas/final-report-v2.0.schema.json +4 -0
- package/runtime/schemas/final-report-v3.0.schema.json +4 -0
- package/runtime/templates/reports/html/base.template.html +13 -3
- package/runtime/templates/reports/html/i18n/en.json +43 -5
- package/runtime/templates/reports/html/i18n/ko.json +43 -5
- package/runtime/templates/reports/html/macros/forms.html +4 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +11 -1
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +16 -15
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +5 -5
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +1 -1
- package/runtime/validators/validate-report-views.py +30 -1
- package/runtime/validators/validate-run.py +113 -3
package/docs/architecture.md
CHANGED
|
@@ -877,7 +877,7 @@ The Phase 7 `render-views` step accepts either a final-report data.json or its M
|
|
|
877
877
|
- **Human summary**: `humanSummary` is the sole v2 top-level human summary contract. It is not copied into AI Markdown. Each task view decides how to present it together with the task deliverable instead of sharing a generic dashboard body.
|
|
878
878
|
- **Audit isolation**: worker execution, convergence, and token/cost material remain available for traceability but are subordinate to the user's findings and decisions. They never replace the task analysis narrative.
|
|
879
879
|
- **Implementation-planning activity**: activity-contract reports show each agent's task, summary, and outcome in the default view. Commands, exit codes, file-and-line evidence, and result paths remain inside expandable detail. Approval decision cards link to the relevant `id-A-NNN` activity anchors and preserve each option's disposition in the exported user response.
|
|
880
|
-
- **Cited ids resolve to a home**: every id the record defines is
|
|
880
|
+
- **Cited ids resolve to a home**: every id the record defines is reachable — a page-global anchor (`#id-<id>`; cross-check consensus and dissent cards are `#id-xv-<id>`) for the rows a view declares in `HumanReportView.anchored_fields`, for the clarification articles, the activity table and the end-state section; a card-scoped anchor (`#id-<parent>-<id>`, `scoped_anchor_fields`) for rows that repeat under every parent, such as a direction's scope commitments; and the evidence ledger for every other row with an id — so prose, citation lists, and table cells link to it (`report_html/common.py` `anchor_index`, `report_html/filters.py`). Enforced by `tests/report/test_report_index.py` over every fixture (every indexed id anchored, every in-page link landing, every defined id reachable) and by `validators/validate-report-views.py` check 7 (no dead in-page link) at Phase 7. Two id families are cited everywhere and defined nowhere in the record, so the renderer follows the two pointers the run pinned (`report_html/context_links.py`): the brief's end-state rows (`EB`/`PB`/`EO`) are read from `<task>/instruction-set/task-brief.md` and rendered once, each joined with this run's `endStateCoverage` verdict (and rendered from the verdicts alone when the task has no brief file), as the "End states" section with `#id-EB-001` anchors, and the clarification ids a previous run settled link to that run's page through `clarificationCarryIn.sourceFile` (`../../<type>/reports/final-report-<type>-<seq>.html#id-C-005`). A missing file yields no rows and no links, never a render failure. Rows that repeat under every parent — a direction's `IC-`/`PI-` rows are numbered from 001 inside each direction — anchor inside their parent's card as `#id-<parent>-<id>` and are linked only from that card's own prose (`common.scoped_anchor_map`, the view's `cardLinks`). Four more link sources fill ids the page defines nowhere: a planning report's selected direction (`selectedDirectionRef.optionId`) links to its card on the option-selection page named by `sourceReport` when that record exists; an implementation report's plan ids (`VC-`, `PI-`, plan items) link to the approved plan's page named by `approvedPlanReference.planFile` when that record exists (`context_links.approved_plan_links`); a worker finding number (`F-NNN`) links to the one `evidence.primary[]` row whose `sourceItems` cite it, and stays text when two rows share it or none does (`common.worker_finding_links`); and a `summary[]` row some field cites by id lands in the ledger as a summary point, while uncited digest rows stay out. Clarification option text (answer, rationale, added work, direction change) goes through the same link filter; the option's `data-option-value` and `<option>` text stay verbatim because they are the response-matching values.
|
|
881
881
|
- **Implementation-option-selection cards**: each ranked direction reads as four blocks — what it changes (goal, expected change areas, scope commitments), how it is done (core mechanism, architecture boundaries), why it ranks where it does (criterion scores beside the criterion weights, feasibility votes with counterevidence, safety blockers, unresolved facts), and the expected outcome (coverage count and verdict, requirement coverage, planning invariants). The evaluation criteria and their weights render as their own section before the ranking, the direction picker shows each option's goal, and the candidate audit shows each rejected candidate's goal.
|
|
882
882
|
- **Schema v1 compatibility**: existing v1 data and quick Markdown reports keep the legacy conditional renderer, including `readerSummary`, reader modes, and the original Markdown-parsing path.
|
|
883
883
|
- **`C-*` select option order (schema v1)**: the legacy renderer parses `Expected form`, puts the `Recommended:` answer **first**, and relabels the `Alternatives:` items consecutively as `(a)`, `(b)`, and so on (the original character labels are not retained). Schema v2 parses nothing here — a `Kind=decision` row carries `options[]`, and the presentation order is the array order with the `role: recommended` entry first.
|
package/docs/cli.md
CHANGED
|
@@ -845,7 +845,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
845
845
|
| `okstra design-snapshot --narrative <report-narrative.md> --output <design-preparation.json>` | Detect implementation-planning design surfaces and write the detector-owned snapshot consumed by final report assembly. |
|
|
846
846
|
| `okstra plan-verify --narrative <report-narrative.md> --state <plan-body-verification.json>` | Recompute the plan-body gate from the convergence-owned state before `data.json` publication. `--report <historical-data.json>` remains the v2 reader. |
|
|
847
847
|
| `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, then checks, renders, spawns follow-ups, validates, and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; only `teardown-stages` is skipped so a failed run does not reclaim worktrees. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
|
|
848
|
-
| `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly, and schema v3 data uses the same always-generated, task-specific human HTML path. The full reading copy uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Passing the Markdown sibling locates the same data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity,
|
|
848
|
+
| `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly, and schema v3 data uses the same always-generated, task-specific human HTML path. The full reading copy uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Passing the Markdown sibling locates the same data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, Response ID parity, and that every in-page `href="#…"` lands on an element of the page. For a non-English report the command prints two counts: `translated N string(s) into <lang> (M left in English, K unresolved)` from the sidecar overlay, and `rendered R line(s) still in English on the <lang> page` from the written page itself — the second sees fields the extractor does not offer, so `M = 0` with `R > 0` means a reader-facing key is missing from `PROSE_KEYS`. |
|
|
849
849
|
| `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 |
|
|
850
850
|
| `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. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `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 |
|
|
851
851
|
| `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 |
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -24,12 +24,19 @@ Report assembly reads the role-owned inputs, validates them, derives links and s
|
|
|
24
24
|
|
|
25
25
|
An active clarification exists only in `activeClarifications[]`. A decision carried from a previous run exists only in `carriedDecisions[]`; do not recreate it as an active question.
|
|
26
26
|
|
|
27
|
-
Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <instruction-set/clarification-response.md> --clarification-id C-NNN` — repeat `--clarification-id` to take several in one call. That bundle is the source of truth for an earlier run's answer: it is task-level and cumulative, so no prior run seq has to be located, and each response section names the report that posed the question, which is where the row's `statement`, `expectedForm`, and options come from. A carried row lands as `answered`, not `resolved` — it was resolved in another run, and `resolution.checkRefs` names *this* run's activity rows. Carrying an answer also obliges a `supersessionLedger` entry for it.
|
|
27
|
+
Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). The carry-in record is the `--clarification-response` file; for a new plan it is the option-selection record `--selected-direction` names, and for an implementation run the approved plan `--approved-plan` names (`render._carry_in_source`) — the same pointer assembly writes to `clarificationCarryIn.sourceFile`, so the page links those ids to the prior run's page. A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <instruction-set/clarification-response.md> --clarification-id C-NNN` — repeat `--clarification-id` to take several in one call. That bundle is the source of truth for an earlier run's answer: it is task-level and cumulative, so no prior run seq has to be located, and each response section names the report that posed the question, which is where the row's `statement`, `expectedForm`, and options come from. A carried row lands as `answered`, not `resolved` — it was resolved in another run, and `resolution.checkRefs` names *this* run's activity rows. Carrying an answer also obliges a `supersessionLedger` entry for it.
|
|
28
28
|
|
|
29
29
|
Each decision option has `role`, `answer`, `rationale`, `disposition`, `reach`, optional `scopeEffects`, `addedWork`, and `directionChange`. `reach` is exactly one of `in-repo` or `cross-repo`. `scopeEffects` may contain `new-schema` and `deferrable`. A `correctness-critical` option cannot use `select` or `accept-risk`; a `noncritical-dissent` option cannot use `select`.
|
|
30
30
|
|
|
31
31
|
Resolution `checkRefs` name existing `A-NNN` activity rows. Those activity rows carry `clarificationRefs[]`; their `planItemIds[]` let report assembly derive the reverse plan-item links. Do not store copied plan or activity identifiers in `approvalContext`.
|
|
32
32
|
|
|
33
|
+
## Field content rules
|
|
34
|
+
|
|
35
|
+
Two rules the assembled record is checked against, both learned from the 2026-09-05 audit of shipped pages:
|
|
36
|
+
|
|
37
|
+
- **An empty value is empty, never a null literal.** A prose field with nothing to say is omitted or left `""`. `"None"`, `"null"`, `"undefined"` and `"NaN"` are serialisation artefacts the page prints as text (`tradeoffMatrix` cells in nlpvibe planning-009…014). The lowercase `none` an option's `addedWork`/`directionChange` uses to mean "nothing" is a contract token and stays. **Enforced:** `validators/validate-run.py` `_validate_no_null_literals_in_prose` fails a prose field holding one of those literals.
|
|
38
|
+
- **Citations name rows the reader can reach.** `evidenceRefs`, `supportingEvidence` and the other citation lists carry the record's own ids (`E-`, `CV-`, `D-`, `EA-`, `C-`, `A-`) or a `path:line`. A worker's own finding number (`F-NNN`) reaches the reader only through a promoted `evidence.primary[]` row whose `sourceItems` name it as `<worker>:F-NNN` — the page links a bare `F-NNN` to that row when exactly one row carries it, and leaves it as dead text otherwise. **Enforced:** `_unbridged_worker_finding_refs` in `validators/validate-run.py` warns on every bare `F-NNN` no `sourceItems` entry carries; the narrative rule `_validate_no_opaque_id_references` already fails one in a reader-facing sentence.
|
|
39
|
+
|
|
33
40
|
## Report-writer dispatch
|
|
34
41
|
|
|
35
42
|
For report contract 3.0, prompt materialization first freezes one report synthesis
|
|
@@ -90,7 +90,7 @@ profile document.
|
|
|
90
90
|
- if a schema-v1 table or an analysis-worker result table requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(pick 1 of A / B)` or `(pick N of …)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
91
91
|
- For schema v2, data.json is canonical and the HTML exports answers to a user-response sidecar; the source report is never edited. `--resume-clarification` carries those answers into the next run. The lower-level `--clarification-response <path>` remains available for scripted runs.
|
|
92
92
|
- When a response is carried in, reconcile every prior `clarificationItems[]` row against new evidence and update its status to `resolved` or `obsolete` before issuing the next verdict. Schema-v1 compatibility Markdown may additionally render its conditional Section 0; the schema-v2 full reading copy records decisions under `## Clarification and User Decisions`.
|
|
93
|
-
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). **Enforced:** `validators/validate-run.py` `_validate_supersession_ledger` requires an entry per answered clarification; whether the claim is *true* is what the §5.5.9 adversarial round tests.
|
|
93
|
+
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). A plan built from a selected direction inherits the answers the option-selection record carried before it has any statement to retire, so those carried rows need no entry; the rows this plan itself raised and settled still do. **Enforced:** `validators/validate-run.py` `_validate_supersession_ledger` requires an entry per answered clarification, exempting the ledger's `carriedDecisions[]` ids on a selected-direction plan; whether the claim is *true* is what the §5.5.9 adversarial round tests.
|
|
94
94
|
- Verdict Card data consistency (shared; schema-v1 Markdown keeps the legacy visible card):
|
|
95
95
|
- The Card carries no verdict token — the token lives once, in `finalVerdict.verdictToken`, and every gate reads it there. `verdictCard.direction` byte-matches `finalVerdict.direction`; next-step routing agrees with `recommendedNextSteps[0]`. The full reading copy and human summary are derived from the data fields without repeating both visible sections. **Enforced in part:** the v3.0 schema's `verdictCard` is `additionalProperties: false` with no verdict-token property, so the token cannot be duplicated onto the Card, and `scripts/okstra_ctl/report_narrative.py` `_writer_owned_schema` applies the finished report's `$defs.Direction` enum to the narrative, rejecting an off-enum `direction` while the writer can still be re-run. The byte-match between the two `direction` fields is not compared by anything — assembly overwrites `nextStep` on both when the plan-body gate passes (`scripts/okstra_ctl/report_assembly.py:590-604`) but leaves `direction` as the writer wrote it.
|
|
96
96
|
- Cross-worker traceability (shared — applies to every analysis worker output and to the lead's `## 6.` / `## 2.` tables in the final-report):
|
|
@@ -125,7 +125,7 @@ roles:
|
|
|
125
125
|
- A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
|
|
126
126
|
- **Backtrace at authoring (`Blocks=approval` rows).** Every approval blocker this run opens must be traceable to the plan by the time the report is assembled, through one of the two link shapes `okstra incremental-scope` reads: an activity citing the row's `C-NNN` in `clarificationRefs` and the affected `P-*` ids in `planItemIds` (assembly derives the plan items' `clarificationRefs` from exactly this — a row opened before plan items existed gets its backtrace on a later activity once the Stage Map is written), or the blocked requirement's coverage row carrying `blocked C-NNN`. An unlinked id cannot auto-narrow the answered re-run: `incremental-scope` returns `mode == "unresolved"` and the user must name stage numbers by hand. The plan-body promotion path (step 8) already records these links; this rule extends the same obligation to rows raised during planning itself — the observed unlinked rows were all of that class. A genuinely plan-wide blocker links the plan-wide items it judges (`P-Dir-1`, `P-Dep-*`); it still cannot narrow, and that is the honest answer for it. **Enforced (advisory):** `validators/validate-run.py` `_validate_approval_clarification_backtrace` fails an open approval blocker with no link or a link resolving to no stage.
|
|
127
127
|
- Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
|
|
128
|
-
- 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.
|
|
128
|
+
- 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. A selected-direction `requirementCoverage[]` row is keyed by `originalRequirementId` (the brief's `EB-`/`PB-`/`EO-` id); prose, cross-check statements and coverage claims cite that id. An `R-NNN` reaches the reader only if the row defines it in `id` — a number the plan never defines is a bare token with nothing to land on (2026-09-05, dev-10626 planning-001 cited `R-001`–`R-007` that no row carried).
|
|
129
129
|
- 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`.
|
|
130
130
|
- 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.
|
|
131
131
|
- **Do not chase English heading substrings.** The Markdown heading scan (`collect_validation_errors`) is the fallback branch of `collect_plan_errors` in `validators/validate-implementation-plan-stages.py` and runs only for a report carrying no `implementationPlanning` data — historical v1 Markdown. A v2 report is validated from its data (`collect_data_validation_errors`, same file). The v2 full reading copy template renders nine headings and serialises the plan as JSON beneath them, so the v1 heading substrings cannot appear, and a report is not defective for lacking them.
|
|
@@ -1949,6 +1949,27 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1949
1949
|
_initialize_report_ledgers(ctx, payload)
|
|
1950
1950
|
|
|
1951
1951
|
|
|
1952
|
+
# 이 run 이 답변을 물려받는 레코드. phase 마다 진입 인자가 다르다: 분석 phase 는
|
|
1953
|
+
# `--clarification-response`, 새 계획은 `--selected-direction`(후보비교 레코드),
|
|
1954
|
+
# 구현은 `--approved-plan`(계획 레코드). 2026-09-05 실측(dev-10626 planning):
|
|
1955
|
+
# 첫 갈래만 보던 동안 계획·구현 run 은 원장이 빈 채로 시작해 C-NNN 을 행 없이
|
|
1956
|
+
# 인용했다.
|
|
1957
|
+
_CARRY_IN_SOURCE_KEYS = (
|
|
1958
|
+
("CLARIFICATION_RESPONSE_PATH", "CLARIFICATION_RESPONSE_RELATIVE_PATH"),
|
|
1959
|
+
("SELECTED_DIRECTION_PATH", "SELECTED_DIRECTION_RELATIVE_PATH"),
|
|
1960
|
+
("APPROVED_PLAN_PATH", "APPROVED_PLAN_RELATIVE_PATH"),
|
|
1961
|
+
)
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
def _carry_in_source(ctx: Mapping[str, Any]) -> tuple[str, str]:
|
|
1965
|
+
"""(절대 경로, run 상대 참조) — 없으면 ("", "")."""
|
|
1966
|
+
for path_key, ref_key in _CARRY_IN_SOURCE_KEYS:
|
|
1967
|
+
value = str(ctx.get(path_key) or "")
|
|
1968
|
+
if value:
|
|
1969
|
+
return value, str(ctx.get(ref_key) or value)
|
|
1970
|
+
return "", ""
|
|
1971
|
+
|
|
1972
|
+
|
|
1952
1973
|
def _initialize_report_ledgers(ctx: Mapping[str, Any], manifest: Mapping[str, Any]) -> None:
|
|
1953
1974
|
approval_value = str(ctx.get("APPROVAL_DECISIONS_PATH") or "")
|
|
1954
1975
|
approval_path = Path(approval_value)
|
|
@@ -1964,16 +1985,12 @@ def _initialize_report_ledgers(ctx: Mapping[str, Any], manifest: Mapping[str, An
|
|
|
1964
1985
|
})
|
|
1965
1986
|
# 이월 결정은 여기서 심는다. 리드가 손으로 carry 하던 동안 대부분의
|
|
1966
1987
|
# run 이 빈 채로 갔고, 리포트는 행 없는 C-NNN 을 인용했다.
|
|
1967
|
-
|
|
1968
|
-
if
|
|
1988
|
+
source_value, source_ref = _carry_in_source(ctx)
|
|
1989
|
+
if source_value:
|
|
1969
1990
|
from .approval_decisions import seed_carried_decisions
|
|
1970
1991
|
|
|
1971
1992
|
seed_carried_decisions(
|
|
1972
|
-
approval_path,
|
|
1973
|
-
Path(response_value),
|
|
1974
|
-
source_run_ref=str(
|
|
1975
|
-
ctx.get("CLARIFICATION_RESPONSE_RELATIVE_PATH") or response_value
|
|
1976
|
-
),
|
|
1993
|
+
approval_path, Path(source_value), source_run_ref=source_ref,
|
|
1977
1994
|
)
|
|
1978
1995
|
activity_value = str(ctx.get("LEAD_EVENTS_PATH") or "")
|
|
1979
1996
|
activity_path = Path(activity_value)
|
|
@@ -304,10 +304,16 @@ def _run_inputs_clarification_response(manifest_path: Path) -> str:
|
|
|
304
304
|
except (JsonBoundaryError, OSError):
|
|
305
305
|
return ""
|
|
306
306
|
inputs = payload.get("inputs") if isinstance(payload, Mapping) else None
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
307
|
+
if not isinstance(inputs, Mapping):
|
|
308
|
+
return ""
|
|
309
|
+
# 새 계획은 후보비교 레코드를, 구현은 계획 레코드를 물려받는다 — 답변은 그
|
|
310
|
+
# 레코드의 행이다. 첫 키만 보던 동안 두 phase 의 `clarificationCarryIn` 은
|
|
311
|
+
# 비었고, 열람본의 C-NNN 은 이전 run 의 페이지로 이어지지 못했다.
|
|
312
|
+
for key in ("clarificationResponsePath", "selectedDirectionPath", "approvedPlanPath"):
|
|
313
|
+
value = inputs.get(key)
|
|
314
|
+
if isinstance(value, str) and value.strip():
|
|
315
|
+
return value.strip()
|
|
316
|
+
return ""
|
|
311
317
|
|
|
312
318
|
|
|
313
319
|
def _verification_scope(project_root: Path, manifest: Mapping[str, Any]) -> str:
|