okstra 0.125.1 → 0.125.3
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/project-structure-overview.md +0 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/bin/okstra-incremental-carry.py +10 -0
- package/runtime/bin/okstra-incremental-scope.py +10 -0
- package/runtime/prompts/lead/convergence.md +9 -1
- package/runtime/prompts/lead/plan-body-verification.md +16 -8
- package/runtime/prompts/lead/report-writer.md +3 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -0
- package/runtime/python/okstra_ctl/incremental_carry.py +24 -5
- package/runtime/python/okstra_ctl/incremental_scope.py +8 -3
- package/runtime/python/okstra_ctl/render.py +3 -3
- package/runtime/schemas/final-report-v1.0.schema.json +9 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +39 -0
- package/runtime/templates/reports/brief.template.md +29 -0
- package/runtime/templates/reports/final-report.template.md +3 -3
- package/runtime/validators/validate-brief.py +34 -0
- package/runtime/validators/validate-run.py +32 -8
- package/runtime/templates/prd/brief.template.md +0 -286
|
@@ -317,7 +317,6 @@ Token/cost accounting:
|
|
|
317
317
|
| `templates/reports/final-report.template.md` | Jinja2 final-report Markdown template |
|
|
318
318
|
| `templates/reports/report.css`, `report.js` | Inline assets for self-contained HTML report view |
|
|
319
319
|
| `templates/reports/*.template.md` | Inputs, schedule, user-response, settings templates |
|
|
320
|
-
| `templates/prd/brief.template.md` | Brief template |
|
|
321
320
|
| `templates/project-docs/task-index.template.md` | Project task index template |
|
|
322
321
|
|
|
323
322
|
### 4.8 `schemas/`
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -148,7 +148,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Ant
|
|
|
148
148
|
|
|
149
149
|
Before invoking the Antigravity CLI, you MUST:
|
|
150
150
|
|
|
151
|
-
1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Antigravity via stdin — already contains this anchor; do not strip it.
|
|
151
|
+
1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Antigravity via stdin — already contains this anchor; do not strip it. **Exception — reverify dispatches**: a Phase 5.5 re-verification prompt (its `**Prompt History Path:**` carries a `-reverify-r<N>-` segment) deliberately omits this anchor, because lightweight mode judges the supplied findings without re-reading source materials. Proceed without it; do NOT return a sentinel and do NOT synthesize the preamble path. The `**Errors log path:**` / `**Errors sidecar path:**` gate under "Error reporting" still applies to reverify prompts.
|
|
152
152
|
2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
|
|
153
153
|
|
|
154
154
|
The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/antigravity-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Antigravity Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Section-0 placement follows the worker preamble §"Reading rules" (canonical). If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
|
|
@@ -148,7 +148,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Cod
|
|
|
148
148
|
|
|
149
149
|
Before invoking the Codex CLI, you MUST:
|
|
150
150
|
|
|
151
|
-
1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Codex via stdin — already contains this anchor; do not strip it.
|
|
151
|
+
1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Codex via stdin — already contains this anchor; do not strip it. **Exception — reverify dispatches**: a Phase 5.5 re-verification prompt (its `**Prompt History Path:**` carries a `-reverify-r<N>-` segment) deliberately omits this anchor, because lightweight mode judges the supplied findings without re-reading source materials. Proceed without it; do NOT return a sentinel and do NOT synthesize the preamble path. The `**Errors log path:**` / `**Errors sidecar path:**` gate under "Error reporting" still applies to reverify prompts.
|
|
152
152
|
2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
|
|
153
153
|
|
|
154
154
|
The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/codex-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Codex Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Section-0 placement follows the worker preamble §"Reading rules" (canonical). If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Thin CLI wrapper — installed to ~/.okstra/bin/. Delegates to okstra_ctl."""
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
7
|
+
from okstra_ctl.incremental_carry import main # noqa: E402
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Thin CLI wrapper — installed to ~/.okstra/bin/. Delegates to okstra_ctl."""
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
7
|
+
from okstra_ctl.incremental_scope import main # noqa: E402
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -251,7 +251,7 @@ Call `await_workers(handles)` through the same adapter and apply the shared term
|
|
|
251
251
|
|
|
252
252
|
### Required reverify-prompt anchor headers (BLOCKING)
|
|
253
253
|
|
|
254
|
-
Every reverify prompt MUST start with
|
|
254
|
+
Every reverify prompt MUST start with these 7 anchor headers — in this exact order, before any other content:
|
|
255
255
|
|
|
256
256
|
```
|
|
257
257
|
**Project Root:** <absolute-path>
|
|
@@ -259,8 +259,16 @@ Every reverify prompt MUST start with the same 5 anchor headers used in the init
|
|
|
259
259
|
**Result Path:** runs/<task-type>/worker-results/<role-slug>-reverify-r<N>-<task-type>-<seq>.md
|
|
260
260
|
Assigned worker prompt history path: <Project Root>/<Prompt History Path>
|
|
261
261
|
**Model:** <role>, <modelExecutionValue>
|
|
262
|
+
**Errors log path:** <absolute-path>
|
|
263
|
+
**Errors sidecar path:** <absolute-path>
|
|
262
264
|
```
|
|
263
265
|
|
|
266
|
+
The two errors paths carry the same absolute values the lead forwarded in the initial Phase 4 dispatch for that role (source: the launch prompt's `## Run Logs (error-log wiring)` section). Omitting either one makes a CLI-wrapper worker return `<WORKER>_ERRORS_PATH_MISSING` before it invokes its CLI — the path-delivery contract in [team-contract](./team-contract.md) "Error reporting" is not relaxed for reverify, because a reverify dispatch can fail the same way an initial dispatch can.
|
|
267
|
+
|
|
268
|
+
Relative to the Phase 4 anchor set rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()`, a reverify prompt adds `**Model:**` and drops two anchors whose targets lightweight mode never reads: `**Worker Preamble Path:**` and `**Coding preflight pack:**`.
|
|
269
|
+
|
|
270
|
+
The rationale for both drops is §"Reverify prompt: required-reading suppression" below.
|
|
271
|
+
|
|
264
272
|
`<modelExecutionValue>` MUST be resolved from one of these canonical sources, in priority order:
|
|
265
273
|
|
|
266
274
|
1. `task-manifest.json` → `resultContract.requiredWorkerRoles[].modelExecutionValue` for the receiving role
|
|
@@ -42,6 +42,7 @@ Plan-body verification is configured under `convergence.planBodyVerification` in
|
|
|
42
42
|
|---------|---------|-------------|
|
|
43
43
|
| `enabled` | `true` | If `false`, the round is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). |
|
|
44
44
|
| `maxRounds` | `1` | Upper bound. Plan-body verification is consistency / completeness checking, not fact checking — additional rounds rarely help. Range 1–3. |
|
|
45
|
+
| `selfFixMaxRounds` | `3` | Upper bound on the report-writer self-fix loop (§"Round protocol" step 7). Range 1–5. The loop also stops early on no-progress, so this is a ceiling, not a target. |
|
|
45
46
|
| `gating` | `true` | If `true` (default), `majority-disagree` blocks the Approval marker. If `false`, the round is advisory-only and the marker always renders. |
|
|
46
47
|
|
|
47
48
|
Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false.
|
|
@@ -126,27 +127,33 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
126
127
|
- `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
|
|
127
128
|
- `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
|
|
128
129
|
- `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes), OR any single-vote-blocking kind fires: one `DISAGREE(a)` / `DISAGREE(d)` on any item, or one `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks the Approval marker**.
|
|
130
|
+
- `needs-reverify` — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not. This does **not** block the Approval marker — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` returns `needs-reverify` for this shape and `_recompute_plan_body_gate` folds it into `passed-with-dissent`.
|
|
129
131
|
- `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
|
|
130
132
|
5. Gate result resolution:
|
|
131
133
|
- any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
|
|
132
134
|
- all dispatches non-result → `aborted-non-result`
|
|
133
|
-
- any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
|
|
135
|
+
- any `partial-consensus` / `dissent-isolated` / `needs-reverify` present, no `majority-disagree` → `passed-with-dissent`
|
|
134
136
|
- all items `full-consensus` → `passed`
|
|
135
137
|
6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v1.0.schema.json`; template at `templates/reports/final-report.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
|
|
136
|
-
7. **Self-fix
|
|
138
|
+
7. **Self-fix loop (up to `selfFixMaxRounds`, targeting planner-fixable defects).** After aggregation, while at least one `majority-disagree` item has a majority of its `DISAGREE` verdicts at `fixability == planner-fixable`, lead runs self-fix rounds **before** promoting anything to the user:
|
|
137
139
|
- lead instructs report-writer to rewrite only those planner-fixable items for correction (NOT a full draft regeneration — patch only the plan section each `P-*` points to; procedure in [report-writer](./report-writer.md) §"Self-fix rewrite").
|
|
138
140
|
- missing or weak `P-Prep-*` contracts are repaired by adding kind-specific inline detail or an AI-prepared PREP item with a concrete proposal. Facts that require user or external authority remain `blocked` and keep their request material; never invent those facts during self-fix.
|
|
139
|
-
- lead re-runs plan-body verification
|
|
141
|
+
- lead re-runs plan-body verification (focused on the corrected items + adjacent items the rewrite touched, plus any `needs-reverify` items whose peer failed to vote last round). After re-verification, overwrite `planItems[].verdicts` with the new verdicts.
|
|
140
142
|
- for an item whose `majority-disagree` was resolved by self-fix, record `self-fixed in round <N>: <what was fixed>` in `planItems[].selfFixNote`. A resolved item does not create a clarification.
|
|
141
|
-
-
|
|
143
|
+
- **Loop termination.** Record the round count in `planBodyVerification.selfFixRoundsApplied` and why the loop stopped in `planBodyVerification.selfFixStopReason`:
|
|
144
|
+
- `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
|
|
145
|
+
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning.
|
|
146
|
+
- `max-rounds-reached` — `selfFixRoundsApplied == selfFixMaxRounds`. Exit.
|
|
147
|
+
- `not-attempted` — the loop never ran because no item qualified.
|
|
148
|
+
The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
|
|
142
149
|
- a `majority-disagree` item with a majority of `needs-user-input` is NOT a self-fix target — it goes straight to the next step's clarification promotion.
|
|
143
|
-
8. For every `majority-disagree` item **that remains after the self-fix
|
|
150
|
+
8. For every `majority-disagree` item **that remains after the self-fix loop** (items not resolved by self-fix, or with a `needs-user-input` majority from the start), lead adds a row to `## 1. Clarification Items` with:
|
|
144
151
|
- new `C-<N>` ID (numbering continues from any existing rows)
|
|
145
152
|
- `Statement` summarising the disagreement and the worker breakage `<kind>`
|
|
146
153
|
- `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
|
|
147
154
|
- `Blocks=approval`
|
|
148
155
|
- the item's `planItems[].clarificationId` set to that `C-<N>` (1:1 link). `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item's `clarificationId` is missing, dangling, or points at a non-`approval` row.
|
|
149
|
-
- a `planner-fixable` item that survives the self-fix
|
|
156
|
+
- a `planner-fixable` item that survives the self-fix loop is still promoted, but its `Statement` MUST state "planner self-fix attempted but unresolved" and name the stop reason. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails when a planner-fixable majority item is promoted while the budget is not exhausted — it requires `selfFixRoundsApplied >= 1` **and** `selfFixStopReason` in `{no-progress, max-rounds-reached}`, so neither `all-resolved` nor `not-attempted` can excuse a promotion.
|
|
150
157
|
9. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
|
|
151
158
|
|
|
152
159
|
## `plan-body-verification-<task-type>-<seq>.json` schema
|
|
@@ -160,7 +167,8 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
160
167
|
"gating": true,
|
|
161
168
|
"verificationMode": "lightweight",
|
|
162
169
|
"gateResult": "passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result",
|
|
163
|
-
"
|
|
170
|
+
"selfFixRoundsApplied": 0,
|
|
171
|
+
"selfFixStopReason": "not-attempted | all-resolved | no-progress | max-rounds-reached",
|
|
164
172
|
"planItems": [
|
|
165
173
|
{
|
|
166
174
|
"id": "P-Opt-1",
|
|
@@ -193,7 +201,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
193
201
|
|
|
194
202
|
`planItems[].votes.<worker>` is the verbatim verdict token emitted by the worker — `AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT` — or `verification-error` for terminal non-result dispatches. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
|
|
195
203
|
|
|
196
|
-
`planBodyVerification.
|
|
204
|
+
`planBodyVerification.selfFixRoundsApplied` (int, default 0): how many self-fix rounds actually ran. `planBodyVerification.selfFixStopReason` (enum, default `not-attempted`): why the loop exited — see §"Round protocol" step 7. `planItems[].verdicts[].fixability` is each `DISAGREE`'s `planner-fixable | needs-user-input` judgement, recorded in the final data.json (`§5.5.9`).
|
|
197
205
|
|
|
198
206
|
## Plan-body reverify prompt
|
|
199
207
|
|
|
@@ -235,7 +235,9 @@ When lead instructs a self-fix round (passing the target `P-*` list + the reason
|
|
|
235
235
|
- requirement-coverage mapping error → remap to a real option/stage/step or XP row.
|
|
236
236
|
- missing/weak design-prep contract → fix with kind-specific inline detail or an AI-prepared PREP item carrying a concrete `aiProposal`. Do NOT invent facts requiring user/external authority — keep them `blocked` + request material.
|
|
237
237
|
|
|
238
|
-
After correcting, leave `self-fixed in round <N>: <what was fixed>` in that `planItems[].selfFixNote` and
|
|
238
|
+
After correcting, leave `self-fixed in round <N>: <what was fixed>` in that `planItems[].selfFixNote` and set `planBodyVerification.selfFixRoundsApplied` to the number of rounds run so far. `needs-user-input` items are NEVER a correction target — they are promoted to clarification as-is.
|
|
239
|
+
|
|
240
|
+
Lead may instruct several rounds (bounded by `selfFixMaxRounds`, see [plan-body-verification](./plan-body-verification.md) §"Round protocol" step 7). Each round targets only the items still broken, so a round that fixes nothing new ends the loop — correcting an item by breaking a sibling counts as no progress, not progress.
|
|
239
241
|
|
|
240
242
|
### Final-verification verdict token contract (BLOCKING)
|
|
241
243
|
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
- **recommendedNextSteps policy:** keep the substance of cross-project preconditions/carries in `crossProjectDependencies`, and put in `§3 Recommended Next Steps` only a pointer to that section (`§5.4 Cross-Project Dependencies`) — no double recording.
|
|
115
115
|
- validation checklist (pre / mid / post) — each item is an exact command or observable outcome
|
|
116
116
|
- rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
|
|
117
|
+
- **Requirement admissibility (scope boundary):** a brief line becomes a Requirement Coverage row only when **a stage can satisfy it by changing files in this repository** — source, tests, config, or deployment *manifest files*. A line whose satisfaction needs a person's approval, a ticket status change, or an action against live infrastructure (applying a manifest, a cutover, creating a dashboard or alert, validating in staging/production) is NOT a requirement for this phase: it belongs to the brief's `## External Gates`, and this plan neither creates a stage for it nor cites it in coverage. Briefs generated by `okstra-brief-gen` pre-split these into `## Acceptance Criteria` (admissible) and `## External Gates` (not); when reading an older brief that carries a raw Definition-of-Done checklist, apply the same test line by line. The boundary is the *action*, not the topic — "add the flag to `values-prod.yaml`" is admissible, "apply that manifest to prod" is not. Planning an operational stage this phase cannot execute (see the run-scope rule above forbidding deployments) produces steps whose commands never resolve, which the §5.5.9 gate then correctly blocks — the plan must not create that deadlock in the first place.
|
|
117
118
|
- **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` cites the brief heading or file/line where the requirement came from. `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, or `blocked C-NNN`. If any row is `gap` or `blocked C-NNN`, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
|
|
118
119
|
- **Review-rule compliance plan:** when a project-local review rule pack is found, each Option Candidate MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
|
|
119
120
|
- the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
|
|
@@ -299,6 +299,21 @@ def _parse_stage_csv(value: str | None, *, option: str) -> set[int] | None:
|
|
|
299
299
|
raise CarryError(f"{option} contains an invalid stage number") from exc
|
|
300
300
|
|
|
301
301
|
|
|
302
|
+
def _load_data(path: str, *, option: str) -> dict:
|
|
303
|
+
"""Read one data.json as a CarryError-reporting operation. Unreadable or
|
|
304
|
+
malformed input means the carry cannot happen, which is what CarryError
|
|
305
|
+
already communicates — without this the caller gets a traceback instead of
|
|
306
|
+
the documented `carry refused:` refusal it knows how to fall back from.
|
|
307
|
+
"""
|
|
308
|
+
try:
|
|
309
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
310
|
+
except (OSError, ValueError) as exc:
|
|
311
|
+
raise CarryError(f"{option} could not be read: {exc}") from exc
|
|
312
|
+
if not isinstance(data, dict):
|
|
313
|
+
raise CarryError(f"{option} must contain a JSON object")
|
|
314
|
+
return data
|
|
315
|
+
|
|
316
|
+
|
|
302
317
|
def main(argv: list[str]) -> int:
|
|
303
318
|
ap = argparse.ArgumentParser(prog="okstra incremental-carry")
|
|
304
319
|
ap.add_argument("--prev-data", required=True, help="prior run final-report data.json")
|
|
@@ -309,9 +324,9 @@ def main(argv: list[str]) -> int:
|
|
|
309
324
|
ap.add_argument("--out", required=True, help="path to write the merged data.json")
|
|
310
325
|
args = ap.parse_args(argv)
|
|
311
326
|
|
|
312
|
-
prev = json.loads(Path(args.prev_data).read_text(encoding="utf-8"))
|
|
313
|
-
cur = json.loads(Path(args.cur_data).read_text(encoding="utf-8"))
|
|
314
327
|
try:
|
|
328
|
+
prev = _load_data(args.prev_data, option="--prev-data")
|
|
329
|
+
cur = _load_data(args.cur_data, option="--cur-data")
|
|
315
330
|
merged = merge_carried_forward(
|
|
316
331
|
prev,
|
|
317
332
|
cur,
|
|
@@ -325,9 +340,13 @@ def main(argv: list[str]) -> int:
|
|
|
325
340
|
print(f"carry refused: {exc}", file=sys.stderr)
|
|
326
341
|
return 1
|
|
327
342
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
343
|
+
try:
|
|
344
|
+
Path(args.out).write_text(
|
|
345
|
+
json.dumps(merged, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
346
|
+
)
|
|
347
|
+
except OSError as exc:
|
|
348
|
+
print(f"carry refused: --out could not be written: {exc}", file=sys.stderr)
|
|
349
|
+
return 1
|
|
331
350
|
carried = sum(
|
|
332
351
|
1
|
|
333
352
|
for item in merged.get("implementationPlanning", {})
|
|
@@ -109,9 +109,9 @@ def main(argv: list[str]) -> int:
|
|
|
109
109
|
ap.add_argument("--prep-items", default="", help="comma-separated changed PREP item IDs")
|
|
110
110
|
args = ap.parse_args(argv)
|
|
111
111
|
|
|
112
|
-
data = json.loads(Path(args.prev_data).read_text(encoding="utf-8"))
|
|
113
|
-
stages = parse_stage_graph(data)
|
|
114
112
|
try:
|
|
113
|
+
data = json.loads(Path(args.prev_data).read_text(encoding="utf-8"))
|
|
114
|
+
stages = parse_stage_graph(data)
|
|
115
115
|
impacted = {int(t.strip()) for t in args.impacted.split(",") if t.strip()}
|
|
116
116
|
prep_ids = {t.strip() for t in args.prep_items.split(",") if t.strip()}
|
|
117
117
|
impacted.update(design_prep_impacted_stages(data, prep_ids))
|
|
@@ -123,7 +123,12 @@ def main(argv: list[str]) -> int:
|
|
|
123
123
|
stages=stages, impacted_stages=impacted,
|
|
124
124
|
prev_base_sha=args.prev_base_sha, cur_base_sha=args.cur_base_sha,
|
|
125
125
|
)
|
|
126
|
-
|
|
126
|
+
# Every bad input degrades to a full re-verification rather than raising:
|
|
127
|
+
# `full` is always the safe answer, and the caller reads the decision off
|
|
128
|
+
# stdout, so a traceback would leave it with no decision at all. OSError
|
|
129
|
+
# covers an unreadable --prev-data, KeyError/TypeError a stageMap row that
|
|
130
|
+
# is missing `stage` or is not a mapping.
|
|
131
|
+
except (OSError, ValueError, KeyError, TypeError) as exc:
|
|
127
132
|
decision = IncrementalDecision(
|
|
128
133
|
"full", [], [], f"invalid incremental-scope input: {exc}",
|
|
129
134
|
)
|
|
@@ -156,7 +156,6 @@ _FM_TAGS_CATALOG: dict[str, list[str]] = {
|
|
|
156
156
|
"quick-input": ["quick", "input"],
|
|
157
157
|
"final-report": ["final-report"],
|
|
158
158
|
"schedule": ["schedule"],
|
|
159
|
-
"prd-brief": ["prd", "brief"],
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
|
|
@@ -185,8 +184,6 @@ def _doc_type_from_template_path(template_path: str) -> str:
|
|
|
185
184
|
stem = name[: -len(".template.md")]
|
|
186
185
|
else:
|
|
187
186
|
stem = Path(name).stem
|
|
188
|
-
if stem == "brief" and "prd" in Path(template_path).parts:
|
|
189
|
-
return "prd-brief"
|
|
190
187
|
return stem
|
|
191
188
|
|
|
192
189
|
|
|
@@ -1160,6 +1157,8 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
1160
1157
|
False otherwise
|
|
1161
1158
|
- `planBodyVerification` is implementation-planning specific; the key is
|
|
1162
1159
|
always emitted (dead-letter on other phases) so the schema stays stable.
|
|
1160
|
+
Its `selfFixMaxRounds` default 3 bounds the report-writer self-fix loop
|
|
1161
|
+
that runs before a planner-fixable defect is promoted to the user.
|
|
1163
1162
|
|
|
1164
1163
|
ctx knobs honoured:
|
|
1165
1164
|
- `OKSTRA_PLAN_VERIFICATION`: "true" | "false" | "" (empty → default True).
|
|
@@ -1199,6 +1198,7 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
1199
1198
|
"planBodyVerification": {
|
|
1200
1199
|
"enabled": plan_verify_enabled,
|
|
1201
1200
|
"maxRounds": 1,
|
|
1201
|
+
"selfFixMaxRounds": 3,
|
|
1202
1202
|
"gating": True,
|
|
1203
1203
|
},
|
|
1204
1204
|
}
|
|
@@ -1844,7 +1844,15 @@
|
|
|
1844
1844
|
"aborted-non-result"
|
|
1845
1845
|
]
|
|
1846
1846
|
},
|
|
1847
|
-
"
|
|
1847
|
+
"selfFixRoundsApplied": { "type": "integer", "minimum": 0 },
|
|
1848
|
+
"selfFixStopReason": {
|
|
1849
|
+
"enum": [
|
|
1850
|
+
"not-attempted",
|
|
1851
|
+
"all-resolved",
|
|
1852
|
+
"no-progress",
|
|
1853
|
+
"max-rounds-reached"
|
|
1854
|
+
]
|
|
1855
|
+
},
|
|
1848
1856
|
"planItems": {
|
|
1849
1857
|
"type": "array",
|
|
1850
1858
|
"items": {
|
|
@@ -564,6 +564,39 @@ stop conditions (codebase-scan-specific additions):
|
|
|
564
564
|
|
|
565
565
|
When both of the above conditions hold, sharpening may end even if budget remains.
|
|
566
566
|
|
|
567
|
+
### Splitting a source checklist (Definition of Done / acceptance list)
|
|
568
|
+
|
|
569
|
+
Issue-tracker templates almost always ship one checklist that mixes work an
|
|
570
|
+
agent does with work a person does — a Linear "Definition of Done" typically
|
|
571
|
+
carries `tests written and passing` next to `reviewed by ≥1 dev`,
|
|
572
|
+
`validated in staging by <name>`, and `ticket moved to Done`. Copied across
|
|
573
|
+
whole, every one of those lines reads to a downstream phase as a requirement,
|
|
574
|
+
because a markdown checkbox carries no owner.
|
|
575
|
+
|
|
576
|
+
Split every such checklist across the two sections using one test:
|
|
577
|
+
|
|
578
|
+
> **Can an okstra phase satisfy this by changing files in this repository?**
|
|
579
|
+
|
|
580
|
+
- **Yes → `## Acceptance Criteria`.** Source, tests, config, and deployment
|
|
581
|
+
*manifest files* all count — editing `values-prod.yaml` is a file change.
|
|
582
|
+
- **No → `## External Gates`.** A person's approval or sign-off, manual QA,
|
|
583
|
+
validation performed in staging/production, creating a dashboard or alert,
|
|
584
|
+
running a deployment, and ticket status transitions. Record who or what owns
|
|
585
|
+
it (`Brice`, `release manager`, `CI`), so the reader can act on it outside
|
|
586
|
+
okstra.
|
|
587
|
+
|
|
588
|
+
The distinction is the *action*, not the topic: "add the RSS flag to
|
|
589
|
+
`values-prod.yaml`" is a criterion; "apply that manifest to prod" and "confirm
|
|
590
|
+
the new-app count on the Metabase dashboard with Brice" are gates.
|
|
591
|
+
|
|
592
|
+
Never drop a gate to make the brief tidier — an omitted release step is worse
|
|
593
|
+
than a correctly-parked one. Never promote a gate into Acceptance Criteria to
|
|
594
|
+
look thorough either: an okstra phase cannot satisfy it, so it can only surface
|
|
595
|
+
later as an unmeetable requirement that blocks the run.
|
|
596
|
+
|
|
597
|
+
If a line is genuinely ambiguous, put it in `## Open Questions` and let
|
|
598
|
+
`requirements-discovery` resolve the owner.
|
|
599
|
+
|
|
567
600
|
### Augmentation labels (REQUIRED — no unlabelled augmentation)
|
|
568
601
|
|
|
569
602
|
Every augmentation — both inline `> augmented: …` blockquotes and
|
|
@@ -611,6 +644,10 @@ Required sections:
|
|
|
611
644
|
- **Problem / Symptom** — current state. For bugs: repro + observed/expected;
|
|
612
645
|
for greenfield: gap between current and desired.
|
|
613
646
|
- **Desired Outcome** — success shape, not a solution prescription.
|
|
647
|
+
- **Acceptance Criteria** — must-pass points an okstra phase can satisfy by
|
|
648
|
+
changing repository files. See "Splitting a source checklist" below.
|
|
649
|
+
- **External Gates** — must-pass points owned by a person or by live
|
|
650
|
+
infrastructure. Recorded for the reader; never a requirement for any phase.
|
|
614
651
|
- **Constraints** — deadlines, compatibility, technical/operational limits.
|
|
615
652
|
- **Related Artifacts** — files, URLs, issues, prior task-keys.
|
|
616
653
|
- **Related Task Graph** — structured task-to-task edges when the work was
|
|
@@ -709,6 +746,8 @@ The installed template `~/.okstra/templates/reports/brief.template.md` is the by
|
|
|
709
746
|
| `## Problem / Symptom` | Required | Not required — omit | Required |
|
|
710
747
|
| `## Context` | Required | Required | Required |
|
|
711
748
|
| `## Desired Outcome` | Required | Required | Required |
|
|
749
|
+
| `## Acceptance Criteria` | Required — `_(none)_` or one bullet per repo-satisfiable must-pass point | Required — `_(none)_` or bullets | Required — `_(none)_` or bullets |
|
|
750
|
+
| `## External Gates` | Required — `_(none)_` or one bullet per person/infra-owned gate | Required — `_(none)_` or bullets | Required — `_(none)_` or bullets |
|
|
712
751
|
| `## Constraints` | Required | Required | Required |
|
|
713
752
|
| `## Related Artifacts` | Required | Required | Required |
|
|
714
753
|
| `## Related Task Graph` | Required — `_(none)_` or canonical table | Required — `_(none)_` or canonical table | Required — `_(none)_` or canonical table |
|
|
@@ -73,6 +73,35 @@ between current and desired.>
|
|
|
73
73
|
|
|
74
74
|
<!-- Do NOT prescribe a solution — that belongs to implementation-planning. -->
|
|
75
75
|
|
|
76
|
+
## Acceptance Criteria
|
|
77
|
+
|
|
78
|
+
<One bullet per must-pass point that okstra phases can satisfy by changing
|
|
79
|
+
files in this repository. Use _(none)_ if none.>
|
|
80
|
+
|
|
81
|
+
<!-- Admissibility test — a criterion belongs here only if it is satisfied by
|
|
82
|
+
changing repository files (source, tests, config, deployment manifests).
|
|
83
|
+
Anything that needs a person's approval, a ticket status change, or an action
|
|
84
|
+
against live infrastructure goes to `## External Gates` instead. Downstream
|
|
85
|
+
phases treat this section as the requirement set: `implementation-planning`
|
|
86
|
+
builds one Requirement Coverage row per bullet, `final-verification` gates
|
|
87
|
+
acceptance on it. -->
|
|
88
|
+
|
|
89
|
+
- <criterion satisfied by a repository file change>
|
|
90
|
+
|
|
91
|
+
## External Gates
|
|
92
|
+
|
|
93
|
+
<Must-pass points owned by a person or by live infrastructure — peer review
|
|
94
|
+
sign-off, manual QA by a named person, staging/production validation,
|
|
95
|
+
dashboard or alert setup, ticket status transitions. Use _(none)_ if none.>
|
|
96
|
+
|
|
97
|
+
<!-- These are recorded so the reader knows the full release picture, and are
|
|
98
|
+
NOT requirements for any okstra phase: no phase plans a stage for them, no
|
|
99
|
+
phase gates on them, and they never become acceptance blockers. Ticket
|
|
100
|
+
templates routinely mix these with real criteria — split them here rather
|
|
101
|
+
than downstream. -->
|
|
102
|
+
|
|
103
|
+
- <gate>: <who or what owns it>
|
|
104
|
+
|
|
76
105
|
## Constraints
|
|
77
106
|
|
|
78
107
|
<Deadlines, compatibility, technical/operational limits. Use _(none)_ if
|
|
@@ -262,10 +262,10 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
|
|
|
262
262
|
|
|
263
263
|
### Validation Checklist{% if t("sectionAside.validationChecklist") != "Validation Checklist" %} ({{ t("sectionAside.validationChecklist") }}){% endif %}
|
|
264
264
|
|
|
265
|
-
|
|
|
266
|
-
|
|
265
|
+
| {{ t("columns.recordMeta") }} | Check | Command / Observation | Expected outcome |
|
|
266
|
+
|--------|-------|------------------------|-------------------|
|
|
267
267
|
{% for row in implementationPlanning.validationChecklist -%}
|
|
268
|
-
| {{ row.id | mdcell }}
|
|
268
|
+
| **{{ row.id | mdcell }}**<br>Ticket: `{{ row.ticketId | mdcell }}`<br>Phase: {{ row.phase | mdcell }} | {{ row.check | mdcell }} | `{{ row.commandOrObservation | mdcell }}` | {{ row.expectedOutcome | mdcell }} |
|
|
269
269
|
{% endfor %}
|
|
270
270
|
|
|
271
271
|
### Rollback Strategy{% if t("sectionAside.rollbackStrategy") != "Rollback Strategy" %} ({{ t("sectionAside.rollbackStrategy") }}){% endif %}
|
|
@@ -426,6 +426,39 @@ def check_requirement_section(text: str, errors: list[str]) -> None:
|
|
|
426
426
|
)
|
|
427
427
|
|
|
428
428
|
|
|
429
|
+
# Splitting the source checklist across these two sections is what keeps a
|
|
430
|
+
# person-owned gate ("validated in staging by <name>") out of the requirement
|
|
431
|
+
# set. Both must exist even when empty, because a missing heading is
|
|
432
|
+
# indistinguishable from "the reporter listed no gates" — and the silent
|
|
433
|
+
# version of that mistake is what puts an unmeetable requirement into
|
|
434
|
+
# Requirement Coverage.
|
|
435
|
+
SCOPE_SPLIT_SECTIONS = ("Acceptance Criteria", "External Gates")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def check_scope_split_sections(text: str, errors: list[str]) -> None:
|
|
439
|
+
"""Both halves of the requirement/gate split must exist with a body.
|
|
440
|
+
|
|
441
|
+
`## Acceptance Criteria` holds must-pass points an okstra phase can satisfy
|
|
442
|
+
by changing repository files; `## External Gates` holds the ones a person or
|
|
443
|
+
live infrastructure owns (okstra-brief-gen SKILL.md §"Splitting a source
|
|
444
|
+
checklist"). `_(none)_` stays valid — it is the contract's explicit
|
|
445
|
+
placeholder for a deliberately-empty section.
|
|
446
|
+
"""
|
|
447
|
+
for heading in SCOPE_SPLIT_SECTIONS:
|
|
448
|
+
if not has_section_heading(text, heading):
|
|
449
|
+
errors.append(
|
|
450
|
+
f"required section '## {heading}' is missing (a brief must split "
|
|
451
|
+
"must-pass points into repo-satisfiable criteria and "
|
|
452
|
+
"person/infrastructure-owned gates)"
|
|
453
|
+
)
|
|
454
|
+
continue
|
|
455
|
+
if not section_body(text, heading).strip():
|
|
456
|
+
errors.append(
|
|
457
|
+
f"required section '## {heading}' has an empty body "
|
|
458
|
+
"(use _(none)_ only for a deliberately-empty section)"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
429
462
|
def check_reporter_confirmations(
|
|
430
463
|
rc_status: str | None, reporter_rows: list[str], errors: list[str]
|
|
431
464
|
) -> None:
|
|
@@ -488,6 +521,7 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
488
521
|
check_related_task_graph(text, errors)
|
|
489
522
|
|
|
490
523
|
check_requirement_section(text, errors)
|
|
524
|
+
check_scope_split_sections(text, errors)
|
|
491
525
|
|
|
492
526
|
# 2. brief-id matches filename stem
|
|
493
527
|
stem = path.stem
|
|
@@ -1800,8 +1800,8 @@ _SINGLE_VOTE_BLOCKING_KINDS = {"a", "d"}
|
|
|
1800
1800
|
def _classify_plan_item_gate(item: dict) -> str:
|
|
1801
1801
|
"""Recompute one plan item's gate class from its per-worker verdicts,
|
|
1802
1802
|
per `prompts/lead/plan-body-verification.md` "Round protocol". Returns one of
|
|
1803
|
-
``majority-disagree`` / ``
|
|
1804
|
-
``all-non-result``. Collapses ``partial-consensus`` and
|
|
1803
|
+
``majority-disagree`` / ``needs-reverify`` / ``has-dissent`` /
|
|
1804
|
+
``full-consensus`` / ``all-non-result``. Collapses ``partial-consensus`` and
|
|
1805
1805
|
``dissent-isolated`` into ``has-dissent`` because they resolve to the
|
|
1806
1806
|
same gate value; only the majority-disagree boundary changes the gate.
|
|
1807
1807
|
"""
|
|
@@ -1827,6 +1827,13 @@ def _classify_plan_item_gate(item: dict) -> str:
|
|
|
1827
1827
|
# `a`/`d` for any item; `f` only for P-Req items (requirement coverage).
|
|
1828
1828
|
is_req = str(item.get("id") or "").upper().startswith("P-REQ")
|
|
1829
1829
|
if disagree_kinds & _SINGLE_VOTE_BLOCKING_KINDS or (is_req and "f" in disagree_kinds):
|
|
1830
|
+
# "One confirmed DISAGREE" presupposes the item was actually
|
|
1831
|
+
# cross-verified. When the peer returned a non-result nothing confirmed
|
|
1832
|
+
# the dissent, so blocking here would reproduce the same
|
|
1833
|
+
# worker-failure-makes-the-gate-stricter paradox the majority branch
|
|
1834
|
+
# below guards against. Route it to a re-verify round instead.
|
|
1835
|
+
if len(non_error) < 2:
|
|
1836
|
+
return "needs-reverify"
|
|
1830
1837
|
return "majority-disagree"
|
|
1831
1838
|
# Otherwise a genuine majority is required — and a majority needs at least
|
|
1832
1839
|
# two participating votes, so a lone surviving DISAGREE (its peer returned a
|
|
@@ -1852,7 +1859,7 @@ def _recompute_plan_body_gate(pbv: dict) -> str | None:
|
|
|
1852
1859
|
return "aborted-non-result"
|
|
1853
1860
|
if any(c == "majority-disagree" for c in classes):
|
|
1854
1861
|
return "blocked-by-disagreement"
|
|
1855
|
-
if any(c
|
|
1862
|
+
if any(c in ("has-dissent", "needs-reverify") for c in classes):
|
|
1856
1863
|
return "passed-with-dissent"
|
|
1857
1864
|
return "passed"
|
|
1858
1865
|
|
|
@@ -2402,8 +2409,14 @@ def _validate_plan_body_clarification_matching(data: dict, failures: list[str])
|
|
|
2402
2409
|
)
|
|
2403
2410
|
|
|
2404
2411
|
|
|
2412
|
+
# Stop reasons that justify promoting a still-broken planner-fixable item to the
|
|
2413
|
+
# user: the self-fix budget ran out, or a round produced no net resolution so
|
|
2414
|
+
# further rounds would repeat themselves.
|
|
2415
|
+
_SELF_FIX_EXHAUSTED_REASONS = frozenset({"max-rounds-reached", "no-progress"})
|
|
2416
|
+
|
|
2417
|
+
|
|
2405
2418
|
def _validate_self_fix_before_clarification(data: dict, failures: list[str]) -> None:
|
|
2406
|
-
"""A planner-fixable defect MUST
|
|
2419
|
+
"""A planner-fixable defect MUST exhaust the self-fix budget before it is
|
|
2407
2420
|
promoted to a `## 1. Clarification Items` row. Closes the hole where the
|
|
2408
2421
|
lead dumps a fixable plan defect (abbreviated path, prose command,
|
|
2409
2422
|
placeholder, coverage remap) onto the user instead of having report-writer
|
|
@@ -2418,7 +2431,14 @@ def _validate_self_fix_before_clarification(data: dict, failures: list[str]) ->
|
|
|
2418
2431
|
round_count = pbv.get("roundCount")
|
|
2419
2432
|
if not isinstance(round_count, int) or round_count < 1:
|
|
2420
2433
|
return
|
|
2421
|
-
|
|
2434
|
+
rounds_applied = pbv.get("selfFixRoundsApplied")
|
|
2435
|
+
stop_reason = pbv.get("selfFixStopReason")
|
|
2436
|
+
budget_exhausted = (
|
|
2437
|
+
isinstance(rounds_applied, int)
|
|
2438
|
+
and rounds_applied >= 1
|
|
2439
|
+
and stop_reason in _SELF_FIX_EXHAUSTED_REASONS
|
|
2440
|
+
)
|
|
2441
|
+
if budget_exhausted:
|
|
2422
2442
|
return
|
|
2423
2443
|
for item in pbv.get("planItems") or []:
|
|
2424
2444
|
if not isinstance(item, dict):
|
|
@@ -2431,12 +2451,16 @@ def _validate_self_fix_before_clarification(data: dict, failures: list[str]) ->
|
|
|
2431
2451
|
]
|
|
2432
2452
|
fixable = [v for v in disagrees if v.get("fixability") == "planner-fixable"]
|
|
2433
2453
|
if disagrees and len(fixable) * 2 > len(disagrees):
|
|
2454
|
+
allowed = " / ".join(sorted(_SELF_FIX_EXHAUSTED_REASONS))
|
|
2434
2455
|
failures.append(
|
|
2435
2456
|
"final-report data.json: plan item "
|
|
2436
2457
|
f"`{item.get('id') or '<unknown>'}` is majority-disagree with a "
|
|
2437
|
-
"planner-fixable majority but
|
|
2438
|
-
"
|
|
2439
|
-
"
|
|
2458
|
+
f"planner-fixable majority but the self-fix budget is not "
|
|
2459
|
+
f"exhausted (`selfFixRoundsApplied`={rounds_applied!r}, "
|
|
2460
|
+
f"`selfFixStopReason`={stop_reason!r}; need >=1 rounds and a stop "
|
|
2461
|
+
f"reason of {allowed}). A planner-fixable defect MUST be corrected "
|
|
2462
|
+
"by report-writer self-fix rounds until the budget runs out or a "
|
|
2463
|
+
"round makes no progress, before it becomes a clarification row "
|
|
2440
2464
|
"(plan-body-verification.md Self-fix round)."
|
|
2441
2465
|
)
|
|
2442
2466
|
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: OKSTRA PRD Brief - {{TASK_KEY}}
|
|
3
|
-
id: {{FM_ID}}
|
|
4
|
-
tags: {{FM_TAGS}}
|
|
5
|
-
status: new
|
|
6
|
-
aliases: {{FM_ALIASES}}
|
|
7
|
-
date: {{TASK_DATE}}
|
|
8
|
-
task-id: "{{TASK_ID}}"
|
|
9
|
-
task-group: "{{TASK_GROUP}}"
|
|
10
|
-
project-id: "{{PROJECT_ID}}"
|
|
11
|
-
taskType: "{{FM_TASK_TYPE}}"
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# OKSTRA Task Brief
|
|
15
|
-
|
|
16
|
-
<!--
|
|
17
|
-
This brief is the single source-of-truth document that lets an okstra worker (Claude/Codex/Antigravity) analyze the task without any prior knowledge of the codebase or domain.
|
|
18
|
-
|
|
19
|
-
Principles:
|
|
20
|
-
1. Workers cannot reach external links → embed every piece of primary evidence inline (fenced code block).
|
|
21
|
-
2. Workers don't know domain terms → predefine the key terms in the Domain Glossary.
|
|
22
|
-
3. Keep operator-facing information (okstra.sh commands, working directory) out of the brief — put it in a separate runbook.
|
|
23
|
-
4. The sections you must fill differ by Task Type → complete that task's conditional block.
|
|
24
|
-
5. Mark empty sections explicitly with `_N/A — <reason>_`. Silence is a source of worker confusion.
|
|
25
|
-
-->
|
|
26
|
-
|
|
27
|
-
## Identity
|
|
28
|
-
|
|
29
|
-
| Field | Value |
|
|
30
|
-
|-------|-------|
|
|
31
|
-
| Brief Title | `<task-group>-<TASK-ID>-<short-slug>` |
|
|
32
|
-
| Project ID | `<project-id>` |
|
|
33
|
-
| Task Group | `<task-group>` |
|
|
34
|
-
| Task ID | `<TASK-ID>` |
|
|
35
|
-
| Task Type | `requirements-discovery` \| `error-analysis` \| `implementation-planning` \| `final-verification` |
|
|
36
|
-
| Issue / Ticket | `<TASK-ID> · <title in original language> (English: <translation if non-English>)` |
|
|
37
|
-
| Requested Outcome | <one sentence. The key deliverable this run must produce> |
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Request Summary
|
|
42
|
-
|
|
43
|
-
- **What is being requested or changed?**
|
|
44
|
-
<one paragraph, or 3-5 bullets>
|
|
45
|
-
- **Why now?**
|
|
46
|
-
<business/technical trigger. Deadline, incident, dependent task, etc.>
|
|
47
|
-
- **New / Continuation / Reopened?**
|
|
48
|
-
<One of the three + prior run identifier (if any)>
|
|
49
|
-
- **What decision should this run produce?**
|
|
50
|
-
<the 1-3 key decisions this run must answer. Vagueness distracts the worker>
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Inline Evidence
|
|
55
|
-
|
|
56
|
-
<!--
|
|
57
|
-
Workers cannot reach external URLs / Notion / Linear / Slack. Embed all primary evidence inline here.
|
|
58
|
-
-->
|
|
59
|
-
|
|
60
|
-
### Symptom / Sample Payload
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
<observed symptom, sample response payload, UI screenshot caption, etc. — as text>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Logs / Stack Trace (if applicable)
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
<log excerpt. Mask sensitive information>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Relevant Code Excerpts
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// path: src/domains/upload/dto/upload-job.view.ts:23-45
|
|
76
|
-
<code excerpt — the worker should grasp the essentials without reading the whole file>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### External Doc Excerpts (Notion/Linear/Confluence excerpts)
|
|
80
|
-
|
|
81
|
-
> <quote from the source. The link is secondary; the quote is the substance>
|
|
82
|
-
> -- Source: <doc title + section>
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Domain Glossary
|
|
87
|
-
|
|
88
|
-
<!--
|
|
89
|
-
Codex/Antigravity don't know this codebase. Define 5-15 key terms, one sentence each.
|
|
90
|
-
If a worker guesses at an undefined term, analysis quality drops immediately.
|
|
91
|
-
-->
|
|
92
|
-
|
|
93
|
-
| Term | Definition |
|
|
94
|
-
|------|-----------|
|
|
95
|
-
| `<domain term 1>` | <1-2 sentence definition> |
|
|
96
|
-
| `<domain term 2>` | <1-2 sentence definition> |
|
|
97
|
-
| `<code identifier 1>` | <role + location + relationship to other entities> |
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Current Context
|
|
102
|
-
|
|
103
|
-
- **Current behavior or state**:
|
|
104
|
-
<how the system behaves today>
|
|
105
|
-
- **Desired behavior or outcome**:
|
|
106
|
-
<how it should behave>
|
|
107
|
-
- **Existing related implementation**:
|
|
108
|
-
- `<file-path>:<line-range>` — <one-line role>
|
|
109
|
-
- **Related code paths** (for reference — put direct excerpts in Inline Evidence):
|
|
110
|
-
- `<path>`
|
|
111
|
-
- `<path>`
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Out of Scope
|
|
116
|
-
|
|
117
|
-
<!--
|
|
118
|
-
An explicit exclusion list. Central to preventing worker scope-creep.
|
|
119
|
-
-->
|
|
120
|
-
|
|
121
|
-
Things this run will **not** address:
|
|
122
|
-
|
|
123
|
-
- <out-of-scope item 1 + reason for exclusion>
|
|
124
|
-
- <out-of-scope item 2 + reason for exclusion>
|
|
125
|
-
- <item to be handled in a separate ticket/run — specify the ticket ID>
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Task-Type Focus
|
|
130
|
-
|
|
131
|
-
<!--
|
|
132
|
-
Of the two blocks below, fill only the one matching the current task-type and delete the rest.
|
|
133
|
-
-->
|
|
134
|
-
|
|
135
|
-
### If `requirements-discovery`
|
|
136
|
-
|
|
137
|
-
- **Why might this be a bugfix?**
|
|
138
|
-
<evidence, or "weak signal — none observed">
|
|
139
|
-
- **Why might this be a feature/improvement?**
|
|
140
|
-
<evidence>
|
|
141
|
-
- **Why might this be a refactor/ops?**
|
|
142
|
-
<evidence>
|
|
143
|
-
- **Classification blockers** — what is preventing a confident classification?
|
|
144
|
-
<the evidence gap, specifically>
|
|
145
|
-
|
|
146
|
-
### If `error-analysis`
|
|
147
|
-
|
|
148
|
-
- **Symptom** — what does the user/system observe?
|
|
149
|
-
- **Reproduction Steps** — 1-2-3 format. State the environment/preconditions.
|
|
150
|
-
```
|
|
151
|
-
1. <env: staging | local | prod>
|
|
152
|
-
2. <action>
|
|
153
|
-
3. <action>
|
|
154
|
-
4. Expected: <…> / Actual: <…>
|
|
155
|
-
```
|
|
156
|
-
- **Frequency** — always / intermittent / only under specific conditions / one-off?
|
|
157
|
-
- **Blast Radius** — scope of affected users/requests/data
|
|
158
|
-
- **Suspected Cause(s)** — hypotheses so far. Each with confidence (High/Med/Low) and rationale.
|
|
159
|
-
- **What has been ruled out** — false leads already verified (so the worker doesn't retread them)
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Constraints and Risks
|
|
164
|
-
|
|
165
|
-
- **Business constraints**: <e.g. PII, SLA — do not list external approval/authorization items (assume the user holds all permissions)>
|
|
166
|
-
- **Technical constraints**: <e.g. backward-compat, existing client, schema>
|
|
167
|
-
- **Delivery constraints**: <deadline, dependent deployment, rollout gate — but do not list waiting on external approval or permission checks as schedule factors>
|
|
168
|
-
- **Approval / review checkpoints**: _N/A — assume the user holds all permission and approval authority (okstra default rule). Describe specifically only when a genuine external blocker exists._
|
|
169
|
-
- **Known assumptions**: <explicit assumptions — items for the worker to verify>
|
|
170
|
-
- **Open uncertainties**: <things to confirm — answering them unlocks a decision. Exclude external permission/approval items>
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Configuration / Deployment Context
|
|
175
|
-
|
|
176
|
-
<!--
|
|
177
|
-
Fill this section *only when applicable*. DTO changes, pure logic edits, docs work, etc. are usually _N/A_.
|
|
178
|
-
When irrelevant, replace the entire section with this single line:
|
|
179
|
-
|
|
180
|
-
_N/A — this task does not touch config or deployment._
|
|
181
|
-
-->
|
|
182
|
-
|
|
183
|
-
- **Config files in scope**: `<path>` — <which keys are relevant>
|
|
184
|
-
- **Current observed values**: <key=value, cite the source>
|
|
185
|
-
- **Expected values / invariants**: <what must not change + what must change>
|
|
186
|
-
- **Deployment manifests in scope**: `<helm chart / k8s manifest / terraform path>`
|
|
187
|
-
- **Rollout invariants**: <e.g. zero-downtime, version skew window>
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## External Resource Hints (for Lead pre-fetch)
|
|
192
|
-
|
|
193
|
-
<!--
|
|
194
|
-
A list of external resources the okstra Lead must embed inline into the worker prompt.
|
|
195
|
-
Workers don't access MCP/external tools directly, so the Lead snapshots them ahead of time and embeds them in the prompt.
|
|
196
|
-
-->
|
|
197
|
-
|
|
198
|
-
| Resource | Type | Why needed |
|
|
199
|
-
|----------|------|-----------|
|
|
200
|
-
| `<table-name>` | MySQL schema | <which analysis needs schema verification> |
|
|
201
|
-
| `<library@version>` | Library docs (`mcp__test-context7`) | <to confirm API signatures> |
|
|
202
|
-
| `<aws-doc-keyword>` | AWS knowledge base | <for design verification> |
|
|
203
|
-
| `/Volumes/.../app/<sibling-project>/<path>` | Cross-project file | <reference impl> |
|
|
204
|
-
|
|
205
|
-
If it's clear there are no resources, mark this section `_N/A_`.
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Related Tasks
|
|
210
|
-
|
|
211
|
-
<!--
|
|
212
|
-
You must specify a relation label: blocker | blocked-by | sibling | follow-up | duplicate | shares-codepath
|
|
213
|
-
-->
|
|
214
|
-
|
|
215
|
-
| Task | Relation | Note |
|
|
216
|
-
|------|----------|------|
|
|
217
|
-
| `DEV-XXXX` | `sibling` | same epic, same codebase, possible concurrent changes |
|
|
218
|
-
| `DEV-YYYY` | `blocker` | this task can start only after DEV-YYYY completes |
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Definition of Done (for this run)
|
|
223
|
-
|
|
224
|
-
<!--
|
|
225
|
-
Requested Outcome is the big picture. Here you write "the verifiable conditions this run's output must satisfy."
|
|
226
|
-
-->
|
|
227
|
-
|
|
228
|
-
This run's final report must satisfy all of the following:
|
|
229
|
-
|
|
230
|
-
- [ ] <explicit answer to decision item 1, or "undecidable + reason">
|
|
231
|
-
- [ ] <explicit answer to decision item 2>
|
|
232
|
-
- [ ] <list of remaining blocking questions>
|
|
233
|
-
- [ ] <recommended next phase + reasoning>
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## Questions for Workers
|
|
238
|
-
|
|
239
|
-
<!--
|
|
240
|
-
Specify priority as P0 (must answer) / P1 (answer if possible) / P2 (bonus).
|
|
241
|
-
-->
|
|
242
|
-
|
|
243
|
-
1. **[P0]** <key question 1>
|
|
244
|
-
2. **[P0]** <key question 2>
|
|
245
|
-
3. **[P1]** <secondary question>
|
|
246
|
-
4. **[P2]** <exploratory question>
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## Expected Outputs
|
|
251
|
-
|
|
252
|
-
| Category | Expected content |
|
|
253
|
-
|----------|-----------------|
|
|
254
|
-
| Root cause / plan options / blockers | <ranked list of options or candidates> |
|
|
255
|
-
| Missing information | <what additional discovery is required> |
|
|
256
|
-
| Risks | <ordered by severity> |
|
|
257
|
-
| Recommended next actions | <phase routing + concrete next step> |
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## Notes for Lead (synthesis emphasis)
|
|
262
|
-
|
|
263
|
-
<!--
|
|
264
|
-
Worker selection (which model to use) is decided automatically from recommendedWorkers in task-manifest.json, so don't write it here.
|
|
265
|
-
This section covers *synthesis emphasis* only.
|
|
266
|
-
-->
|
|
267
|
-
|
|
268
|
-
- **Synthesis priority**: <e.g. safety vs speed, public/private boundary accuracy, backward compat>
|
|
269
|
-
- **Where worker disagreement matters most**: <the point where consensus matters most>
|
|
270
|
-
- **Where reduced confidence is acceptable**: <exploratory areas — a hypothesis alone is enough>
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Brief Hygiene Checklist
|
|
275
|
-
|
|
276
|
-
Self-check after writing:
|
|
277
|
-
|
|
278
|
-
- [ ] Every external link has an inline excerpt embedded
|
|
279
|
-
- [ ] The Domain Glossary has every term a codebase-blind worker needs to know
|
|
280
|
-
- [ ] Out of Scope is written explicitly
|
|
281
|
-
- [ ] Only the relevant block of Task-Type Focus remains (the other block is deleted)
|
|
282
|
-
- [ ] If error-analysis, Reproduction Steps are filled in
|
|
283
|
-
- [ ] Definition of Done is a verifiable checklist
|
|
284
|
-
- [ ] The Configuration / Deployment section has content only when applicable, and is closed with `_N/A_` when irrelevant
|
|
285
|
-
- [ ] The ticket title is identifiable in Korean/English/original language
|
|
286
|
-
- [ ] Related Tasks have relation labels specified
|