okstra 0.189.2 → 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 +3 -3
- 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-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +5 -1
- package/runtime/python/okstra_ctl/plan_items_cli.py +12 -0
- 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/python/okstra_ctl/worker_audit_check.py +23 -8
- 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 +220 -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
|
@@ -816,7 +816,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
816
816
|
| `okstra usage-report [--days <positive-int>] [--project-root <dir>] [--cwd <dir>] [--text\|--json]` | Read-only backend for the okstra-usage skill. `--text` emits ordered fixed labels for model use. The default and `--json` preserve the full machine JSON contract and exit codes. Defaults to the current project's last 30 days. |
|
|
817
817
|
| `okstra worker-state transition --team-state <path> --worker <id> --status <in-progress\|completed\|timeout\|error\|not-run> [--reason <text>] [--model <execution-value>]` | Atomically update one persisted worker row. `in-progress` records the authoritative `startedAt` and clears `endedAt`; terminal states record `endedAt`; `timeout`, `error`, and `not-run` require a reason. Dispatch adapters use this same transition path, so CLI-backed and in-process orchestration share the status timestamp contract |
|
|
818
818
|
| `okstra worker-liveness [--team-state <path> --worker <id>]… [--max-idle <seconds>] [--launch-grace <seconds>] [--stall-confirm <seconds>] [--json]` | Judge whether pending workers are still alive so the lead's poll ends a stalled wait early instead of paying the full deadline. The selector repeats; each `--team-state` must have a paired `--worker`. The worker row's `livenessMode` picks the probe: `audit-heartbeat` reads its `auditSidecarPath` and reports `stalled` when the `- PROGRESS:` heartbeat is past the idle budget; `wrapper-status` reads its `promptPath` and reports `did-not-launch` when neither the wrapper `.log` nor `.status.json` appears. Both graces start at the persisted `startedAt`, never at an artifact mtime — the audit sidecar is reused on re-dispatch, so a heartbeat older than this dispatch counts as no signal yet rather than a stall. A heartbeat budget breach is confirmed before it is reported: the probe re-reads the sidecar after `--stall-confirm` seconds (default: half that stage's budget; `0` disables) and reports `stalled` only when the newest heartbeat has not advanced, so a worker inside one long uninterruptible tool call is not judged dead for being slow. Healthy probes report `live`. It only judges—it never kills or re-dispatches. Exit 1 on an unhealthy verdict, so a poll loop can branch without parsing JSON. The heartbeat line shape and budget come from the `okstra_ctl.worker_heartbeat` SSOT shared with the Phase 7 audit (`validators/validate_session_conformance.py`) |
|
|
819
|
-
| `okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> --seq <nnn> [--worker <id>]` | Apply the Phase 7 worker audit-sidecar rules mid-run, while the worker session is still alive. For each of this run's `worker-results/<worker>-<task-type>-<seq>.md` it checks that the file carries no `## 0. Reading Confirmation` heading, that the matching audit sidecar exists, and — for prompts carrying the required-v1 evidence-ledger marker — that every backticked `path:line` citation has an Evidence read row in that sidecar. `--worker` scopes it to the role that just returned. Emits `{ok, failures[]}` and exits 2 when `failures[]` is non-empty. The rules come from the `okstra_ctl.worker_audit_ledger` SSOT shared with `validate-run.py`, so an early pass and the Phase 7 pass cannot disagree. Run it right after collecting a result: the same failure at Phase 7 leaves only a retroactive edit, which breaks the audit chain, or a failed run |
|
|
819
|
+
| `okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> --seq <nnn> [--worker <id>]` | Apply the Phase 7 worker audit-sidecar rules mid-run, while the worker session is still alive. For each of this run's `worker-results/<worker>-<task-type>-<seq>.md` it checks that the file carries no `## 0. Reading Confirmation` heading, that the matching audit sidecar exists, and — for prompts carrying the required-v1 evidence-ledger marker — that every backticked `path:line` citation has an Evidence read row in that sidecar. `--worker` scopes it to the role that just returned. Emits `{ok, inspected, inspectedFiles[], failures[], blocking[], advisory[], runImpact}` and exits 2 when `failures[]` (= `blocking` + `advisory`) is non-empty. Exit 2 means "fix it now", not "the run fails": only `blocking` rows (no audit sidecar) fail the run at Phase 7, while `advisory` rows (a citation with no matching Evidence read row) never fail the run and are only repairable while the worker session is alive — do not reject or re-dispatch a result over an advisory row alone. The rules come from the `okstra_ctl.worker_audit_ledger` SSOT shared with `validate-run.py`, so an early pass and the Phase 7 pass cannot disagree. Run it right after collecting a result: the same failure at Phase 7 leaves only a retroactive edit, which breaks the audit chain, or a failed run |
|
|
820
820
|
| `okstra log-report [--project-root <dir>] [--cwd <dir>] [--top <N>] [--json]` | Read-only inventory of wrapper transcript `.log` files and their sibling prompt `.md` files. Each ranked entry preserves `path` / `sizeBytes` for compatibility and also reports `transcriptPath`, `transcriptBytes`, `promptPath`, `promptBytes`, and `transcriptToPromptRatio`; totals distinguish prompt bytes from transcript bytes and count paired files. Ranking remains transcript-size descending |
|
|
821
821
|
| `okstra recap <assemble\|record\|note> <task-root\|task-key> …` | Backend for the okstra-inspect `recap` facet. `assemble` is read-only and prints a JSON summary of phase transitions across a task's runs. `record --kind <summary\|qa> --mode <artifact\|code> --answer <text> [--question <text>] [--citation <path:line> …]` appends one line to `<task-root>/recap/recap-log.jsonl` and never mutates other artifacts. `note --kind <verification-evidence\|decision-draft\|analysis-note> --slug <topic> --purpose <text> --scope-note <text> (--body <markdown>\|--body-file <path>)` writes an agent-authored note to `<task-root>/notes/` and prints its path plus the `--clarification-response` argument for feeding it into a later run |
|
|
822
822
|
| `okstra user-response <list-view\|show-view\|begin\|answer\|plan-decision\|legacy-report-authoring\|finalize> …` | Backend for the `/okstra-user-response` skill. `list-view` and `show-view --report <md\|data.json> --project-root <dir>` are fixed-text model views; `show-view` validates that the report belongs to the explicit project root and prints each open row's why-asked line, linked plan items, and cited `path:line` artifacts so the skill can read them before asking. The legacy `list` and `show` JSON reads retain their automation-compatible fields. `begin --report <md\|data.json> --task-key <key>` returns an opaque transaction id. A predefined clarification choice uses `answer --transaction <id> --clarification-id <C-NNN> --kind <kind> --option-number <N>`; Python resolves the answer, disposition, reach, and scope effects from the validated report. Direct input instead uses `--disposition <answer\|reframe> --value-file <md> [--rationale-file <md>]`. Every value, rationale, and reason file must be a regular file under `<PROJECT_ROOT>/.okstra/tmp/user-response/`; external paths and symbolic links are rejected. `plan-decision` accepts `approved`, `revision-requested`, or `rejected`, validates any `--implementation-option` against the report candidates, and requires `--reason-file` for the latter two statuses. `legacy-report-authoring` is restricted to report contract 2.0. `finalize` validates the complete existing sidecar before a lossless merge, uses compare-and-swap under a run-local lock, and atomically publishes only the user-owned sidecar; exit 0 ok / 1 error. |
|
|
@@ -837,7 +837,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
837
837
|
| `okstra codex-run <args…>` | Codex lead-adapter dry-run entry point. Accepts the same arguments as `render-bundle` but owns `--render-only --lead-runtime codex`. It prepares the task bundle and prints the prompt for the Codex lead without dispatching workers |
|
|
838
838
|
| `okstra worker-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Provider-neutral deterministic dispatcher for `runner=cli-wrapper` assignments. It verifies each adjacent invocation specification against the immutable run manifest immediately before process creation and records `core-pre-dispatch`; native-session rows stay with the host. The default selects CLI analysis assignments only. Phase 6 uses explicit `--workers report-writer`, and a mixed analysis/report batch is rejected. `--dry-run` performs the same verification and resolution without starting a provider process. |
|
|
839
839
|
| `okstra codex-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Compatibility alias for `okstra worker-dispatch`; it no longer selects a Codex-only transport-agent path. |
|
|
840
|
-
| `okstra agent-prompt materialize\|check-corrections\|apply-corrections\|verify\|record-dispatch\|link-result\|reject-result\|abandon-attempt\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Every run-branch report-writer prompt gets its `## Output` section (narrative, pointer record, reading audit) rendered by okstra, and an instruction body that writes a `## Output` or `## Corrections` heading is refused. A corrective report-writer round — the narrative at `reportNarrativePath` already exists and its structure parses, value defects included — must pass `--corrections <ledger>` (`schemas/report-writer-corrections-v1.0.schema.json`: `replace` / `remove` / `rewrite` entries keyed by the validator's field-path grammar, `baseNarrativePath` naming a preserved copy of the attempt): the ledger is applied to that base and checked against the writer-owned schema and the task's semantic validator before dispatch, every defect is reported at once, and okstra renders the prompt's `## Corrections` section from it; a report-writer materialization without a ledger over such a narrative is refused before any prompt is written, while a narrative whose structure does not parse (line grammar, unknown top-level field) is re-authored without one. `check-corrections --run-manifest <path> --corrections <ledger> [--json]` runs the same check without materializing (exit 1 lists the defects; `mechanical: true` means every entry is a `replace` or `remove` that passed). `apply-corrections` with the same arguments applies such a mechanical ledger without a writer round: it writes the corrected narrative to `reportNarrativePath` and records a `lead-correction-applied` activity row (`evidenceRefs` = ledger path + correction ids) through the run's activity contract; it refuses a ledger with `rewrite` entries or any defect, a base that is the live narrative, a run without `activityContractVersion` 1, and a ledger already applied. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result; one result path belongs to one dispatch, so a corrective round retires the first attempt with `reject-result --dispatch-id <first> --superseded-by <corrective> --reason <text>` before the new link is accepted — the rejected row stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason` rather than being deleted. `abandon-attempt --invocation-ref <ref> --reason <text>` closes a started attempt whose worker died without producing a result — the one case neither `link-result` (which needs the result file) nor the dispatch-failure path covers — so a retry can follow it instead of the run having to be re-rendered. It refuses any attempt whose `writePolicy.sourcePolicy.mode` is not `source-readonly`: closing an attempt records `failed-no-mutation`, which is true by policy for a read-only worker and a guess for a mutating one. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. A published prompt is immutable, so re-running `materialize` with an edited instruction file fails as `existing_invocation_conflict`; `--replace-undispatched` is the one exit, for a call that failed a pre-dispatch gate and therefore ran nowhere — it covers a differing prompt and a differing metadata alike, since the two are published together and describe one call. It republishes prompt and metadata together, and it is verified rather than trusted — a row in `agentDispatches` or `workerDispatches` naming this `invocationId` refuses the replacement and names the dispatch that used it. |
|
|
840
|
+
| `okstra agent-prompt materialize\|check-corrections\|apply-corrections\|verify\|record-dispatch\|link-result\|reject-result\|abandon-attempt\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Every run-branch report-writer prompt gets its `## Output` section (narrative, pointer record, reading audit) rendered by okstra, and an instruction body that writes a `## Output` or `## Corrections` heading is refused. A corrective report-writer round — the narrative at `reportNarrativePath` already exists and its structure parses, value defects included — must pass `--corrections <ledger>` (`schemas/report-writer-corrections-v1.0.schema.json`: `replace` / `remove` / `rewrite` entries keyed by the validator's field-path grammar, `baseNarrativePath` naming a preserved copy of the attempt): the ledger is applied to that base and checked against the writer-owned schema and the task's semantic validator before dispatch, every defect is reported at once, and okstra renders the prompt's `## Corrections` section from it; a report-writer materialization without a ledger over such a narrative is refused before any prompt is written, while a narrative whose structure does not parse (line grammar, unknown top-level field) is re-authored without one. `check-corrections --run-manifest <path> --corrections <ledger> [--json]` runs the same check without materializing (exit 1 lists the defects; `mechanical: true` means every entry is a `replace` or `remove` that passed). `apply-corrections` with the same arguments applies such a mechanical ledger without a writer round: it writes the corrected narrative to `reportNarrativePath` and records a `lead-correction-applied` activity row (`evidenceRefs` = ledger path + correction ids) through the run's activity contract; it refuses a ledger with `rewrite` entries or any defect, a base that is the live narrative, a run without `activityContractVersion` 1, and a ledger already applied. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result; one result path belongs to one dispatch, so a corrective round retires the first attempt with `reject-result --dispatch-id <first> --superseded-by <corrective> --reason <text>` before the new link is accepted — the rejected row stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason` rather than being deleted. The corrective dispatch is a new invocation: an invocation whose last attempt finished with a mutation takes no further attempt (`execution_manifest._validate_next_attempt` lets only `failed-no-mutation` be followed), so a retry attempt of the rejected invocation itself is refused by the manifest, and `reject-result` does not make it possible. `abandon-attempt --invocation-ref <ref> --reason <text>` closes a started attempt whose worker died without producing a result — the one case neither `link-result` (which needs the result file) nor the dispatch-failure path covers — so a retry can follow it instead of the run having to be re-rendered. It refuses any attempt whose `writePolicy.sourcePolicy.mode` is not `source-readonly`: closing an attempt records `failed-no-mutation`, which is true by policy for a read-only worker and a guess for a mutating one. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. A published prompt is immutable, so re-running `materialize` with an edited instruction file fails as `existing_invocation_conflict`; `--replace-undispatched` is the one exit, for a call that failed a pre-dispatch gate and therefore ran nowhere — it covers a differing prompt and a differing metadata alike, since the two are published together and describe one call. It republishes prompt and metadata together, and it is verified rather than trusted — a row in `agentDispatches` or `workerDispatches` naming this `invocationId` refuses the replacement and names the dispatch that used it. |
|
|
841
841
|
| `okstra team dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--jobs-file <path>] [--dry-run]` / `okstra team await --project-root <dir> --run-manifest <path> [--json]` / `okstra team teardown --project-root <dir> --run-manifest <path> [--dry-run] [--json]` | Read a `leadRuntime=external` run manifest and dispatch, await, or tear down pane-backed workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a pane cannot be opened, gracefully degrade to the CLI wrapper and record the fallback in `workerDispatches[].degradedFrom` |
|
|
842
842
|
| `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <assigned-id> (--summary <text>\|--summary-file <markdown>) --outcome <outcome> [--plan-item-id <current-id>]… [--command <text> --command-cwd <dir> --command-exit-code <n> --command-output-file <markdown>] [--request-ref <returned-ref>]` | Append one structured activity after checking the agent against this run's role assignments and every plan item against its current convergence state. Python returns an `activityRequestRef`; supply only that returned value with `--request-ref` to retry idempotently. A new call without it remains a distinct activity even with identical contents. Legacy JSON command records remain automation compatibility only. |
|
|
843
843
|
| `okstra agent-activity project --project-root <dir> --run-manifest <path> --data <data.json>` | Project this run's canonical activity events into `agentActivity[]`. The command preserves event order, rejects duplicate or decreasing activity IDs, and replaces no other report field. A historical manifest without `activityContractVersion: 1` returns an empty projection and leaves data.json unchanged. Normal Phase 7 execution reaches this behavior through `report-finalize`; use the standalone command only for diagnostics. |
|
|
@@ -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):
|
|
@@ -22,7 +22,7 @@ are collected and convergence finished. Phase 1-5 do not need it.
|
|
|
22
22
|
- their independent verdict (PASS / CONCERNS / FAIL),
|
|
23
23
|
- cited diff snippets supporting the verdict,
|
|
24
24
|
- the verifier's `Read-only command log` (every command they ran with exact invocation and exit code, in execution order — copied verbatim from the worker result),
|
|
25
|
-
- **independent validation re-run results** — per plan-validation command: command line, exit code, and tail of output captured by the verifier (not the executor); any divergence from the executor's reported result MUST be called out as a `Discrepancy` line citing both sides, and when nothing diverged the line is omitted rather than written as `None` — an empty `discrepancy` is how "no divergence" is recorded, and non-empty text there with verdict `PASS` fails the run,
|
|
25
|
+
- **independent validation re-run results** — per plan-validation command: command line, exit code, and tail of output captured by the verifier (not the executor); any divergence from the executor's reported result MUST be called out as a `Discrepancy` line citing both sides — and, when the divergence rests on a plan `validationChecklist` row, naming that row with its `phase` (`VC-003 (phase: mid)`; `_validate_verifier_discrepancy_names_checklist_phase` fails a row cited without it) — and when nothing diverged the line is omitted rather than written as `None` — an empty `discrepancy` is how "no divergence" is recorded, and non-empty text there with verdict `PASS` fails the run,
|
|
26
26
|
- **style / lint / type-check results** — each check-only tool the verifier ran, its exit code, and the count of new findings attributable to lines this run introduced. When no tool is configured for a touched language, record the single line `no lint/style tool configured for <language>`,
|
|
27
27
|
- any fix recommendations the verifier declined to apply.
|
|
28
28
|
The Okstra lead synthesises a unified verdict but MUST preserve dissent — do not collapse opinions into one paragraph. External Tier 3 advisory results are excluded from this aggregate promotion and remain user-owned follow-up evidence. If any other verifier issued `FAIL` on a `Discrepancy` line, the synthesised verdict MUST be `FAIL`. There is no exception and no override: `_validate_verifier_fail_blocks_verdict` in `validators/validate-run.py` fails any report whose `finalVerdict.verdictToken` passes while a `verifierResults[]` row records `FAIL`, and it reads that row alone — a rationale written beside it changes nothing. A divergence the lead believes is not the code's (a committed flaky-test record, a documented environment delta) belongs in the routing recommendation and the user-owned follow-up, and is settled in the next fix run where the verifier re-checks the finding and cites it `resolved`.
|
|
@@ -23,7 +23,7 @@ Every verifier acts as a QA gate, not just a diff reviewer. Trusting the executo
|
|
|
23
23
|
|
|
24
24
|
Verifier obtains the QA command set from exactly two declared sources, in order — there is **no fallback to guessing tools from manifest files**.
|
|
25
25
|
|
|
26
|
-
1. **Tier 1 — plan validation set (task-specific):** every command listed under the approved plan's `validation` block (pre / mid / post). The plan is the file at this prompt's `**Approved plan:**` anchor, scoped to the stage its `**Stage for this implementation run:**` anchor names; both are generated headers, so a missing one is `contract-violated`, never a value to infer.
|
|
26
|
+
1. **Tier 1 — plan validation set (task-specific):** every command listed under the approved plan's `validation` block (pre / mid / post). The plan is the file at this prompt's `**Approved plan:**` anchor, scoped to the stage its `**Stage for this implementation run:**` anchor names; both are generated headers, so a missing one is `contract-violated`, never a value to infer. Each checklist row also carries `phase`, and the phase is part of what the row asserts: `pre` runs before the stage edits, `mid` between the edits and the stage commit, `post` after the commit. A `mid` diff-scope check (`git diff --name-only` listing the touched paths) is reproduced over the stage range — `git diff --name-only <stage base>...HEAD` — once the stage has committed; its empty output on the clean post-commit tree is the plan's own step order, not a divergence. A `Discrepancy` that cites a checklist row names the row and its phase as `VC-NNN (phase: mid)`; a verifier that reads the command without the phase has read half the row. **Enforced:** `_validate_verifier_discrepancy_names_checklist_phase` in `validators/validate-run.py` fails a divergence that cites a `VC-` row without that row's phase.
|
|
27
27
|
2. **Tier 2 — project baseline:** the project's standing QA baseline from the `Project QA Commands` section emitted by `okstra model-io project-context --project-root <PROJECT_ROOT> --task-ref <task-ref>`.
|
|
28
28
|
```json
|
|
29
29
|
{
|
|
@@ -146,7 +146,7 @@ Tier 3 external-advisory discrepancies are excluded from this promotion: preserv
|
|
|
146
146
|
|
|
147
147
|
**When the re-run matched, leave `discrepancy` empty and omit the `Discrepancy` line.** The field records a divergence, so an empty field *is* the record of "no divergence" — the schema makes it optional for exactly that. Do not write `None`, `n/a`, or a sentence explaining that nothing diverged: the check reads any non-empty text as a recorded divergence, so a verifier that states its clean result in prose is failed for the result it is reporting.
|
|
148
148
|
|
|
149
|
-
**Enforced:** `_validate_verifier_discrepancy_is_not_passed` in `validators/validate-run.py` fails a non-empty `discrepancy` recorded with verdict `PASS
|
|
149
|
+
**Enforced:** `_validate_verifier_discrepancy_is_not_passed` in `validators/validate-run.py` fails a non-empty `discrepancy` recorded with verdict `PASS`, and `_validate_verifier_discrepancy_names_checklist_phase` fails a `discrepancy` that cites a plan `validationChecklist` row without that row's `phase` (§ Tier 1). `CONCERNS` remains available for the Tier 3 external-advisory divergence this rule excludes — the report carries no tier field, so `PASS` is the only case decidable here.
|
|
150
150
|
|
|
151
151
|
### Read-only command log (per verifier)
|
|
152
152
|
|
|
@@ -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.
|
|
@@ -175,7 +175,11 @@ def _add_dispatch_parsers(commands: argparse._SubParsersAction) -> None:
|
|
|
175
175
|
reject_result = commands.add_parser(
|
|
176
176
|
"reject-result",
|
|
177
177
|
help="mark a linked result rejected so a corrective re-dispatch can "
|
|
178
|
-
"claim its path"
|
|
178
|
+
"claim its path. The corrective dispatch is a new invocation: an "
|
|
179
|
+
"invocation whose last attempt finished with a mutation takes no "
|
|
180
|
+
"further attempt (only `failed-no-mutation` may be followed), so "
|
|
181
|
+
"a retry of the rejected invocation is refused by the execution "
|
|
182
|
+
"manifest, not by this command",
|
|
179
183
|
)
|
|
180
184
|
_common_paths(reject_result)
|
|
181
185
|
reject_result.add_argument("--run-manifest", required=True)
|
|
@@ -1374,6 +1374,18 @@ def _apply_verdicts(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
1374
1374
|
)
|
|
1375
1375
|
if args.round_number < 1:
|
|
1376
1376
|
raise PlanItemContractError("--round must be 1 or greater")
|
|
1377
|
+
if (
|
|
1378
|
+
isinstance(verification, Mapping)
|
|
1379
|
+
and verification.get("gating") is False
|
|
1380
|
+
and args.round_number > 1
|
|
1381
|
+
):
|
|
1382
|
+
# `complete-round` 가 같은 조건으로 거절하지만 그때는 이미 라운드 2 판정이
|
|
1383
|
+
# 상태에 쓰인 뒤다 — 판정은 있는데 `roundHistory` 에 그 라운드가 없는
|
|
1384
|
+
# 불일치가 남고, 상태 파일은 수렴 엔진 소유라 복구 경로가 없었다
|
|
1385
|
+
# (2026-09-05 실측, dev-10626 planning). 쓰기 전에 같은 문장으로 거절한다.
|
|
1386
|
+
raise PlanItemContractError(
|
|
1387
|
+
"advisory plan-body gating allows one verification round"
|
|
1388
|
+
)
|
|
1377
1389
|
project_root = _probe_project_root(getattr(args, "run_manifest", None))
|
|
1378
1390
|
writer = (
|
|
1379
1391
|
_append_item_verdicts if getattr(args, "append", False)
|
|
@@ -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:
|