okstra 0.121.0 → 0.122.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/architecture.md +4 -2
- package/docs/project-structure-overview.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/bin/lib/okstra/usage.sh +2 -2
- package/runtime/prompts/launch.template.md +4 -4
- package/runtime/prompts/lead/adapters/claude-code.md +104 -0
- package/runtime/prompts/lead/adapters/codex.md +45 -0
- package/runtime/prompts/lead/adapters/external.md +46 -0
- package/runtime/prompts/lead/context-loader.md +5 -5
- package/runtime/prompts/lead/convergence.md +11 -28
- package/runtime/prompts/lead/okstra-lead-contract.md +42 -92
- package/runtime/prompts/lead/plan-body-verification.md +21 -5
- package/runtime/prompts/lead/report-writer.md +50 -51
- package/runtime/prompts/lead/team-contract.md +33 -106
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
- package/runtime/python/okstra_ctl/models.py +1 -1
- package/runtime/python/okstra_ctl/render.py +76 -194
- package/runtime/python/okstra_ctl/report_views.py +22 -7
- package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
- package/runtime/schemas/final-report-v1.0.schema.json +3 -0
- package/runtime/skills/okstra-schedule/SKILL.md +60 -18
- package/runtime/templates/reports/final-report.template.md +4 -4
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/i18n/ko.json +1 -1
- package/runtime/validators/validate-run.py +89 -0
- package/runtime/validators/validate-schedule.py +7 -3
- package/runtime/validators/validate_session_conformance.py +58 -33
- package/src/cli-registry.mjs +7 -0
- package/src/commands/inspect/stage-map.mjs +100 -0
- package/src/commands/lifecycle/install.mjs +3 -0
- package/src/commands/lifecycle/uninstall.mjs +8 -23
- package/src/lib/skill-catalog.mjs +8 -0
|
@@ -9,7 +9,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
|
|
|
9
9
|
|
|
10
10
|
## Verifier roles (resolved at run-prep time)
|
|
11
11
|
|
|
12
|
-
- **Verifier dispatch labelling.**
|
|
12
|
+
- **Verifier dispatch labelling.** The core functional role label is `<provider>-verifier` (here, and identically in `final-verification`). Provider, role, and model identity are owned by `prompts/lead/okstra-lead-contract.md` "Model assignments"; the selected runtime adapter owns provider-native dispatch-label mapping (including any `name` / `**Pane role:**` fields) and token-attribution wiring under its "Semantic operation mapping".
|
|
13
13
|
- The verifier slots are `Claude verifier` and `Codex verifier`, plus `Antigravity verifier` **only when `antigravity` is in the resolved `--workers` roster**. Every verifier in the resolved roster is dispatched regardless of which provider holds the executor role; the executor's own provider is run *separately* as a verifier (a fresh CLI session with no shared context) so that no verdict is produced from the same session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
|
|
14
14
|
- Session isolation — not model-variant divergence — is the primary self-review safeguard: each verifier is a separate CLI invocation with its own context window, so reusing the same model variant for executor and same-provider verifier is acceptable. Different model variants (e.g. executor=opus / Claude verifier=sonnet) remain recommended when available.
|
|
15
15
|
- Phase-specific model defaults override the shared defaults: `Claude verifier`=`opus`, `Codex verifier`=`gpt-5.6`, `Antigravity verifier`=`auto` (only when present in the roster). The `Executor`'s model is taken from the provider-specific worker model corresponding to `--executor`: claude→`--claude-model` (default `opus`), codex→`--codex-model` (default `gpt-5.6`), antigravity→`--antigravity-model` (default `auto`).
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
- 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 — not deferred to the `implementation` entry gate. It enforces: 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 (≤8), the `depends-on` DAG, and the per-stage vertical-slice contract (S10). S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
|
|
63
63
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
64
64
|
- at least two implementation options. **Each option must include**:
|
|
65
|
-
- **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`.
|
|
65
|
+
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
|
|
66
66
|
- affected interfaces / public contracts and downstream consumers
|
|
67
67
|
- estimated blast radius (units, configs, deployment manifests, data migrations)
|
|
68
68
|
- trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
Validator S10d rejects a missing/empty line in any of the three categories (skipped only when a `TDD exemption:` line is present). The `RED:` step below must encode these cases, not a single assertion.
|
|
82
82
|
- **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
|
|
83
83
|
- `### 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.
|
|
84
|
-
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change (시간 하한 없음; 함께 바뀌는 여러 파일을 포함할 수 있다); for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`expected` = FAIL) — the RED step encodes the case set, not a single happy-path assertion; at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN **and** that the `RED:` step's `expected` reads FAIL / the `GREEN:` step's reads PASS; S10e rejects a `TDD exemption:` whose reason is not one of doc-only / config-only / pure-rename (both in `validators/validate-implementation-plan-stages.py`).
|
|
84
|
+
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change (시간 하한 없음; 함께 바뀌는 여러 파일을 포함할 수 있다); for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`expected` = FAIL) — the RED step encodes the case set, not a single happy-path assertion; at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN **and** that the `RED:` step's `expected` reads FAIL / the `GREEN:` step's reads PASS; S10e rejects a `TDD exemption:` whose reason is not one of doc-only / config-only / pure-rename (both in `validators/validate-implementation-plan-stages.py`).
|
|
85
85
|
- **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
|
|
86
86
|
- `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <근거>` line per requirement. When the verification body is a test spec, author it with the project's own test framework (devDependency) invoked via a discovery override at `<task_root>/qa/scripts/` (jest: `--config <project config> --roots <task_root>/qa/scripts`) — never hand-roll `describe`/`expect` and never widen the project's own test config; for TypeScript specs also write `<task_root>/qa/scripts/tsconfig.json` extending the project tsconfig with the runner's `types` entry so editors resolve the file.
|
|
87
87
|
- `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
- the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
|
|
111
111
|
- **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.**
|
|
112
112
|
- every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (the unified table is the single home for these — the "no separate `Open Questions` block" rule is in the shared `_common-contract.md` clarification policy)
|
|
113
|
-
- **§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 (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) 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 `prompts/lead/plan-body-verification.md`. 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. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. 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").
|
|
113
|
+
- **§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 (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) 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 `prompts/lead/plan-body-verification.md`. 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. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. 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"). majority-disagree 항목 중 `planner-fixable` 다수는 사용자 clarification 으로 승격하기 전에 report-writer 자가수정 1회를 거친다(`prompts/lead/plan-body-verification.md` "Self-fix round"). self-fix 로 해소되지 않았거나 `needs-user-input` 다수인 항목만 `Blocks=approval` clarification 이 된다. `validators/validate-run.py` `_validate_self_fix_before_clarification` 가 self-fix 없이 승격된 planner-fixable 다수 항목을 `contract-violated` 로 실패시킨다.
|
|
114
114
|
- **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:
|
|
115
115
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
116
116
|
2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
각 단위를 새 task-key 로 시작한다(이 phase 는 다운스트림 run 을 직접 시작하지 않는다).
|
|
43
43
|
- Decision-tree walk (bounded):
|
|
44
44
|
- 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.
|
|
45
|
-
- 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:
|
|
45
|
+
- For every clarification row, put the single best answer and one-line rationale in `Expected form` as `Recommended: (a) ...`. Put other options and one-sentence consequences in the same cell as `Alternatives: (b) ... (c) ...` — the recommended answer is option `(a)` and alternatives continue from `(b)`, so the labels read `(a) (b) (c) …` in order.
|
|
46
46
|
- **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.
|
|
47
47
|
- 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.
|
|
48
48
|
- Expected output emphasis:
|
|
@@ -14,6 +14,7 @@ class LeadRuntimeInfo:
|
|
|
14
14
|
adapter_dispatch_mode: str
|
|
15
15
|
session_accounting: str
|
|
16
16
|
has_claude_session: bool
|
|
17
|
+
adapter_contract_relative_path: str
|
|
17
18
|
|
|
18
19
|
def adapter_payload(self) -> dict[str, str]:
|
|
19
20
|
return {
|
|
@@ -36,6 +37,7 @@ _LEAD_RUNTIMES = {
|
|
|
36
37
|
adapter_dispatch_mode="team",
|
|
37
38
|
session_accounting="claude-jsonl",
|
|
38
39
|
has_claude_session=True,
|
|
40
|
+
adapter_contract_relative_path="lead/adapters/claude-code.md",
|
|
39
41
|
),
|
|
40
42
|
"codex": LeadRuntimeInfo(
|
|
41
43
|
runtime="codex",
|
|
@@ -46,6 +48,7 @@ _LEAD_RUNTIMES = {
|
|
|
46
48
|
adapter_dispatch_mode="render-only",
|
|
47
49
|
session_accounting="artifact-only",
|
|
48
50
|
has_claude_session=False,
|
|
51
|
+
adapter_contract_relative_path="lead/adapters/codex.md",
|
|
49
52
|
),
|
|
50
53
|
"external": LeadRuntimeInfo(
|
|
51
54
|
runtime="external",
|
|
@@ -56,6 +59,7 @@ _LEAD_RUNTIMES = {
|
|
|
56
59
|
adapter_dispatch_mode="render-only",
|
|
57
60
|
session_accounting="artifact-only",
|
|
58
61
|
has_claude_session=False,
|
|
62
|
+
adapter_contract_relative_path="lead/adapters/external.md",
|
|
59
63
|
),
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -59,7 +59,7 @@ PROVIDER_MAPPINGS = {"claude": CLAUDE, "antigravity": ANTIGRAVITY, "codex": CODE
|
|
|
59
59
|
# the claude provider; "report-writer" is role-only (None → caller uses lead's).
|
|
60
60
|
ROLE_DEFAULTS = {
|
|
61
61
|
"lead": "opus", "claude": "opus", "codex": "gpt-5.6",
|
|
62
|
-
"antigravity": "
|
|
62
|
+
"antigravity": "gemini-3.1-pro", "report-writer": None,
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
@@ -1735,6 +1735,9 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1735
1735
|
lead_contract_path = ctx.get("OKSTRA_LEAD_CONTRACT_PATH") or str(
|
|
1736
1736
|
home_prompts / "lead" / "okstra-lead-contract.md"
|
|
1737
1737
|
)
|
|
1738
|
+
adapter_contract_path = ctx.get("OKSTRA_LEAD_ADAPTER_CONTRACT_PATH") or str(
|
|
1739
|
+
home_prompts / _lead_info(ctx).adapter_contract_relative_path
|
|
1740
|
+
)
|
|
1738
1741
|
context_loader_path = ctx.get("OKSTRA_CONTEXT_LOADER_PATH") or str(
|
|
1739
1742
|
home_prompts / "lead" / "context-loader.md"
|
|
1740
1743
|
)
|
|
@@ -1756,7 +1759,10 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1756
1759
|
okstra_runtime_resources_block = (
|
|
1757
1760
|
"## Okstra Runtime Resources\n"
|
|
1758
1761
|
"\n"
|
|
1759
|
-
f"-
|
|
1762
|
+
f"- Lifecycle core contract: `{lead_contract_path}`\n"
|
|
1763
|
+
f"- Selected runtime adapter contract: `{adapter_contract_path}`\n"
|
|
1764
|
+
"- Read the lifecycle core first, then the selected adapter. "
|
|
1765
|
+
"Only the selected adapter may define host tool spelling.\n"
|
|
1760
1766
|
f"- Context loader: `{context_loader_path}`\n"
|
|
1761
1767
|
f"- Team contract: `{team_contract_path}`\n"
|
|
1762
1768
|
f"- Convergence contract: `{convergence_path}`\n"
|
|
@@ -1822,193 +1828,97 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1822
1828
|
else "- `Antigravity worker` is not selected for this run, so no Antigravity attempt is required."
|
|
1823
1829
|
)
|
|
1824
1830
|
|
|
1825
|
-
# Teammate dispatch gate block: phase-conditional. CC v2.1.178 removed
|
|
1826
|
-
# TeamCreate/TeamDelete + the Agent team_name param; every session owns one
|
|
1827
|
-
# implicit team, so workers dispatch with Agent(name: ...) and no team_name.
|
|
1828
|
-
# - release-handoff (and any other phase that renders with no workers
|
|
1829
|
-
# selected) is single-lead and dispatches no teammate at all. Emit a
|
|
1830
|
-
# short notice instead of the BLOCKING gate.
|
|
1831
|
-
# - concurrent implementation runs only record an audit marker (each session
|
|
1832
|
-
# has its own implicit team, so there is no shared-team conflict).
|
|
1833
|
-
# - All other phases keep the full teammate-dispatch contract.
|
|
1834
1831
|
task_type = ctx.get("TASK_TYPE", "")
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
"okstra codex-dispatch "
|
|
1838
|
-
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1839
|
-
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1840
|
-
)
|
|
1841
|
-
team_dispatch_command = (
|
|
1842
|
-
"okstra team dispatch "
|
|
1843
|
-
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1844
|
-
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1845
|
-
)
|
|
1846
|
-
team_await_command = (
|
|
1847
|
-
"okstra team await "
|
|
1848
|
-
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1849
|
-
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1850
|
-
)
|
|
1851
|
-
if task_type == "release-handoff" or not selected:
|
|
1852
|
-
team_creation_gate_block = (
|
|
1853
|
-
"## Single-Lead Phase (no team creation)\n"
|
|
1854
|
-
"\n"
|
|
1855
|
-
"This run is single-lead. There is no worker roster, no\n"
|
|
1856
|
-
"`TeamCreate` call, no `Agent(...)` worker dispatch, and no\n"
|
|
1857
|
-
f"convergence loop. The {lead_role} performs every step inline\n"
|
|
1858
|
-
"(reading inputs, drafting commit / PR text when applicable,\n"
|
|
1859
|
-
"asking the user, running git / gh, and writing the final\n"
|
|
1860
|
-
"report). Do NOT call `TeamCreate` or dispatch any sub-agent\n"
|
|
1861
|
-
"from this run — that would be a contract violation."
|
|
1862
|
-
)
|
|
1863
|
-
elif lead_runtime == "codex":
|
|
1864
|
-
team_creation_gate_block = (
|
|
1865
|
-
"## Codex Dispatch Gate (BLOCKING)\n"
|
|
1866
|
-
"\n"
|
|
1867
|
-
"This run was prepared for `leadRuntime=codex`. Do NOT call\n"
|
|
1868
|
-
"`TeamCreate`, do NOT call Claude Code `Agent(...)`, and do NOT\n"
|
|
1869
|
-
"invoke Claude slash skills. Codex dispatch is artifact-first and\n"
|
|
1870
|
-
"uses the prepared run manifest plus CLI-backed worker wrappers.\n"
|
|
1871
|
-
"\n"
|
|
1872
|
-
"Required actions, in order:\n"
|
|
1873
|
-
"\n"
|
|
1874
|
-
"1. Inspect the run manifest and team-state paths listed below.\n"
|
|
1875
|
-
"2. Emit `PROGRESS: phase-3-team-create skipped (codex-dispatch)`;\n"
|
|
1876
|
-
" there is no Claude Teams surface in this adapter.\n"
|
|
1877
|
-
"3. Plan worker execution with:\n"
|
|
1878
|
-
f" `{codex_dispatch_command} --dry-run`\n"
|
|
1879
|
-
"4. Dispatch supported Codex-side workers with:\n"
|
|
1880
|
-
f" `{codex_dispatch_command}`\n"
|
|
1881
|
-
" When `--workers` is omitted, unsupported roster entries such as\n"
|
|
1882
|
-
" `claude` are skipped with explicit `not-run` reasons.\n"
|
|
1883
|
-
"5. Run `report-writer` only with explicit opt-in:\n"
|
|
1884
|
-
f" `{codex_dispatch_command} --enable-codex-report-writer --report-writer-codex-model <model>`\n"
|
|
1885
|
-
"\n"
|
|
1886
|
-
"`okstra codex-dispatch` records `team-state.dispatchMode`, writes\n"
|
|
1887
|
-
"worker prompt histories when missing, persists worker results, and\n"
|
|
1888
|
-
"runs post-report validation for the Codex report-writer path."
|
|
1889
|
-
)
|
|
1890
|
-
elif lead_runtime == "external":
|
|
1891
|
-
team_creation_gate_block = (
|
|
1892
|
-
"## Tmux Worker Dispatch Gate (BLOCKING)\n"
|
|
1893
|
-
"\n"
|
|
1894
|
-
"This run was prepared for `leadRuntime=external`. Do NOT call "
|
|
1895
|
-
"`TeamCreate`, do NOT call Claude Code `Agent(...)`, and do NOT call "
|
|
1896
|
-
"`okstra codex-dispatch`. This adapter dispatches workers through "
|
|
1897
|
-
"okstra-owned tmux panes.\n"
|
|
1898
|
-
"\n"
|
|
1899
|
-
"Required actions, in order:\n"
|
|
1900
|
-
"1. Inspect the run manifest and team-state paths listed below.\n"
|
|
1901
|
-
"2. Emit `PROGRESS: phase-3-team-create skipped (tmux-pane)`; there is no Claude Teams surface in this adapter.\n"
|
|
1902
|
-
"3. Plan worker execution with:\n"
|
|
1903
|
-
f" `{team_dispatch_command} --dry-run`\n"
|
|
1904
|
-
"4. Dispatch workers with:\n"
|
|
1905
|
-
f" `{team_dispatch_command}`\n"
|
|
1906
|
-
"5. Arm one background wait using this harness's async shell primitive:\n"
|
|
1907
|
-
f" `{team_await_command}`\n"
|
|
1908
|
-
"6. On wake, read team-state. Worker completion is valid only from "
|
|
1909
|
-
"`workerDispatches[]` plus result files; pane creation alone is not completion."
|
|
1910
|
-
)
|
|
1911
|
-
elif task_type == "implementation" and concurrent_stages:
|
|
1912
|
-
team_creation_gate_block = (
|
|
1913
|
-
"## Concurrent-run marker (BLOCKING)\n"
|
|
1914
|
-
"\n"
|
|
1915
|
-
"A concurrent implementation run of the same task-key holds stage(s)\n"
|
|
1916
|
-
f"`{concurrent_stages}` right now. Each session owns its own implicit\n"
|
|
1917
|
-
"team (`session-<leadSessionId-prefix>`), so concurrent same-task runs do\n"
|
|
1918
|
-
"not share team state — this run dispatches normally and only records the\n"
|
|
1919
|
-
"concurrent context for the audit trail.\n"
|
|
1920
|
-
"\n"
|
|
1921
|
-
"Required actions, in order:\n"
|
|
1922
|
-
"\n"
|
|
1923
|
-
"1. Before any dispatch, record in team-state:\n"
|
|
1924
|
-
' `teamCreate: { attempted: false, status: "skipped",'
|
|
1925
|
-
f' reason: "concurrent-run", concurrentStages: [{concurrent_stages}] }}`.\n'
|
|
1926
|
-
"2. Immediately after recording it, emit the checkpoint line\n"
|
|
1927
|
-
" `PROGRESS: phase-3-team-create skipped (concurrent-run)` — the\n"
|
|
1928
|
-
" phase-3 checkpoint is required and the session-conformance validator\n"
|
|
1929
|
-
" fails the run without it.\n"
|
|
1930
|
-
"3. Dispatch every worker with `Agent(name: ..., run_in_background: true)`\n"
|
|
1931
|
-
" and NO `team_name`; teammates join this session's implicit team and\n"
|
|
1932
|
-
" render as split-panes when `$TMUX` is set."
|
|
1933
|
-
)
|
|
1934
|
-
else:
|
|
1832
|
+
adapter_setup_facts: list[str] = []
|
|
1833
|
+
if lead_runtime == "claude-code":
|
|
1935
1834
|
base_name = f'okstra-{ctx.get("TASK_KEY", "")}'
|
|
1936
1835
|
impl_stage = str(ctx.get("EFFECTIVE_STAGES", "") or "").strip()
|
|
1937
1836
|
fv_stage = str(ctx.get("RUN_STAGE", "") or "").strip()
|
|
1938
1837
|
stage_suffix = ""
|
|
1939
1838
|
if task_type == "implementation" and impl_stage:
|
|
1940
|
-
# stage 격리 run 은 stage 별 team — 같은 task 의 다른 stage 가 남긴
|
|
1941
|
-
# team 과 이름이 충돌하지 않는다(worktree branch `-s<N>` 접미사와 동형).
|
|
1942
1839
|
stage_suffix = f"-s{impl_stage}"
|
|
1943
1840
|
elif task_type == "final-verification" and fv_stage:
|
|
1944
|
-
# 단일-stage final-verification 도 stage 별 team. `-fv-` 를 끼워
|
|
1945
|
-
# 같은 stage 의 implementation team(`-s<N>`)과도 구분한다 — 둘은
|
|
1946
|
-
# 동시에 살아 있을 수 있다. whole-task 검증(RUN_STAGE 빈 값)은
|
|
1947
|
-
# 기본 이름 유지.
|
|
1948
1841
|
stage_suffix = f"-fv-s{fv_stage}"
|
|
1949
|
-
|
|
1842
|
+
adapter_setup_facts.append(
|
|
1843
|
+
f"- Adapter audit label: `{sanitize_team_name(base_name, stage_suffix)}`"
|
|
1844
|
+
)
|
|
1845
|
+
concurrent_stages = str(ctx.get("CONCURRENT_RUN_STAGES", "") or "").strip()
|
|
1846
|
+
if concurrent_stages:
|
|
1847
|
+
adapter_setup_facts.append(
|
|
1848
|
+
f"- Prepare-recorded concurrent stages: `{concurrent_stages}`"
|
|
1849
|
+
)
|
|
1850
|
+
adapter_setup_block = (
|
|
1851
|
+
"\n".join(adapter_setup_facts)
|
|
1852
|
+
if adapter_setup_facts
|
|
1853
|
+
else "- Adapter setup facts: none"
|
|
1854
|
+
)
|
|
1855
|
+
|
|
1856
|
+
if task_type == "release-handoff" or not selected:
|
|
1950
1857
|
team_creation_gate_block = (
|
|
1951
|
-
"##
|
|
1952
|
-
"\n"
|
|
1953
|
-
"Before any `Agent` dispatch for workers, you MUST perform Phase 3 of the\n"
|
|
1954
|
-
f'okstra lead contract (`{lead_contract_path}` → "Phase 3 — Teammate setup").\n'
|
|
1858
|
+
"## Single-Lead Phase (no worker dispatch)\n"
|
|
1955
1859
|
"\n"
|
|
1956
|
-
"
|
|
1957
|
-
"
|
|
1958
|
-
"
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
"\n"
|
|
1963
|
-
"Required actions, in order, regardless of how many workers are selected\n"
|
|
1964
|
-
"for this run (roster comes from `resultContract.requiredWorkerRoles` in\n"
|
|
1965
|
-
"`task-manifest.json` — it may be 1, 2, 3, or more workers):\n"
|
|
1860
|
+
"This run has no worker roster or convergence loop. The lead performs "
|
|
1861
|
+
"the phase inline and follows the selected adapter only for user prompts, "
|
|
1862
|
+
"artifact operations, usage collection, and cleanup."
|
|
1863
|
+
)
|
|
1864
|
+
else:
|
|
1865
|
+
team_creation_gate_block = (
|
|
1866
|
+
"## Runtime Adapter Dispatch Gate (BLOCKING)\n"
|
|
1966
1867
|
"\n"
|
|
1967
|
-
f"
|
|
1968
|
-
"
|
|
1969
|
-
"
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
"
|
|
1973
|
-
|
|
1974
|
-
"
|
|
1975
|
-
"
|
|
1976
|
-
"
|
|
1868
|
+
f"- Selected lead runtime: `{lead_runtime}`\n"
|
|
1869
|
+
f"- Selected adapter contract: `{adapter_contract_path}`\n"
|
|
1870
|
+
"- Read that adapter before Phase 3. Execute `prompt_user`, "
|
|
1871
|
+
"`dispatch_worker`, `await_workers`, `redispatch_worker`, "
|
|
1872
|
+
"`shutdown_workers`, `record_lead_event`, and `collect_usage` only "
|
|
1873
|
+
"through its mapping.\n"
|
|
1874
|
+
"- Do not call a primitive documented by an unselected adapter.\n"
|
|
1875
|
+
"- Record the adapter-required Phase 3 state and emit the canonical "
|
|
1876
|
+
"`PROGRESS: phase-3-team-create ...` checkpoint before dispatch.\n"
|
|
1877
|
+
f"{adapter_setup_block}\n"
|
|
1878
|
+
"- The run manifest is authoritative for concurrent-run metadata, "
|
|
1879
|
+
"dispatch backend, and artifact paths."
|
|
1977
1880
|
)
|
|
1978
1881
|
|
|
1882
|
+
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
1883
|
+
lead_bootstrap_instruction = (
|
|
1884
|
+
f"Read the Lifecycle core contract at `{lead_contract_path}`, then read the "
|
|
1885
|
+
f"selected runtime adapter at `{adapter_contract_path}`. Read the manifests "
|
|
1886
|
+
"below for all task metadata, paths, model assignments, and worker roster. "
|
|
1887
|
+
"Execute `write_artifact`, `dispatch_worker`, and `await_workers` only through "
|
|
1888
|
+
"the selected adapter's mapping. Follow the lifecycle core's lazy-read rules "
|
|
1889
|
+
"for support resources; do not invoke hidden/internal skills."
|
|
1890
|
+
)
|
|
1891
|
+
worker_preamble_path = ctx.get(
|
|
1892
|
+
"WORKER_PROMPT_PREAMBLE_PATH",
|
|
1893
|
+
str(okstra_home() / "templates" / "worker-prompt-preamble.md"),
|
|
1894
|
+
)
|
|
1895
|
+
worker_dispatch_guidance = (
|
|
1896
|
+
"## Worker Dispatch Contract\n"
|
|
1897
|
+
"\n"
|
|
1898
|
+
"- Worker prompt paths in `task-manifest.json` are assigned prompt-history "
|
|
1899
|
+
"locations, not pre-rendered completion markers. An absent file means its "
|
|
1900
|
+
"dispatch history has not been materialized yet.\n"
|
|
1901
|
+
"- Construct each required worker prompt and follow the lifecycle core plus "
|
|
1902
|
+
"selected adapter to persist its assigned history through `write_artifact` "
|
|
1903
|
+
"and start it through `dispatch_worker`. Existing-history handling must stay "
|
|
1904
|
+
"idempotent under those contracts.\n"
|
|
1905
|
+
"- Use `await_workers` through the selected adapter and confirm terminal state "
|
|
1906
|
+
"plus every required Result Path. File presence alone is never a signal to "
|
|
1907
|
+
"skip; worker selection and skipped status come from `team-state`.\n"
|
|
1908
|
+
"- Every worker prompt must include the anchor header "
|
|
1909
|
+
f"`**Worker Preamble Path:** {worker_preamble_path}`. That file is the canonical "
|
|
1910
|
+
"source for Required Reading, Error Reporting, and Output sections; do not "
|
|
1911
|
+
"re-inline those blocks into worker prompt bodies."
|
|
1912
|
+
)
|
|
1913
|
+
|
|
1979
1914
|
if lead_runtime == "external":
|
|
1980
|
-
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
1981
|
-
lead_bootstrap_instruction = (
|
|
1982
|
-
"Read the manifests below for all task metadata, paths, model "
|
|
1983
|
-
"assignments, and worker roster. Use the tmux worker dispatch gate "
|
|
1984
|
-
"in this prompt; do not invoke Claude Code skills."
|
|
1985
|
-
)
|
|
1986
1915
|
lead_session_block = (
|
|
1987
1916
|
"## Session\n"
|
|
1988
1917
|
"\n"
|
|
1989
1918
|
"- Lead runtime: `external`\n"
|
|
1990
1919
|
"- Lead session accounting: artifact-only; this harness does not expose Claude Code jsonl session IDs."
|
|
1991
1920
|
)
|
|
1992
|
-
worker_dispatch_guidance = (
|
|
1993
|
-
"## Tmux Worker Dispatch\n"
|
|
1994
|
-
"\n"
|
|
1995
|
-
"- Worker prompt paths in `task-manifest.json` are assigned history "
|
|
1996
|
-
"locations. `okstra team dispatch` materializes missing prompt files "
|
|
1997
|
-
"from the run manifest and active-run-context; existing prompt files "
|
|
1998
|
-
"are never overwritten.\n"
|
|
1999
|
-
"- Do not call `TeamCreate`, `Agent(...)`, or `okstra codex-dispatch`. "
|
|
2000
|
-
"Use `okstra team dispatch` and `okstra team await` from the gate "
|
|
2001
|
-
"above for worker execution.\n"
|
|
2002
|
-
"- File presence is not a signal to skip. Worker selection and skipped "
|
|
2003
|
-
"statuses come from `team-state`, never from the prompts directory."
|
|
2004
|
-
)
|
|
2005
1921
|
elif lead_runtime == "codex":
|
|
2006
|
-
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
2007
|
-
lead_bootstrap_instruction = (
|
|
2008
|
-
"Read the manifests below for all task metadata, paths, model "
|
|
2009
|
-
"assignments, and worker roster. Use the Codex dispatch gate in this "
|
|
2010
|
-
"prompt; do not invoke Claude Code skills."
|
|
2011
|
-
)
|
|
2012
1922
|
lead_session_block = (
|
|
2013
1923
|
"## Session\n"
|
|
2014
1924
|
"\n"
|
|
@@ -2016,41 +1926,13 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2016
1926
|
"- Codex run resume is artifact/checkpoint based; `okstra codex-run` "
|
|
2017
1927
|
"does not create a Claude session id."
|
|
2018
1928
|
)
|
|
2019
|
-
worker_dispatch_guidance = (
|
|
2020
|
-
"## Codex Worker Dispatch\n"
|
|
2021
|
-
"\n"
|
|
2022
|
-
"- Worker prompt paths in `task-manifest.json` are assigned history "
|
|
2023
|
-
"locations. `okstra codex-dispatch` materializes missing prompt files "
|
|
2024
|
-
"from the run manifest and active-run-context; existing prompt files "
|
|
2025
|
-
"are never overwritten.\n"
|
|
2026
|
-
"- Do not call `TeamCreate` or `Agent(...)`. Use the command in the "
|
|
2027
|
-
"Codex Dispatch Gate above for worker execution.\n"
|
|
2028
|
-
"- File presence is not a signal to skip. Worker selection and skipped "
|
|
2029
|
-
"statuses come from `team-state`, never from the prompts directory."
|
|
2030
|
-
)
|
|
2031
1929
|
else:
|
|
2032
|
-
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
2033
|
-
lead_bootstrap_instruction = (
|
|
2034
|
-
f"Read the Okstra lead contract at `{lead_contract_path}` now, then read "
|
|
2035
|
-
"the manifests below for all task metadata, paths, model assignments, and "
|
|
2036
|
-
"worker roster. Follow the contract's lazy-read rules for support resources; "
|
|
2037
|
-
"do not invoke hidden/internal skills."
|
|
2038
|
-
)
|
|
2039
1930
|
lead_session_block = (
|
|
2040
1931
|
"## Session\n"
|
|
2041
1932
|
"\n"
|
|
2042
1933
|
f"- Session ID: `{ctx.get('CLAUDE_SESSION_ID', '')}`\n"
|
|
2043
1934
|
f"- Resume: `{ctx.get('CLAUDE_RESUME_COMMAND_RELATIVE_PATH', '')}`"
|
|
2044
1935
|
)
|
|
2045
|
-
worker_dispatch_guidance = (
|
|
2046
|
-
"## Worker Prompt Files (not pre-rendered)\n"
|
|
2047
|
-
"\n"
|
|
2048
|
-
"- The `runs/<phase>/prompts/<worker>-worker-prompt-*.md` files referenced in `task-manifest.json → artifacts.workerPromptPathByWorkerId` are **NOT** rendered by the okstra runtime. Those paths are the *assigned* locations where the prompt history MUST be written at dispatch time.\n"
|
|
2049
|
-
"- Therefore: at the start of every phase the prompts/ directory is normally empty (or contains only previously-dispatched workers' files). This is expected. Do NOT narrate it as \"missing\", \"누락\", or \"not yet rendered\" — it just means dispatch has not happened yet.\n"
|
|
2050
|
-
"- Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Antigravity wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.\n"
|
|
2051
|
-
"- Do not \"check if the file exists and skip dispatch\" — file presence is not a signal to skip. Worker selection and skipping rules come from team-state, never from prompts/ directory contents.\n"
|
|
2052
|
-
f"- **Worker Preamble Path (single anchor — replaces inlined `[Required reading]` and `[Error reporting]` blocks).** Every worker prompt you construct MUST include the anchor header `**Worker Preamble Path:** {ctx.get('WORKER_PROMPT_PREAMBLE_PATH', str(okstra_home() / 'templates' / 'worker-prompt-preamble.md'))}`. The file at that path is the canonical SSOT for Required Reading + Error Reporting + Output sections; workers Read it end-to-end before producing output. Do NOT re-inline those blocks into worker prompt bodies — that is the legacy ~80-line dispatch boilerplate that this anchor is designed to replace."
|
|
2053
|
-
)
|
|
2054
1936
|
|
|
2055
1937
|
# Compute results (deterministic from ctx, 덮어쓰기)
|
|
2056
1938
|
ctx["LEAD_INTRO_LINE"] = lead_intro_line
|
|
@@ -597,24 +597,32 @@ _ENUM_CUE_WORDS: tuple[str, ...] = (
|
|
|
597
597
|
|
|
598
598
|
def _parse_enum_options(statement: str) -> list[tuple[str, str]]:
|
|
599
599
|
"""Return ``[(letter, text)]`` pairs if ``statement`` looks like a
|
|
600
|
-
``(a) X (b) Y (c) Z`` enumeration, else ``[]``.
|
|
601
|
-
|
|
600
|
+
``(a) X (b) Y (c) Z`` enumeration, else ``[]``. The sequence may start
|
|
601
|
+
at any letter — a statement enum starts at ``(a)``, while the
|
|
602
|
+
``Alternatives`` cell of the Expected-form contract starts at ``(b)``
|
|
603
|
+
(the recommended answer is ``(a)``) — so parsing keys off the first
|
|
604
|
+
``(letter)`` marker found and walks the sequence from there. Stops at
|
|
605
|
+
the first recommendation cue (``권장``/``추천``/``사유``/``근거``) so the
|
|
602
606
|
"(c) — recommended because ..." tail doesn't leak into the last
|
|
603
607
|
option's text. Requires ≥ 2 options to trigger select rendering.
|
|
604
608
|
"""
|
|
605
609
|
if not statement:
|
|
606
610
|
return []
|
|
611
|
+
first = re.search(r"\(([a-z])\) ", statement)
|
|
612
|
+
if not first:
|
|
613
|
+
return []
|
|
614
|
+
letters = [chr(c) for c in range(ord(first.group(1)), ord("z") + 1)]
|
|
607
615
|
out: list[tuple[str, str]] = []
|
|
608
616
|
cursor = 0
|
|
609
|
-
for i, letter in enumerate(
|
|
617
|
+
for i, letter in enumerate(letters):
|
|
610
618
|
anchor = f"({letter}) "
|
|
611
619
|
idx = statement.find(anchor, cursor)
|
|
612
620
|
if idx < 0:
|
|
613
621
|
break
|
|
614
622
|
start = idx + len(anchor)
|
|
615
623
|
end_candidates: list[int] = []
|
|
616
|
-
if i + 1 < len(
|
|
617
|
-
n_idx = statement.find(f"({
|
|
624
|
+
if i + 1 < len(letters):
|
|
625
|
+
n_idx = statement.find(f"({letters[i + 1]}) ", start)
|
|
618
626
|
if n_idx >= 0:
|
|
619
627
|
end_candidates.append(n_idx)
|
|
620
628
|
for cue in _ENUM_CUE_WORDS:
|
|
@@ -643,6 +651,13 @@ def _lettered_option(letter: str, text: str) -> str:
|
|
|
643
651
|
return f"({letter}) {text}"
|
|
644
652
|
|
|
645
653
|
|
|
654
|
+
def _strip_leading_letter_label(text: str) -> str:
|
|
655
|
+
"""Drop a leading ``(a)`` / ``(b)`` … contract label so the caller can
|
|
656
|
+
re-letter the option exactly once (the contract labels the recommended
|
|
657
|
+
answer `(a)` and alternatives from `(b)`)."""
|
|
658
|
+
return re.sub(r"^\([a-z]\)\s*", "", text)
|
|
659
|
+
|
|
660
|
+
|
|
646
661
|
def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
647
662
|
"""Parse the ``Expected form`` contract format
|
|
648
663
|
(``Recommended: <answer> — <rationale>; Alternatives: <options>``,
|
|
@@ -662,7 +677,7 @@ def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
|
662
677
|
alt_body = rec_body[alt_idx + len(_ALTERNATIVES_CUE):]
|
|
663
678
|
rec_body = rec_body[:alt_idx]
|
|
664
679
|
# The rationale follows " — "; the option keeps only the answer part.
|
|
665
|
-
answer = rec_body.split(" — ", 1)[0].strip(" .,;—-")
|
|
680
|
+
answer = _strip_leading_letter_label(rec_body.split(" — ", 1)[0].strip(" .,;—-"))
|
|
666
681
|
options: list[tuple[str, str]] = []
|
|
667
682
|
next_letter_idx = 0
|
|
668
683
|
if answer:
|
|
@@ -678,7 +693,7 @@ def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
|
678
693
|
options.append((letter, _lettered_option(letter, text)))
|
|
679
694
|
next_letter_idx += 1
|
|
680
695
|
else:
|
|
681
|
-
alt_text = alt_body.strip(" .,;—-")
|
|
696
|
+
alt_text = _strip_leading_letter_label(alt_body.strip(" .,;—-"))
|
|
682
697
|
if alt_text and next_letter_idx < len(_ENUM_LETTERS):
|
|
683
698
|
letter = _ENUM_LETTERS[next_letter_idx]
|
|
684
699
|
options.append((letter, _lettered_option(letter, alt_text)))
|
|
@@ -15,7 +15,9 @@ recorded pane — those are left for graceful shutdown.
|
|
|
15
15
|
CC v2.1.178 removed ``TeamDelete``: the implicit team ends with the session and
|
|
16
16
|
run-end teardown only dismisses teammates via ``shutdown_request``. Reconcile
|
|
17
17
|
keeps the roster honest so a dismissed-but-dead member does not linger as a
|
|
18
|
-
live pill — see okstra-lead-contract.md "Run-scoped
|
|
18
|
+
live pill — see okstra-lead-contract.md "Run-scoped worker-resource lifecycle"
|
|
19
|
+
for neutral cleanup ownership and the selected Claude adapter's
|
|
20
|
+
"Run-scoped resource lifecycle" for this provider-specific reconciliation.
|
|
19
21
|
|
|
20
22
|
Roster resolution (``--project-root``) exists because ``team-state.lead.sessionId``
|
|
21
23
|
is a phase-3 snapshot: Claude Code re-issues the session id across resume /
|
|
@@ -1508,6 +1508,7 @@
|
|
|
1508
1508
|
"aborted-non-result"
|
|
1509
1509
|
]
|
|
1510
1510
|
},
|
|
1511
|
+
"selfFixRoundApplied": { "type": "boolean" },
|
|
1511
1512
|
"planItems": {
|
|
1512
1513
|
"type": "array",
|
|
1513
1514
|
"items": {
|
|
@@ -1525,6 +1526,7 @@
|
|
|
1525
1526
|
{ "type": "string", "pattern": "^C-\\d{3,}$" }
|
|
1526
1527
|
]
|
|
1527
1528
|
},
|
|
1529
|
+
"selfFixNote": { "type": "string" },
|
|
1528
1530
|
"verdicts": {
|
|
1529
1531
|
"type": "array",
|
|
1530
1532
|
"items": {
|
|
@@ -1537,6 +1539,7 @@
|
|
|
1537
1539
|
"enum": ["AGREE", "DISAGREE", "SUPPLEMENT", "verification-error"]
|
|
1538
1540
|
},
|
|
1539
1541
|
"breakageKind": { "type": "string" },
|
|
1542
|
+
"fixability": { "enum": ["planner-fixable", "needs-user-input"] },
|
|
1540
1543
|
"note": { "type": "string" }
|
|
1541
1544
|
}
|
|
1542
1545
|
}
|