okstra 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/kr/architecture.md +8 -8
- package/docs/kr/cli.md +2 -2
- package/docs/project-structure-overview.md +3 -3
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +323 -0
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +559 -0
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +87 -0
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +121 -0
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/docs/task-process/requirements-discovery.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -3
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/gemini-worker.md +1 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -3
- package/runtime/bin/lib/okstra/tmux-pane.sh +40 -0
- package/runtime/bin/okstra-codex-exec.sh +17 -21
- package/runtime/bin/okstra-gemini-exec.sh +12 -15
- package/runtime/bin/okstra-render-report-views.py +1 -1
- package/runtime/bin/okstra-trace-cleanup.sh +13 -1
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +15 -15
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +9 -9
- package/runtime/prompts/profiles/improvement-discovery.md +5 -5
- package/runtime/prompts/profiles/release-handoff.md +2 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -2
- package/runtime/python/okstra_ctl/clarification_items.py +11 -11
- package/runtime/python/okstra_ctl/render.py +1 -1
- package/runtime/python/okstra_ctl/render_final_report.py +1 -1
- package/runtime/python/okstra_ctl/report_views.py +26 -39
- package/runtime/python/okstra_ctl/run.py +3 -3
- package/runtime/python/okstra_ctl/wizard.py +90 -3
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/skills/okstra-brief/SKILL.md +1 -1
- package/runtime/skills/okstra-convergence/SKILL.md +8 -8
- package/runtime/skills/okstra-report-writer/SKILL.md +22 -22
- package/runtime/skills/okstra-run/SKILL.md +2 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/project-docs/task-index.template.md +1 -8
- package/runtime/templates/reports/final-report.template.md +194 -198
- package/runtime/templates/reports/i18n/en.json +16 -17
- package/runtime/templates/reports/i18n/ko.json +16 -17
- package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +1 -1
- package/runtime/templates/reports/schedule.template.md +3 -7
- package/runtime/templates/reports/user-response.template.md +1 -1
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/lib/fixtures.sh +2 -2
- package/runtime/validators/validate-implementation-plan-stages.py +9 -9
- package/runtime/validators/validate-report-views.py +10 -10
- package/runtime/validators/validate-run.py +36 -36
- package/runtime/validators/validate_improvement_report.py +8 -8
|
@@ -83,7 +83,7 @@ A mocked unit test cannot observe the SQL a query builder actually emits — `co
|
|
|
83
83
|
- **Requirement when fired.** The verifier MUST reproduce a real-DB execution: run the `db-test` tier (Tier 1 = plan `validation` db step; else Tier 2 = `project.json.qaCommands.db-test`) against a **local / replica** datastore (same engine + schema — never shared / staging / prod, consistent with the verifier forbidden-actions list) and record its exact command + exit code. A mock, an in-memory shim that does not parse real SQL, or static reasoning does NOT satisfy this.
|
|
84
84
|
- **No `db-test` command available → blocking, not a passive skip.** If neither tier declares a `db-test` command, the verifier records the blocking finding `db-test not configured — DB change unverified (mock-only)` and sets the verdict to `FAIL`; it MUST NOT emit only the passive `qa-command not configured` note and pass. Recommended fix: declare a `db-test` command in `project.json.qaCommands` or the plan's validation set.
|
|
85
85
|
- **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion `정적 분석상 …, 미검증(실행 안 함)` (never `검증됨`), records it as a blocking finding, and sets `FAIL`. Never downplay the real run as "too heavy / static proof suffices".
|
|
86
|
-
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `##
|
|
86
|
+
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push.
|
|
87
87
|
|
|
88
88
|
## All-verifier-failure policy
|
|
89
89
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
- uncertainty boundaries
|
|
26
26
|
- practical next diagnostic steps
|
|
27
27
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
28
|
-
- if any blocking uncertainty remains at the time of writing the final report, populate `##
|
|
28
|
+
- if any blocking uncertainty remains at the time of writing the final report, populate `## 1. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
|
|
29
29
|
- prefer plain Korean over abbreviations (e.g. write "초당 평균 요청 수" instead of "QPS", "재현 절차" instead of "repro")
|
|
30
30
|
- every clarification row carries a recommended answer + one-line rationale inside the `Expected form` cell; rows that lack a recommendation are rejected as half-formed.
|
|
31
31
|
- **Codebase-first ambiguity resolution (defect rule)**: any ambiguity about repro, file behavior, or symbol semantics that can be answered by `Read` / `Grep` / log inspection MUST be resolved that way and recorded with file:line (or log-line) evidence. Writing a clarification row for something the codebase or shipped logs already answer is a defect of this phase.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- if the cited implementation report is missing, lacks commits for delivered code changes, or the current checkout does not match the implementation report's commit list / diff summary, the run MUST end with status `blocked` and route back to `implementation` or `implementation-planning` rather than verifying an ambiguous target.
|
|
30
30
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
31
31
|
- **Source Implementation Report**: relative path of the originating `implementation` final-report file, the quoted commit list / diff summary used as the verification target, the worktree path inspected, and the base/head SHAs captured at run start. The lead injects this same target snapshot into every analyser prompt (`**Worktree:** / **Verification base ref:** / **Verification head SHA:** / **Verification diff stat:**`); a worker that cannot confirm its analysis ran against that exact head MUST record a `tool-failure` rather than verify an ambiguous target.
|
|
32
|
-
- **Verdict vocabulary**: Section
|
|
32
|
+
- **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`.
|
|
33
33
|
- **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
|
|
34
34
|
- **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
|
|
35
35
|
- **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
- **Two-tier command lookup (shared with `implementation`):** when this phase performs its own independent re-validation, the command source is exactly the same two tiers `implementation` verifiers use — Tier 1 is the originating task brief / approved plan's `validation` set, Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` under `qaCommands`. Auto-detecting tools from manifest files is forbidden; missing tiers are recorded as `qa-command not configured: <category>` and do NOT trigger a guess. The `cmd` deny-list (`--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, `npm install` without `ci`, etc.) is enforced identically. NOTE: runtime fail-fast validation (`okstra_ctl.qa_commands.validate_qa_commands`) only fires at `--task-type implementation` run-prep, so this phase MUST self-check each `qaCommands` entry against the deny-list before executing it — if a denied token is present, skip the command and record it as a `Read-only command log` line `qa-command rejected (denied token: <token>): <label>`.
|
|
38
38
|
- **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`.
|
|
39
39
|
- Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
|
|
40
|
-
- populate `##
|
|
40
|
+
- populate `## 1. Clarification Items` only when a blocker hinges on information only the user can supply (deployment intent, intended target environment, business-rule interpretation); use `Blocks=next-phase` for items that gate continuing to release-handoff
|
|
41
41
|
- Self-review pass before finalising the report (`Claude lead` runs this; do not delegate to a generic subagent):
|
|
42
42
|
1. **Verdict precision** — section 2 includes `Verdict Token` with one of the three allowed verdict tokens; `conditional-accept` lists every condition as an actionable item.
|
|
43
43
|
2. **Blocker traceability** — every blocker cites a concrete artifact (file:line, log excerpt, test exit code, MCP SELECT). Blockers without evidence are demoted to residual risk or removed.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- inspect the current state of every file the task names (or the closest matching files if names are stale) — record current responsibilities, public interfaces, and known coupling points
|
|
18
18
|
- skim recent commits touching those files (`git log -- <path>`) to surface in-flight work or contested areas
|
|
19
19
|
- **codebase-first ambiguity resolution**: any ambiguity that can be answered by `Read` / `Grep` MUST be resolved that way and recorded with file:line evidence. Only ambiguities that genuinely require a human decision are escalated as `Clarification Items` rows. Writing a clarification row for something the code already answers is a defect of this phase.
|
|
20
|
-
- flag any requirement that is ambiguous, contradictory, or missing success criteria — register each one as a row in the report's `##
|
|
20
|
+
- flag any requirement that is ambiguous, contradictory, or missing success criteria — register each one as a row in the report's `## 1. Clarification Items` table with `Blocks=approval` instead of guessing
|
|
21
21
|
- read `<PROJECT_ROOT>/.okstra/glossary.md` and `<PROJECT_ROOT>/.okstra/decisions/` titles if present. Absent okstra memory files are the normal state — do not error. Treat the brief's `terminology:*` resolutions from `requirements-discovery` (if any) as authoritative; if missing, resolve any remaining fuzzy term as a `Blocks=approval` clarification row.
|
|
22
22
|
- Primary focus areas:
|
|
23
23
|
- requirement gaps
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
- The YAML frontmatter `approved: true|false` field is the only authorised approval gate. report-writer always emits `approved: false`. The user clears it either by (a) editing the frontmatter line to `approved: true` directly, or (b) invoking the next phase with `--approve` so the CLI flips the frontmatter on the user's behalf. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
|
|
40
40
|
- Cross-verification mode:
|
|
41
41
|
- Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / option) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode".
|
|
42
|
-
- §
|
|
42
|
+
- §5.5.9 plan-body verification runs with an **adversarial posture** (`skills/okstra-convergence/SKILL.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is unchanged — a *majority* `DISAGREE` (`majority-disagree`) is still required to block approval; a single dissent does not.
|
|
43
43
|
- **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass runs after convergence to surface missed findings; its gaps are merged only after a 1-round adversarial reverify. See `skills/okstra-convergence/SKILL.md` "Coverage critic pass".
|
|
44
44
|
- Non-goals:
|
|
45
45
|
- code-level micro-optimization unless it changes the implementation approach
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
- The final report MUST include section headings containing each of the following exact strings: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stage Exit Contract`, `Stage Validation`, `Dependency`, `Validation Checklist`, `Rollback`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.)
|
|
56
56
|
- Korean translations are allowed in parentheses (e.g. `### Recommended Option (권장 옵션)`), but the English keyword must be present verbatim in the heading line.
|
|
57
57
|
- The shape and ordering follow `final-report-template.md` section 4.5 (`Implementation Plan Deliverables`). Do NOT translate the heading keywords — `validators/validate-run.py` does substring matching on the raw report text and 7-of-8 missing strings is a real, repeatedly observed failure mode (root cause: writer translated the headings to Korean).
|
|
58
|
-
- Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — the exact `##
|
|
58
|
+
- Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤6), and the `depends-on` DAG are all enforced here, not deferred to the `implementation` entry gate.
|
|
59
59
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
60
60
|
- at least two implementation options. **Each option must include**:
|
|
61
61
|
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`.
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
- trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
|
|
65
65
|
- recommended option with rationale tied to the design principles above
|
|
66
66
|
- **Stage Map (mandatory — always emitted, even when N=1):** a table of all stages with `stage | title | depends-on | step-count | exit-contract-summary`. `depends-on` is `(none)` or a comma-separated stage number list. Stages with `depends-on (none)` can be implemented in parallel by two simultaneous `implementation` runs.
|
|
67
|
-
- **Per-stage subsections** (`##
|
|
67
|
+
- **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
|
|
68
68
|
- `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
|
|
69
69
|
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. **Effective row count ≤ 6** (excluding header / divider / blank). Each step is one action completable in 2–5 minutes; for code steps include actual code or diff sketch; prefer TDD ordering (failing test → implementation → green → commit).
|
|
70
70
|
- `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
- validation checklist (pre / mid / post) — each item is an exact command or observable outcome
|
|
77
77
|
- rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
|
|
78
78
|
- 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).
|
|
79
|
-
- **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§
|
|
80
|
-
- every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `##
|
|
81
|
-
- **§
|
|
79
|
+
- **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
|
|
80
|
+
- every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `4.5.x` — the unified table is the single home)
|
|
81
|
+
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (sections 4.5.1 – 4.5.7) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `skills/okstra-convergence/SKILL.md` "Plan-body verification mode". The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture").
|
|
82
82
|
- **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
|
|
83
83
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
84
84
|
2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
1. **Spec coverage** — for every requirement in the task brief, point to the option(s) and step(s) that satisfy it. List gaps explicitly.
|
|
96
96
|
2. **Placeholder scan** — search the report for the patterns in the No-placeholder rule above and fix inline.
|
|
97
97
|
3. **Internal consistency** — option file lists, trade-off matrix, and recommended step list must agree on file paths, names, and signatures. A symbol called `clearLayers()` in the matrix and `clearFullLayers()` in the steps is a bug.
|
|
98
|
-
4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `##
|
|
98
|
+
4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
|
|
99
99
|
5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan.
|
|
100
|
-
6. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** Inspect the `###
|
|
100
|
+
6. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** Inspect the `### 5.5.9 Plan Body Verification` verdict table. For every plan-item row classified as `majority-disagree → C-<N>`, the corresponding `C-<N>` row MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. Do NOT create a parallel `### 5.5.x Open Questions` block — the unified table is the single home. Conversely, the `Classification` column's `C-<N>` reference and the `## 1. Clarification Items` `ID` column MUST match 1:1; an orphan on either side is a contract violation. For `partial-consensus` and `worker-unique` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
|
|
101
101
|
7. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 6. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- this phase REQUIRES a codebase-scan brief whose frontmatter contains `scope: codebase`. A brief without that marker is rejected before worker dispatch.
|
|
14
14
|
- the brief's `priority-lenses` MUST be a non-empty subset (size 1..4) of the lens whitelist defined in `scripts/okstra_ctl/improvement_lenses.py`. Lenses outside the whitelist are rejected.
|
|
15
15
|
- the brief's `scan-scope` defines the only paths workers may read for candidate evidence. `out-of-scope` paths MUST be ignored even when the codebase is otherwise reachable.
|
|
16
|
-
- the brief's `candidate-cap` (default 8 if absent, absolute cap 12) bounds the number of rows in `##
|
|
16
|
+
- the brief's `candidate-cap` (default 8 if absent, absolute cap 12) bounds the number of rows in `## 5.9 Improvement Candidates`.
|
|
17
17
|
- Apply the shared reporter-confirmation precondition as written. For this phase any unresolved `intent-check:` / `conversion-block:` row uses `Blocks=next-phase`.
|
|
18
18
|
- Primary focus areas:
|
|
19
19
|
- candidate discovery within the lens whitelist
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
- Decision-tree walk (bounded):
|
|
30
30
|
- When candidates branch on a structural question (e.g. "is module X meant to own this responsibility?"), resolve via `Read` / `Grep` first. Only escalate to the user inside the Phase 1.5 budget.
|
|
31
31
|
- Expected output emphasis:
|
|
32
|
-
- the `##
|
|
33
|
-
- `##
|
|
34
|
-
- `##
|
|
32
|
+
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 10-column schema from `validators/validate-improvement-report.py` (Cand ID `I-NNN`, Lens from whitelist, Title, Scope ⊆ scan-scope, Severity, Effort, Consensus, Source workers `<worker>:<id>` from {claude, codex, gemini}, Recommended next-phase ∈ {requirements-discovery, implementation-planning, error-analysis}, Evidence as path:line list)
|
|
33
|
+
- `## 7. Final Verdict` Verdict Token ∈ {`candidates-ready`, `no-candidates`, `blocked`}; Direction `routing`; Next Step "사용자에게 후보 K개 선택 의뢰 (## 5.9 표 참조)"
|
|
34
|
+
- `## 3. Recommended Next Steps` first entry summarises per-candidate routing and proposes new task-key names of the form `<task-group>/imp-<Cand-ID>`
|
|
35
35
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
36
|
-
- if scan-scope or priority-lenses cannot be made concrete during Phase 1.5, end the run with Verdict Token `blocked`, populate `##
|
|
36
|
+
- if scan-scope or priority-lenses cannot be made concrete during Phase 1.5, end the run with Verdict Token `blocked`, populate `## 1. Clarification Items` with `Blocks=next-phase` rows, and do not run worker dispatch
|
|
37
37
|
- every clarification row carries a recommended answer + one-line rationale inside the `Expected form` cell
|
|
38
38
|
- Non-goals:
|
|
39
39
|
- concrete implementation plans, cost estimates, or code edits for any candidate
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
- Lead-only contract (replaces the shared team contract for this phase):
|
|
7
7
|
- The Claude lead is the sole agent for this run. No `Agent(...)` worker dispatch, no `TeamCreate`, no parallel sub-agents, no convergence loop.
|
|
8
8
|
- The lead drafts the PR title and PR body **inline** by reading the run brief, the cited final-verification report, `git log --oneline <base>..HEAD`, and `git diff <base>..HEAD --stat`. No drafter worker is dispatched.
|
|
9
|
-
- The lead authors the final-report file directly (no `Report writer worker` dispatch). The report still conforms to the standard `okstra-final-report.template.md` structure, including the `##
|
|
9
|
+
- The lead authors the final-report file directly (no `Report writer worker` dispatch). The report still conforms to the standard `okstra-final-report.template.md` structure, including the `## 5.6 Release Handoff Deliverables` section.
|
|
10
10
|
- The shared anti-escalation rule from the common contract still applies: do not start any other lifecycle phase from inside this run.
|
|
11
11
|
- The shared "authority & permissions assumption" rule from the common contract still applies: assume the user holds every permission needed; do not block on hypothetical approvals.
|
|
12
12
|
- The shared "MCP read-only" rule still applies if the brief lists MCP servers, though most release-handoff runs do not use MCP.
|
|
13
13
|
- Pre-handoff entry gate (mandatory — refuse to start if any item fails):
|
|
14
|
-
- the task brief MUST cite the originating `final-verification` final-report path under `## Source Verification Report`. The lead opens that file and confirms section `##
|
|
14
|
+
- the task brief MUST cite the originating `final-verification` final-report path under `## Source Verification Report`. The lead opens that file and confirms section `## 7. Final Verdict` contains a `Verdict Token` field whose value is exactly `accepted`.
|
|
15
15
|
- if the verdict is `conditional-accept`, `blocked`, or any other token (including ambiguous phrasing like "looks good"), the run MUST end immediately with status `blocked` and a routing recommendation back to `error-analysis` or `implementation-planning`. Do NOT prompt the user; Do NOT run any git command.
|
|
16
16
|
- the lead MUST capture `git status --short` and confirm the working tree is clean. Dirty state aborts the run; release-handoff packages the commits produced by `implementation`, it does not stage or commit changes.
|
|
17
17
|
- the lead MUST capture `git rev-parse --abbrev-ref HEAD` and record it as the **feature branch**. If the current branch is itself `main`, `master`, `prod`, `preprod`, `staging`, or `dev`, the run MUST end immediately — release-handoff never operates on a base branch.
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
- When the brief's `Desired Outcome`, classification, or routing target depends on a chain of decisions, walk that chain one branch at a time. Each branch is one `Clarification Items` row, not a free-form interview.
|
|
40
40
|
- For every clarification row, put the single best answer and one-line rationale in `Expected form` as `Recommended: ...`. Put other options and one-sentence consequences in the same cell as `Alternatives: ...`.
|
|
41
41
|
- **Codebase-first rule**: if a branch can be resolved by `Read` / `Grep` / file inspection, resolve it that way and record `Evidence checked: <path:line>` in the `Statement` cell. Do NOT escalate to the user.
|
|
42
|
-
- Budget: the unified `##
|
|
42
|
+
- Budget: the unified `## 1. Clarification Items` table caps at the smaller of (a) one row per unresolved decision branch, (b) 8 rows total. Beyond the cap, fold remaining ambiguity into the routing recommendation's risk notes.
|
|
43
43
|
- Expected output emphasis:
|
|
44
44
|
- evidence-backed routing decision
|
|
45
45
|
- uncertainty boundaries and missing inputs
|
|
46
46
|
- next recommended phase and safe resume guidance
|
|
47
47
|
- canonical-term resolution for every `terminology:*` brief item, written as a one-line `<term> = <definition>` line in a new `Domain Alignment` subsection of the final report; alongside each, propose whether `<PROJECT_ROOT>/.okstra/glossary.md` should be updated (proposal only — actual writes happen via `okstra-brief` Step 4.5 on a subsequent run)
|
|
48
48
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
49
|
-
- if any blocking input is missing at the time of writing the final report, populate `##
|
|
49
|
+
- if any blocking input is missing at the time of writing the final report, populate `## 1. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
|
|
50
50
|
- prefer concrete questions whose answers map directly to a routing decision (`bugfix` vs `feature`, `error-analysis` vs `implementation-planning`, etc.). State each option in plain language with one sentence describing what choosing it would mean for the next phase.
|
|
51
51
|
- every clarification row carries a recommended answer + one-line rationale inside the `Expected form` cell; rows that lack a recommendation are rejected as half-formed.
|
|
52
52
|
- **Codebase-first ambiguity resolution (defect rule)**: any ambiguity that can be answered by `Read` / `Grep` / file inspection MUST be resolved that way and recorded with file:line evidence. Writing a clarification row for something the codebase already answers is a defect of this phase.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Parse the ``##
|
|
1
|
+
"""Parse the ``## 1. Clarification Items`` table from a final-report markdown.
|
|
2
2
|
|
|
3
|
-
The unified §
|
|
3
|
+
The unified §1 table (introduced when §4.5.9 / §5.1 / §5.2 collapsed into a
|
|
4
4
|
single section) is the canonical home for every clarification an
|
|
5
5
|
implementation-planning run owes the user — decisions, file attachments,
|
|
6
6
|
single data points. Each row carries a ``Blocks`` column whose value picks
|
|
@@ -12,7 +12,7 @@ This module exposes one read function for that gate so both
|
|
|
12
12
|
``_validate_approved_plan`` (pre-implementation run-prep) and any later
|
|
13
13
|
validator can share the same parsing logic.
|
|
14
14
|
|
|
15
|
-
Legacy compatibility: reports written before the §
|
|
15
|
+
Legacy compatibility: reports written before the §1 unification used
|
|
16
16
|
``4.5.9 Open Questions`` + ``5.1 Additional Materials`` + ``5.2 Questions
|
|
17
17
|
for the User`` and lacked a ``Blocks`` column. Those reports cannot be
|
|
18
18
|
gate-checked by Blocks; the parser returns ``None`` to signal "schema
|
|
@@ -26,13 +26,13 @@ from pathlib import Path
|
|
|
26
26
|
from typing import Optional
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
SECTION_HEADING_PATTERN = re.compile(r"^##\s+
|
|
30
|
-
NEXT_TOP_LEVEL_HEADING_PATTERN = re.compile(r"^##\s+(?!
|
|
29
|
+
SECTION_HEADING_PATTERN = re.compile(r"^##\s+1\.\s+Clarification Items\s*$", re.MULTILINE)
|
|
30
|
+
NEXT_TOP_LEVEL_HEADING_PATTERN = re.compile(r"^##\s+(?!1\.)", re.MULTILINE)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@dataclass(frozen=True)
|
|
34
34
|
class ClarificationItem:
|
|
35
|
-
"""One row of the §
|
|
35
|
+
"""One row of the §1 table.
|
|
36
36
|
|
|
37
37
|
``raw_*`` fields preserve the exact cell text (after backtick stripping)
|
|
38
38
|
for diagnostics; canonical lowercased versions live in ``blocks`` /
|
|
@@ -77,9 +77,9 @@ def _is_separator_row(line: str) -> bool:
|
|
|
77
77
|
return True
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def
|
|
81
|
-
"""Return the substring spanning the §
|
|
82
|
-
next ``##`` heading), or None if §
|
|
80
|
+
def _section_1_slice(report_text: str) -> Optional[str]:
|
|
81
|
+
"""Return the substring spanning the §1 section (heading exclusive of the
|
|
82
|
+
next ``##`` heading), or None if §1 is absent."""
|
|
83
83
|
start_match = SECTION_HEADING_PATTERN.search(report_text)
|
|
84
84
|
if not start_match:
|
|
85
85
|
return None
|
|
@@ -89,7 +89,7 @@ def _section_5_slice(report_text: str) -> Optional[str]:
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def parse_clarification_items(report_text: str) -> Optional[list[ClarificationItem]]:
|
|
92
|
-
"""Return the list of §
|
|
92
|
+
"""Return the list of §1 rows. ``None`` means "no unified §1 table
|
|
93
93
|
detected" (legacy report or missing section) — caller must NOT treat
|
|
94
94
|
that as "table is empty".
|
|
95
95
|
|
|
@@ -97,7 +97,7 @@ def parse_clarification_items(report_text: str) -> Optional[list[ClarificationIt
|
|
|
97
97
|
just the ``- 추가 정보 요청 없음.`` placeholder); that IS a confident
|
|
98
98
|
"no approval-blocking items".
|
|
99
99
|
"""
|
|
100
|
-
section =
|
|
100
|
+
section = _section_1_slice(report_text)
|
|
101
101
|
if section is None:
|
|
102
102
|
return None
|
|
103
103
|
|
|
@@ -75,7 +75,7 @@ def _strip_phase_blocks(text: str, current_phase: str) -> str:
|
|
|
75
75
|
entirely. When *current_phase* is empty or not one of the four
|
|
76
76
|
block-targetable phases (e.g. `requirements-discovery`,
|
|
77
77
|
`error-analysis`), every block is dropped — correct because none of
|
|
78
|
-
the `##
|
|
78
|
+
the `## 5.5` / `5.6` / `5.7` / `5.8` deliverable sections apply
|
|
79
79
|
there.
|
|
80
80
|
|
|
81
81
|
Observed (fontsninja-classifier-v2 RD run): the raw final-report
|
|
@@ -9,7 +9,7 @@ the canonical user-facing markdown.
|
|
|
9
9
|
|
|
10
10
|
Why this exists: prior to v0.32, report-writer-worker wrote the markdown
|
|
11
11
|
directly. Free-form authoring led to silent contract violations — missing
|
|
12
|
-
columns in the Execution Status table, omitted §
|
|
12
|
+
columns in the Execution Status table, omitted §4 phase-continuation
|
|
13
13
|
rows, invented ``## Index`` sections. Routing everything through one
|
|
14
14
|
template + schema cuts those failure modes to zero.
|
|
15
15
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Single product, single source of truth:
|
|
4
4
|
|
|
5
5
|
* ``render_html(src_md, *, run_meta)`` — deterministic self-contained
|
|
6
|
-
HTML renderer for human readers. Sections §
|
|
7
|
-
rows (those reachable from §
|
|
8
|
-
controls. §
|
|
6
|
+
HTML renderer for human readers. Sections §1/§3/§4 user-actionable
|
|
7
|
+
rows (those reachable from §1 ``C-*`` IDs) get embedded ``<form>``
|
|
8
|
+
controls. §5.6 / §5.7 / §5.8 deliverable sub-sections are explicitly
|
|
9
9
|
excluded from form attachment — they are read-only deliverables.
|
|
10
10
|
|
|
11
11
|
User responses are NEVER merged back into the original report. The HTML
|
|
@@ -57,7 +57,7 @@ def _strip_leading_frontmatter(text: str) -> str:
|
|
|
57
57
|
|
|
58
58
|
from .clarification_items import (
|
|
59
59
|
_is_separator_row,
|
|
60
|
-
|
|
60
|
+
_section_1_slice,
|
|
61
61
|
_split_pipe_row,
|
|
62
62
|
parse_clarification_items,
|
|
63
63
|
)
|
|
@@ -79,7 +79,7 @@ _LINK_PATTERN = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
|
79
79
|
|
|
80
80
|
# Sections whose Response-ID-bearing rows must NOT get form attachment
|
|
81
81
|
# (read-only deliverables — see plan §1.4).
|
|
82
|
-
_NO_FORM_SECTION_PREFIXES = ("##
|
|
82
|
+
_NO_FORM_SECTION_PREFIXES = ("## 5.6", "### 5.6", "## 5.7", "### 5.7", "## 5.8", "### 5.8")
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
@dataclass(frozen=True)
|
|
@@ -161,7 +161,7 @@ def _markdown_to_html(
|
|
|
161
161
|
headings: list[tuple[int, str, str]] = []
|
|
162
162
|
i = 0
|
|
163
163
|
n = len(lines)
|
|
164
|
-
current_section_path: list[str] = [] # ['##
|
|
164
|
+
current_section_path: list[str] = [] # ['## 1. ...', '### 1.1 ...'] etc.
|
|
165
165
|
|
|
166
166
|
while i < n:
|
|
167
167
|
line = lines[i]
|
|
@@ -394,7 +394,7 @@ class _GroupedSpec:
|
|
|
394
394
|
value`` metadata cell led by ``headline_col``; the long columns
|
|
395
395
|
(``wide_cols``) each keep their own min-width column.
|
|
396
396
|
|
|
397
|
-
``kind == "clarification"`` additionally re-attaches the §
|
|
397
|
+
``kind == "clarification"`` additionally re-attaches the §1 form
|
|
398
398
|
widget to the ``user_input_col`` cell and the ``data-*`` row attrs."""
|
|
399
399
|
headline_col: int
|
|
400
400
|
group_cols: tuple[int, ...]
|
|
@@ -407,21 +407,12 @@ class _GroupedSpec:
|
|
|
407
407
|
user_input_col: int = -1
|
|
408
408
|
|
|
409
409
|
|
|
410
|
-
_FOLLOWUP_WIDE_PREFIXES: tuple[str, ...] = ("title", "scope", "reason")
|
|
411
|
-
|
|
412
|
-
|
|
413
410
|
def _grouped_table_spec(
|
|
414
411
|
header_cells: list[str], section_path: list[str]
|
|
415
412
|
) -> Optional[_GroupedSpec]:
|
|
416
|
-
"""
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
table (which keeps the default per-cell ``td-narrow`` rendering).
|
|
420
|
-
|
|
421
|
-
Each table is identified by stable header tokens (the i18n token/cost
|
|
422
|
-
columns are never used as anchors). ``wide_cols`` lists the long-prose
|
|
423
|
-
columns that must keep a guaranteed min-width; everything else short
|
|
424
|
-
collapses into the leading metadata cell."""
|
|
413
|
+
"""Only §1 Clarification Items is grouped in the HTML view (it keeps the
|
|
414
|
+
interactive form and stays flat in the .md). All other narrative tables are
|
|
415
|
+
already rendered compactly by the template, so no grouping is applied here."""
|
|
425
416
|
norm = [h.strip() for h in header_cells]
|
|
426
417
|
|
|
427
418
|
def _spec(headline: int, wide: tuple[int, ...], **kw) -> _GroupedSpec:
|
|
@@ -429,12 +420,8 @@ def _grouped_table_spec(
|
|
|
429
420
|
group = tuple(c for c in range(len(norm)) if c != headline and c not in wide_set)
|
|
430
421
|
return _GroupedSpec(headline_col=headline, group_cols=group, wide_cols=wide, **kw)
|
|
431
422
|
|
|
432
|
-
#
|
|
433
|
-
|
|
434
|
-
return _spec(0, (len(norm) - 1,), kind="plain")
|
|
435
|
-
|
|
436
|
-
# §5 Clarification Items — keep the interactive form, but collapse the
|
|
437
|
-
# short ID/Kind/Status/… columns and widen Statement + User input.
|
|
423
|
+
# §1 Clarification Items — keep the interactive form, and widen the three
|
|
424
|
+
# long-prose columns (Expected form is prose too, not a code column).
|
|
438
425
|
if (
|
|
439
426
|
any("Clarification Items" in h for h in section_path)
|
|
440
427
|
and not _section_forbids_form(section_path)
|
|
@@ -444,9 +431,15 @@ def _grouped_table_spec(
|
|
|
444
431
|
):
|
|
445
432
|
statement_col = next(i for i, h in enumerate(norm) if h.startswith("Statement"))
|
|
446
433
|
user_input_col = norm.index("User input")
|
|
434
|
+
expected_col = next(
|
|
435
|
+
(i for i, h in enumerate(norm) if h.startswith("Expected form")), -1
|
|
436
|
+
)
|
|
437
|
+
wide_cols = tuple(
|
|
438
|
+
c for c in (expected_col, statement_col, user_input_col) if c >= 0
|
|
439
|
+
)
|
|
447
440
|
return _spec(
|
|
448
441
|
norm.index("ID"),
|
|
449
|
-
|
|
442
|
+
wide_cols,
|
|
450
443
|
kind="clarification",
|
|
451
444
|
id_col=norm.index("ID"),
|
|
452
445
|
kind_col=norm.index("Kind") if "Kind" in norm else -1,
|
|
@@ -455,16 +448,6 @@ def _grouped_table_spec(
|
|
|
455
448
|
user_input_col=user_input_col,
|
|
456
449
|
)
|
|
457
450
|
|
|
458
|
-
# §7 Follow-up Tasks — widen Title / Scope / Reason, collapse the rest.
|
|
459
|
-
if any("Follow-up Tasks" in h for h in section_path) and "ID" in norm:
|
|
460
|
-
wide = tuple(
|
|
461
|
-
i
|
|
462
|
-
for i, h in enumerate(norm)
|
|
463
|
-
if any(h.lower().startswith(p) for p in _FOLLOWUP_WIDE_PREFIXES)
|
|
464
|
-
)
|
|
465
|
-
if wide:
|
|
466
|
-
return _spec(norm.index("ID"), wide, kind="plain")
|
|
467
|
-
|
|
468
451
|
return None
|
|
469
452
|
|
|
470
453
|
|
|
@@ -491,7 +474,7 @@ def _grouped_meta_cell(
|
|
|
491
474
|
def _grouped_clarification_row(
|
|
492
475
|
row: list[str], spec: _GroupedSpec
|
|
493
476
|
) -> tuple[str, str]:
|
|
494
|
-
"""Return ``(tr_attrs, wide_cells_html)`` for one §
|
|
477
|
+
"""Return ``(tr_attrs, wide_cells_html)`` for one §1 row, re-attaching
|
|
495
478
|
the form widget + ``data-*`` attrs to ``C-\\d+`` rows exactly as the
|
|
496
479
|
non-grouped path does."""
|
|
497
480
|
rid = row[spec.id_col] if 0 <= spec.id_col < len(row) else ""
|
|
@@ -768,6 +751,10 @@ def _inline(text: str) -> str:
|
|
|
768
751
|
out = _LINK_PATTERN.sub(
|
|
769
752
|
lambda m: f'<a href="{m.group(2)}">{m.group(1)}</a>', out
|
|
770
753
|
)
|
|
754
|
+
# Preserve explicit <br> line breaks used inside compact meta cells (the
|
|
755
|
+
# markdown source intentionally stacks short fields with <br>). html.escape
|
|
756
|
+
# above turned them into <br>; restore the tag.
|
|
757
|
+
out = out.replace("<br>", "<br>").replace("<br/>", "<br>").replace("<br />", "<br>")
|
|
771
758
|
return out
|
|
772
759
|
|
|
773
760
|
|
|
@@ -835,7 +822,7 @@ def serialize_user_response(
|
|
|
835
822
|
# --------------------------------------------------------------------------- #
|
|
836
823
|
|
|
837
824
|
def report_has_clarification_items(src_md: str) -> bool:
|
|
838
|
-
"""True when the final-report MD has at least one §
|
|
825
|
+
"""True when the final-report MD has at least one §1``C-*``
|
|
839
826
|
clarification row. This is the single predicate that gates HTML-view
|
|
840
827
|
generation: the self-contained html's only value over the markdown is
|
|
841
828
|
the embedded ``<form>`` widgets for those rows, so a clarification-free
|
|
@@ -857,7 +844,7 @@ def render_html_view(
|
|
|
857
844
|
) -> Path | None:
|
|
858
845
|
"""Write ``<stem>.html`` next to ``src_md_path`` and return its path,
|
|
859
846
|
or return ``None`` when generation is skipped because the report has
|
|
860
|
-
no §
|
|
847
|
+
no §1 clarification rows (see ``report_has_clarification_items``).
|
|
861
848
|
Idempotent — overwrites an existing html sibling, and removes a stale
|
|
862
849
|
one when a previously-clarification-bearing report no longer has rows."""
|
|
863
850
|
src_text = src_md_path.read_text(encoding="utf-8")
|
|
@@ -175,14 +175,14 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
175
175
|
f"approved plan is not yet approved (frontmatter `approved: {m.group(1)}`): {path}\n"
|
|
176
176
|
" open the report and change the frontmatter line to `approved: true`, "
|
|
177
177
|
"or re-run okstra with `--approve` to flip it from the CLI.\n"
|
|
178
|
-
" resolve any `Blocks=approval` rows in `##
|
|
178
|
+
" resolve any `Blocks=approval` rows in `## 1. Clarification Items` first."
|
|
179
179
|
)
|
|
180
|
-
# frontmatter approved == true 상태. §
|
|
180
|
+
# frontmatter approved == true 상태. §1 Clarification Items 의
|
|
181
181
|
# Blocks=approval 행이 아직 open/answered 면 승인을 무효화한다.
|
|
182
182
|
blockers = unresolved_approval_blockers(body)
|
|
183
183
|
if blockers:
|
|
184
184
|
lines = [
|
|
185
|
-
f"approved plan frontmatter has `approved: true` but §
|
|
185
|
+
f"approved plan frontmatter has `approved: true` but §1 has {len(blockers)} "
|
|
186
186
|
f"unresolved `Blocks=approval` row(s); resolve them or mark them obsolete first:",
|
|
187
187
|
]
|
|
188
188
|
for b in blockers:
|
|
@@ -205,6 +205,28 @@ S_CONFIRM = "confirm"
|
|
|
205
205
|
S_EDIT_TARGET = "edit_target"
|
|
206
206
|
S_DONE = "done"
|
|
207
207
|
|
|
208
|
+
# ---- 멀티탭 배치 프롬프트 그룹 (방출 계층 전용) ----
|
|
209
|
+
# 그룹 id 는 S_* 가 아니므로 prompts JSON SOT / step-id 동기화 검사 대상이 아니다.
|
|
210
|
+
GROUP_MODELS = "models"
|
|
211
|
+
GROUP_OPTIONS = "options"
|
|
212
|
+
GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
|
|
213
|
+
|
|
214
|
+
# 멤버는 모두 서로 의존이 없는 단일선택 픽 step 이어야 한다.
|
|
215
|
+
# *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
|
|
216
|
+
PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
|
|
217
|
+
GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
|
|
218
|
+
S_CODEX_MODEL, S_GEMINI_MODEL, S_REPORT_WRITER_MODEL),
|
|
219
|
+
GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
|
|
220
|
+
S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
|
|
221
|
+
}
|
|
222
|
+
GROUP_LABELS: dict[str, str] = {
|
|
223
|
+
GROUP_MODELS: "모델 선택 (탭별로 선택)",
|
|
224
|
+
GROUP_OPTIONS: "추가 옵션 (탭별로 선택)",
|
|
225
|
+
}
|
|
226
|
+
_STEP_TO_GROUP: dict[str, str] = {
|
|
227
|
+
sid: gid for gid, ids in PROMPT_GROUPS.items() for sid in ids
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
|
|
209
231
|
# ---- Data types ----------------------------------------------------------
|
|
210
232
|
|
|
@@ -305,9 +327,11 @@ class Prompt:
|
|
|
305
327
|
help: str = ""
|
|
306
328
|
echo_template: str = "" # e.g. "task-group: {value}"
|
|
307
329
|
multi: bool = False # only meaningful when kind == "pick"
|
|
330
|
+
# only meaningful when kind == "pick_group": one entry per AskUserQuestion tab
|
|
331
|
+
questions: list["Prompt"] = field(default_factory=list)
|
|
308
332
|
|
|
309
333
|
def to_json(self) -> dict[str, Any]:
|
|
310
|
-
|
|
334
|
+
out = {
|
|
311
335
|
"step": self.step,
|
|
312
336
|
"kind": self.kind,
|
|
313
337
|
"label": self.label,
|
|
@@ -316,6 +340,14 @@ class Prompt:
|
|
|
316
340
|
"echoTemplate": self.echo_template,
|
|
317
341
|
"multi": self.multi,
|
|
318
342
|
}
|
|
343
|
+
if self.kind == "pick_group":
|
|
344
|
+
out["questions"] = [
|
|
345
|
+
{"step": q.step, "label": q.label,
|
|
346
|
+
"options": [asdict(o) for o in q.options],
|
|
347
|
+
"multi": q.multi}
|
|
348
|
+
for q in self.questions
|
|
349
|
+
]
|
|
350
|
+
return out
|
|
319
351
|
|
|
320
352
|
|
|
321
353
|
class WizardError(Exception):
|
|
@@ -373,12 +405,12 @@ def _validate_approved_plan(path_str: str, project_root: Path) -> Path:
|
|
|
373
405
|
" edit the report and change the line to `approved: true`, or re-run "
|
|
374
406
|
"okstra with `--approve` to flip it from the CLI."
|
|
375
407
|
)
|
|
376
|
-
# frontmatter approved == true 라도 §
|
|
408
|
+
# frontmatter approved == true 라도 §1 의 Blocks=approval 행이 미해결이면
|
|
377
409
|
# 승인이 무효 — prepare_task_bundle 의 _validate_approved_plan 과 동일 규약.
|
|
378
410
|
blockers = unresolved_approval_blockers(body)
|
|
379
411
|
if blockers:
|
|
380
412
|
lines = [
|
|
381
|
-
f"approved plan frontmatter has `approved: true` but §
|
|
413
|
+
f"approved plan frontmatter has `approved: true` but §1 has {len(blockers)} "
|
|
382
414
|
f"unresolved `Blocks=approval` row(s); resolve them or mark them obsolete first:",
|
|
383
415
|
]
|
|
384
416
|
for b in blockers:
|
|
@@ -2218,6 +2250,30 @@ def init_state(
|
|
|
2218
2250
|
)
|
|
2219
2251
|
|
|
2220
2252
|
|
|
2253
|
+
def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
|
|
2254
|
+
"""그룹의 적용가능·미답변 픽 멤버를 최대 GROUP_MAX_TABS 개 모은다.
|
|
2255
|
+
|
|
2256
|
+
멤버가 1개뿐이면 멀티탭 UI가 불필요하므로 그 멤버의 평범한 픽을 반환한다.
|
|
2257
|
+
호출부(next_prompt)는 적용 가능한 멤버가 최소 1개일 때만 진입하므로 빈 그룹은
|
|
2258
|
+
도달 불가다.
|
|
2259
|
+
"""
|
|
2260
|
+
members: list[Prompt] = []
|
|
2261
|
+
for sid in PROMPT_GROUPS[group_id]:
|
|
2262
|
+
if sid in state.answered:
|
|
2263
|
+
continue
|
|
2264
|
+
step = STEP_BY_ID[sid]
|
|
2265
|
+
if not step.applies(state):
|
|
2266
|
+
continue
|
|
2267
|
+
members.append(step.build(state))
|
|
2268
|
+
if len(members) >= GROUP_MAX_TABS:
|
|
2269
|
+
break
|
|
2270
|
+
assert members, f"group {group_id!r} reached with no applicable members"
|
|
2271
|
+
if len(members) == 1:
|
|
2272
|
+
return members[0]
|
|
2273
|
+
return Prompt(step=group_id, kind="pick_group",
|
|
2274
|
+
label=GROUP_LABELS[group_id], questions=members)
|
|
2275
|
+
|
|
2276
|
+
|
|
2221
2277
|
def next_prompt(state: WizardState) -> Prompt:
|
|
2222
2278
|
if state.confirmed:
|
|
2223
2279
|
return Prompt(step=S_DONE, kind="done")
|
|
@@ -2225,10 +2281,39 @@ def next_prompt(state: WizardState) -> Prompt:
|
|
|
2225
2281
|
if step.id in state.answered:
|
|
2226
2282
|
continue
|
|
2227
2283
|
if step.applies(state):
|
|
2284
|
+
group_id = _STEP_TO_GROUP.get(step.id)
|
|
2285
|
+
if group_id is not None:
|
|
2286
|
+
return _build_group_prompt(state, group_id)
|
|
2228
2287
|
return step.build(state)
|
|
2229
2288
|
return Prompt(step=S_DONE, kind="done")
|
|
2230
2289
|
|
|
2231
2290
|
|
|
2291
|
+
def _submit_group(state: WizardState, prompt: Prompt, value: str) -> dict[str, Any]:
|
|
2292
|
+
"""pick_group 답(JSON 객체)을 각 멤버 submit() 으로 라우팅한다.
|
|
2293
|
+
|
|
2294
|
+
멤버 submit 이 WizardError 를 던지면 그대로 전파되어 같은 그룹을 재-프롬프트한다.
|
|
2295
|
+
answered 마킹은 모든 멤버 submit 이 통과한 뒤에만 일괄 수행한다(answered 단위의
|
|
2296
|
+
전부-아니면-전무). 개별 멤버가 변경한 state 필드는 롤백하지 않지만, 재-프롬프트 시
|
|
2297
|
+
같은 그룹이 다시 나와 사용자 입력으로 덮어쓰므로 무해하다.
|
|
2298
|
+
"""
|
|
2299
|
+
try:
|
|
2300
|
+
answers = json.loads(value or "{}")
|
|
2301
|
+
except json.JSONDecodeError as exc:
|
|
2302
|
+
raise WizardError(f"pick_group answer must be a JSON object: {exc}")
|
|
2303
|
+
if not isinstance(answers, dict):
|
|
2304
|
+
raise WizardError("pick_group answer must be a JSON object")
|
|
2305
|
+
echoes: list[str] = []
|
|
2306
|
+
for q in prompt.questions:
|
|
2307
|
+
echo = STEP_BY_ID[q.step].submit(state, str(answers.get(q.step, "") or ""))
|
|
2308
|
+
if echo:
|
|
2309
|
+
echoes.append(echo)
|
|
2310
|
+
for q in prompt.questions:
|
|
2311
|
+
if q.step not in state.answered:
|
|
2312
|
+
state.answered.append(q.step)
|
|
2313
|
+
nxt = next_prompt(state)
|
|
2314
|
+
return {"echo": "; ".join(echoes), "next": nxt.to_json()}
|
|
2315
|
+
|
|
2316
|
+
|
|
2232
2317
|
def submit(state: WizardState, value: str) -> dict[str, Any]:
|
|
2233
2318
|
"""Validate the answer for the *currently active* step and advance.
|
|
2234
2319
|
|
|
@@ -2238,6 +2323,8 @@ def submit(state: WizardState, value: str) -> dict[str, Any]:
|
|
|
2238
2323
|
prompt = next_prompt(state)
|
|
2239
2324
|
if prompt.kind == "done":
|
|
2240
2325
|
return {"echo": "", "next": prompt.to_json()}
|
|
2326
|
+
if prompt.kind == "pick_group":
|
|
2327
|
+
return _submit_group(state, prompt, value)
|
|
2241
2328
|
step = STEP_BY_ID[prompt.step]
|
|
2242
2329
|
echo = step.submit(state, value or "")
|
|
2243
2330
|
if prompt.step not in state.answered:
|
|
@@ -87,7 +87,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
87
87
|
" - trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n"
|
|
88
88
|
" - bite-sized stepwise execution order for the recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n"
|
|
89
89
|
" - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n"
|
|
90
|
-
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `##
|
|
90
|
+
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n"
|
|
91
91
|
" - YAML frontmatter line `approved: false` awaiting human flip to `true`\n"
|
|
92
92
|
" - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)"
|
|
93
93
|
),
|