okstra 0.207.0 → 0.208.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.
Files changed (134) hide show
  1. package/README.md +3 -2
  2. package/dist/cli-registry.mjs +6 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/execute/render-bundle.mjs +1 -1
  5. package/dist/commands/lifecycle/doctor.mjs +1 -1
  6. package/dist/lib/skill-catalog.mjs +1 -0
  7. package/dist/lib/skill-catalog.mjs.map +1 -1
  8. package/docs/architecture/storage-model.md +14 -0
  9. package/docs/architecture.md +30 -9
  10. package/docs/cli.md +26 -22
  11. package/docs/contributor-change-matrix.md +1 -1
  12. package/docs/project-structure-overview.md +15 -8
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/agents/operations/explain-flow.json +6 -0
  16. package/runtime/bin/lib/okstra/cli.sh +1 -5
  17. package/runtime/bin/lib/okstra/globals.sh +0 -2
  18. package/runtime/bin/lib/okstra/usage.sh +5 -3
  19. package/runtime/bin/okstra.sh +0 -2
  20. package/runtime/prompts/duties/business-flow-investigator.json +14 -0
  21. package/runtime/prompts/lead/context-loader.md +1 -1
  22. package/runtime/prompts/lead/convergence.md +22 -7
  23. package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
  24. package/runtime/prompts/lead/report-writer.md +1 -1
  25. package/runtime/prompts/lead/team-contract.md +12 -17
  26. package/runtime/prompts/profiles/_common-contract.md +3 -3
  27. package/runtime/prompts/wizard/prompts.ko.json +0 -91
  28. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
  29. package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
  30. package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
  31. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
  32. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
  33. package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
  34. package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
  35. package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
  36. package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
  37. package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
  38. package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
  39. package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
  40. package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
  41. package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
  42. package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
  43. package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
  44. package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
  45. package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
  46. package/runtime/python/okstra_ctl/convergence.py +173 -2
  47. package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
  48. package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
  49. package/runtime/python/okstra_ctl/coverage_census.py +596 -0
  50. package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
  51. package/runtime/python/okstra_ctl/direct_work.py +1 -1
  52. package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
  53. package/runtime/python/okstra_ctl/doctor.py +12 -6
  54. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  55. package/runtime/python/okstra_ctl/group_context.py +5 -4
  56. package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
  57. package/runtime/python/okstra_ctl/manager_split.py +4 -1
  58. package/runtime/python/okstra_ctl/manager_view.py +9 -3
  59. package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
  60. package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
  61. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
  62. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
  63. package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
  64. package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
  65. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
  66. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
  67. package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
  68. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
  69. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
  70. package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
  71. package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
  72. package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
  73. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
  74. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
  75. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
  76. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
  77. package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
  78. package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
  79. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
  80. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
  81. package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
  82. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
  83. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
  84. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
  85. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
  86. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
  87. package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
  88. package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
  89. package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
  90. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
  91. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
  92. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +10 -8
  93. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
  94. package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
  95. package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
  96. package/runtime/python/okstra_ctl/plan_items.py +1 -1
  97. package/runtime/python/okstra_ctl/render.py +10 -43
  98. package/runtime/python/okstra_ctl/render_final_report.py +3 -0
  99. package/runtime/python/okstra_ctl/report_assembly.py +15 -1
  100. package/runtime/python/okstra_ctl/report_finalize.py +40 -0
  101. package/runtime/python/okstra_ctl/report_html/render.py +3 -0
  102. package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
  103. package/runtime/python/okstra_ctl/run.py +78 -409
  104. package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
  105. package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
  106. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
  107. package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
  108. package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
  109. package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
  110. package/runtime/python/okstra_ctl/wizard/render.py +2 -2
  111. package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
  112. package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
  113. package/runtime/python/okstra_ctl/wizard/state.py +36 -145
  114. package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
  115. package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
  116. package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
  117. package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
  118. package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
  119. package/runtime/schemas/business-flow-v1.schema.json +847 -0
  120. package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
  121. package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
  122. package/runtime/skills/okstra-inspect/facets/history.md +5 -5
  123. package/runtime/skills/okstra-run/SKILL.md +2 -2
  124. package/runtime/templates/manager/view.template.html +7 -4
  125. package/runtime/templates/reports/business-flow.template.md +106 -0
  126. package/runtime/templates/reports/html/base.template.html +14 -1
  127. package/runtime/templates/reports/html/business-flow.template.html +31 -0
  128. package/runtime/templates/reports/html/i18n/en.json +1 -0
  129. package/runtime/templates/reports/html/i18n/ko.json +1 -0
  130. package/runtime/templates/worker-prompt-preamble.md +11 -2
  131. package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
  132. package/runtime/validators/validate-run.py +70 -21
  133. package/runtime/validators/validate_analysis_report.py +21 -21
  134. package/runtime/python/okstra_ctl/workers.py +0 -133
@@ -1,14 +1,6 @@
1
1
  # Implementation Option Selection Profile
2
2
 
3
3
  - Purpose: compare feasible implementation directions before planning, preserving a read-only record of the evidence and trade-offs that selects the direction to plan
4
- - Required workers:
5
- - claude
6
- - codex
7
- - antigravity
8
- - report-writer
9
- - Optional workers (opt-in via `--workers`):
10
- - grok
11
- - kimi
12
4
  {{INCLUDE:_common-contract.md}}
13
5
  - Brief consumption:
14
6
  - Apply the shared reporter-confirmation precondition exactly as written. Unresolved `intent-check:` and `conversion-block:` rows use `Blocks=next-phase`.
@@ -18,6 +10,11 @@
18
10
  - In `candidate-comparison` mode only, submit at most three candidates. A candidate must be feasible from inspected evidence, not from an assumed future change.
19
11
  - In `preselected-validation` mode, receive one preselected direction from the lead and validate its evidence, counterevidence, criterion scores, and requirement mappings, and state the same feasibility verdict with its rationale and counterevidence. The worker must not generate new candidates.
20
12
  - Do not produce detailed file lists, stage maps, execution commands, or a plan approval request.
13
+ - Census aspects:
14
+ - requirement mapping: Which candidate or preselected direction satisfies this requirement, and how?
15
+ - requirement support: Which inspected evidence supports that mapping?
16
+ - requirement counter-evidence: What is the strongest evidence against that mapping?
17
+ - task feasibility: Is each candidate feasible, not feasible, or uncertain on inspected evidence?
21
18
  - Pre-selection context exploration:
22
19
  - In `candidate-comparison` mode, inspect the code paths, interfaces, tests, and constraints needed to distinguish candidates before assigning scores.
23
20
  - In `preselected-validation` mode, inspect the code paths, interfaces, tests, and constraints needed to validate the one preselected direction.
@@ -8,7 +8,7 @@ This directory owns candidate evaluation, vote completion, comparison projection
8
8
  |---|---|---|
9
9
  | IOS-1 | Candidate scores, coverage, votes and ranking are recomputed | `scripts/okstra_ctl/phases/implementation_option_selection/validation.py::validate_implementation_option_selection` |
10
10
  | IOS-2 | Blocked facts retain their user decision or technical evidence channel | `scripts/okstra_ctl/phases/implementation_option_selection/validation.py::validate_blocked_answer_channel` |
11
- | IOS-3 | Comparison requires at least three independent analysers | `scripts/okstra_ctl/phases/implementation_option_selection/entry.py::validate_analyser_roster` |
11
+ | IOS-3 | A valid candidate needs two `feasible` votes, or the single vote when the user selected one designer | `scripts/okstra_ctl/phases/implementation_option_selection/validation.py::required_feasible_votes`; `scripts/okstra_ctl/phases/implementation_option_selection/tests/test_implementation_options.py::test_a_single_designer_vote_validates_its_candidate` |
12
12
  | IOS-4 | Candidate fingerprints deduplicate equivalent mechanisms | `scripts/okstra_ctl/phases/implementation_option_selection/tests/test_implementation_options.py::test_duplicate_candidates_share_a_fingerprint` |
13
13
 
14
14
  Report schemas remain in `schemas/final-report-v2.0.schema.json` and `schemas/final-report-v3.0.schema.json`, under `implementationOptionSelection`. Next-phase decisions belong to `prompts/lead/phase-routing.md`.
@@ -36,11 +36,11 @@ Direction confirmation and detailed plan approval are independent user decisions
36
36
  | `candidate-comparison` | Requirement ledger, cause evidence, code evidence, independently proposed raw candidates | At most three ranked valid directions and a separate user selection |
37
37
  | `preselected-validation` | A direction already fixed by upstream evidence or an explicit user instruction | One normalized and validated direction, or `blocked`; no alternative is generated |
38
38
 
39
- The normal analyser roster contains at least three analyser workers plus the report writer. Each analyser may propose at most three raw candidates. All analysers reassess the merged candidate set before ranking.
39
+ The user selects one to five designers (`profile.json`, three recommended) plus the report writer. Each designer may propose at most three raw candidates. All analysers reassess the merged candidate set before ranking.
40
40
 
41
41
  ## 3. Prepare gates
42
42
 
43
- Prepare rejects the phase when the brief has no stable `EB-NNN`, `PB-NNN`, or `EO-NNN` requirement IDs. External Gates are not part of that denominator. Prepare also rejects a roster with fewer than three analysers.
43
+ Prepare rejects the phase when the brief has no stable `EB-NNN`, `PB-NNN`, or `EO-NNN` requirement IDs. External Gates are not part of that denominator.
44
44
 
45
45
  The phase reuses the task-key worktree and may inspect the code and prior task artifacts. It does not obtain a writable implementation-stage worktree.
46
46
 
@@ -224,6 +224,15 @@ def _validate_option_scores(
224
224
  return values_valid and weighted_score_valid
225
225
 
226
226
 
227
+ def required_feasible_votes(participating_analysers: Sequence[str]) -> int:
228
+ """유효한 후보에 필요한 `feasible` 표 수.
229
+
230
+ 사용자가 설계자를 한 명만 고르면 두 번째 표는 오지 않는다. 그때는 그 한 표가
231
+ 전부이고, 교차 모델 검증이 없었다는 사실은 run 의 advisory 와 리포트가 알린다.
232
+ """
233
+ return min(MIN_FEASIBLE_VOTES, max(1, len(set(participating_analysers))))
234
+
235
+
227
236
  def _validate_option_feasibility(
228
237
  option: Mapping[str, object],
229
238
  participating_analysers: Sequence[str],
@@ -246,8 +255,9 @@ def _validate_option_feasibility(
246
255
  isinstance(vote, Mapping) and vote.get("verdict") == "feasible"
247
256
  for vote in votes
248
257
  )
249
- if require_valid and feasible < MIN_FEASIBLE_VOTES:
250
- errors.append(f"{option_id} must have at least two feasible votes")
258
+ required = required_feasible_votes(participating_analysers)
259
+ if require_valid and feasible < required:
260
+ errors.append(f"{option_id} must have at least {required} feasible vote(s)")
251
261
  if require_valid and option.get("safetyBlockers"):
252
262
  errors.append(f"{option_id} safetyBlockers must be empty")
253
263
  if require_valid and option.get("unresolvedFeasibilityFacts"):
@@ -260,7 +270,7 @@ def _validate_option_feasibility(
260
270
  )
261
271
  return (
262
272
  all_participated
263
- and feasible >= MIN_FEASIBLE_VOTES
273
+ and feasible >= required
264
274
  and not option.get("safetyBlockers")
265
275
  and not option.get("unresolvedFeasibilityFacts")
266
276
  and not copied
@@ -394,7 +404,7 @@ def vote_gaps(
394
404
  여기서 세는 것은 **표만 모자란** 후보다. `safetyBlockers` 나
395
405
  `unresolvedFeasibilityFacts` 가 있거나 베낀 표가 있으면 표를 더 받아도
396
406
  유효해지지 않으므로 제외한다. 남은 표를 다 받아도 `feasible` 이
397
- `MIN_FEASIBLE_VOTES` 에 못 미치는 후보도 제외한다 — 부쳐 봐야 결과가
407
+ `required_feasible_votes` 에 못 미치는 후보도 제외한다 — 부쳐 봐야 결과가
398
408
  같다.
399
409
  """
400
410
  roster = list(dict.fromkeys(str(name) for name in participating_analysers))
@@ -422,7 +432,7 @@ def vote_gaps(
422
432
  isinstance(vote, Mapping) and vote.get("verdict") == "feasible"
423
433
  for vote in votes
424
434
  )
425
- if feasible + len(missing) < MIN_FEASIBLE_VOTES:
435
+ if feasible + len(missing) < required_feasible_votes(roster):
426
436
  continue
427
437
  gaps.append(
428
438
  VoteGap(
@@ -1377,7 +1377,8 @@ def _seed(args: argparse.Namespace) -> dict[str, Any]:
1377
1377
 
1378
1378
  Idempotent by id. An existing row keeps everything it carries — verdicts
1379
1379
  already applied, `carriedForwardFromSeq`, `selfFixNote` — because a re-seed
1380
- between rounds must not erase the round before it.
1380
+ between rounds must not erase the round before it. `contentHash` and
1381
+ `stageScope` are re-derived from the current plan.
1381
1382
 
1382
1383
  `--prior-state` extends that to the previous *run*: a newly seeded item
1383
1384
  whose text the previous run already judged inherits its verdicts instead of
@@ -1415,10 +1416,17 @@ def _seed(args: argparse.Namespace) -> dict[str, Any]:
1415
1416
  ]
1416
1417
  carried = _carry_prior_run_verdicts(args, added, hashes)
1417
1418
  recorded.extend(added)
1419
+ scopes = {item["id"]: item.get("stageScope") for item in extracted}
1418
1420
  for row in recorded:
1419
1421
  item_id = row.get("id") if isinstance(row, Mapping) else None
1420
1422
  if isinstance(item_id, str) and item_id in hashes:
1421
1423
  row["contentHash"] = hashes[item_id]
1424
+ # 큐는 추출값으로, 게이트는 이 행으로 범위를 정한다. 행의 범위가 낡으면
1425
+ # 재검증 큐에서 빠진 항목이 게이트를 계속 막는다(2026-10-04 dev-11118 P-Val-5).
1426
+ if scopes[item_id]:
1427
+ row["stageScope"] = scopes[item_id]
1428
+ else:
1429
+ row.pop("stageScope", None)
1422
1430
  if args.state is not None:
1423
1431
  audit_rows = data.get("planItems")
1424
1432
  if not isinstance(audit_rows, list):
@@ -5,7 +5,7 @@
5
5
  "file writes outside the run`s artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`), including task-root QA scripts, manifest, and tsconfig (planning declares conformance commands and required dependencies; implementation writes these files); in particular, do not write to `docs/superpowers/specs/` or `docs/superpowers/plans/`",
6
6
  "executing builds, migrations, deployments, or any state-mutating command",
7
7
  "starting `implementation` inside this run (must be a separate run authorised by an approved deliverable from this phase), even if the user says \"다음 단계 진행해\"",
8
- "dispatching parallel sub-agents beyond the required worker roster (okstra owns worker fan-out)",
8
+ "dispatching parallel sub-agents beyond the run's selected roster (okstra owns worker fan-out)",
9
9
  "leaving placeholders such as TBD / TODO / \"handle edge cases\" / \"similar to Option N\" in the report",
10
10
  "delegating the self-review pass — the Okstra lead must run it"
11
11
  ]
@@ -55,7 +55,9 @@ Plan-body verification is configured under `convergence.planBodyVerification` in
55
55
 
56
56
  Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false. `gating=false` is not that opt-out: extraction and one round still run.
57
57
 
58
- The shared Majority definition and the auto-disable rule (fewer than 2 analyser workers → advisory `gating=false` path) are owned by `prompts/lead/convergence.md` §"Convergence Algorithm" / §"Configuration" and apply here unchanged.
58
+ The shared Majority definition is owned by `prompts/lead/convergence.md` §"Convergence Algorithm" / §"Configuration" and applies here unchanged.
59
+
60
+ **A one-analyser roster.** When the user selected one planner, no second vote can arrive, so that planner's vote is the panel: a non-error `DISAGREE` settles its item on that vote instead of returning `needs-reverify`, and the round closes on the gate it scored. The run carries a `no cross-model check` advisory. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_classify_plan_item_gate` lowers its quorum to one when `planBodyVerification.participatingAnalysers.rostered` is 1.
59
61
 
60
62
  ## Plan-item extraction (Round 0 equivalent)
61
63
 
@@ -456,7 +458,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
456
458
 
457
459
  The environment exception in §"Planning-time environment gap" covers **running build and test commands only** — whether a referenced path exists, whether a command is declared in `package.json`, and whether the plan is internally consistent are all checkable without it, and a blanket "capability constraints prevent workspace resolution" is not a valid answer to any of them.
458
460
 
459
- **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --project-root <dir> --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
461
+ **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --project-root <dir> --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Run it only when the first attempt has a link: a team dispatch links a result only when its attempt settles `completed`, so an attempt that ended in `error` has none, and `reject-result` then fails with `expected exactly one agent result link … found 0` — skip the step in that case. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
460
462
 
461
463
  Then run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python appends one immutable round history entry, records each verified item's votes, derives the current projection from the actual assigned roster, and stamps `completedAt` after the preceding verification command succeeds. The file accumulates across rounds; it is never truncated to the latest one. Report assembly later projects the completed nested `planBodyVerification` into the final record.
462
464
  7. **Self-fix loop (one rewrite, targeting planner-fixable defects).** After the initial verification, lead may run one report-writer rewrite when a `majority-disagree` item has a majority of `DISAGREE` verdicts at `fixability == planner-fixable`. Re-verify changed items once, preserving verdicts on unchanged content. Then stop automatic self-fix regardless of outcome and follow step 8. The fixed order is initial verification → one planner self-fix → targeted re-verification → lead decision or immediate user confirmation. A second automatic self-fix is rejected by `plan_items_cli._record_self_fixes`; `_validate_self_fix_grouping` and session activity validation detect multiple recorded rewrites. No rewrite is needed when no item qualifies.
@@ -207,7 +207,13 @@ def _critic_gate_class(item: dict) -> str | None:
207
207
  return "has-dissent" if dissent else "full-consensus"
208
208
 
209
209
 
210
- def _classify_plan_item_gate(item: dict) -> str:
210
+ def _rostered_analysers(pbv: dict) -> int | None:
211
+ declared = pbv.get("participatingAnalysers")
212
+ rostered = declared.get("rostered") if isinstance(declared, dict) else None
213
+ return rostered if isinstance(rostered, int) else None
214
+
215
+
216
+ def _classify_plan_item_gate(item: dict, *, rostered_analysers: int | None = None) -> str:
211
217
  """Recompute one plan item's gate class from its per-worker verdicts,
212
218
  per `prompts/lead/plan-body-verification.md` "Round protocol". Returns one of
213
219
  ``majority-disagree`` / ``needs-reverify`` / ``has-dissent`` /
@@ -216,7 +222,8 @@ def _classify_plan_item_gate(item: dict) -> str:
216
222
  ``majority-disagree`` so the user gate sees it. ``has-dissent`` remains
217
223
  advisory-only, rollback items, and a single-vote kind that lost its
218
224
  reproduction. An analyser 1-1 is ``needs-reverify`` until ``critic-worker``
219
- settles it.
225
+ settles it. With a one-analyser roster the single vote is the panel: no
226
+ peer can ever vote, so waiting for one would stall the round.
220
227
  """
221
228
  corrected = _critic_gate_class(item)
222
229
  if corrected is not None:
@@ -253,6 +260,7 @@ def _classify_plan_item_gate(item: dict) -> str:
253
260
  if not blocking_disagree:
254
261
  return "has-dissent"
255
262
  blocking_kinds = {bk for (_vd, bk) in blocking_disagree if bk}
263
+ quorum = 1 if rostered_analysers == 1 else 2
256
264
  # Single-vote-blocking kinds: one confirmed DISAGREE on a concrete,
257
265
  # safety-critical, adversarially-verifiable defect is enough to block, even
258
266
  # in a two-worker roster — a lone correct dissent must not be outvoted here.
@@ -270,19 +278,19 @@ def _classify_plan_item_gate(item: dict) -> str:
270
278
  # the dissent, so blocking here would reproduce the same
271
279
  # worker-failure-makes-the-gate-stricter paradox the majority branch
272
280
  # below guards against. Route it to a re-verify round instead.
273
- if len(non_error) < 2:
281
+ if len(non_error) < quorum:
274
282
  return "needs-reverify"
275
283
  return "majority-disagree"
276
284
  # Otherwise a genuine majority is required — and a majority needs at least
277
285
  # two participating votes, so a lone surviving DISAGREE (its peer returned a
278
286
  # non-result) does NOT block. That fixes the paradox where a worker failure
279
287
  # made the gate stricter than a healthy roster would.
280
- if len(non_error) >= 2 and len(blocking_disagree) > len(agree):
288
+ if len(non_error) >= quorum and len(blocking_disagree) > len(agree):
281
289
  return "majority-disagree"
282
290
  if len(blocking_disagree) == len(agree) and len(non_error) >= 2:
283
291
  return "needs-reverify"
284
292
  if (
285
- len(non_error) >= 2
293
+ len(non_error) >= quorum
286
294
  and blocking_disagree
287
295
  and (not (blocking_kinds & single_vote_kinds) or _is_variation_point_item(item))
288
296
  ):
@@ -486,7 +494,9 @@ def _resolved_noncritical_dissent_ids(data: dict) -> set[str]:
486
494
 
487
495
  def _plan_item_decision_authority(item: dict, pbv: dict) -> str | None:
488
496
  """자동 수정 이후의 설계 판단만 리드가 결정하며 사실·사용자 권한은 남긴다."""
489
- classification = _classify_plan_item_gate(item)
497
+ classification = _classify_plan_item_gate(
498
+ item, rostered_analysers=_rostered_analysers(pbv)
499
+ )
490
500
  votes = [row for row in item.get("verdicts", []) if isinstance(row, dict)]
491
501
  non_result = any(
492
502
  row.get("verdict") not in {"AGREE", "SUPPLEMENT", "DISAGREE"} for row in votes
@@ -537,7 +547,9 @@ def _is_dissent_downgraded(
537
547
  ) -> bool:
538
548
  """유효한 리드 결정 또는 사용자 진행 처분은 반대 표를 보존하며 차단을 해소한다."""
539
549
  return _lead_decision_applies(item, pbv) or (
540
- _classify_plan_item_gate(item) == "majority-disagree"
550
+ _classify_plan_item_gate(
551
+ item, rostered_analysers=_rostered_analysers(pbv)
552
+ ) == "majority-disagree"
541
553
  and str(item.get("id") or "") in accepted_item_ids
542
554
  )
543
555
 
@@ -582,7 +594,9 @@ def _set_aside_reason(item: dict, pbv: dict, accepted_item_ids: set[str]) -> str
582
594
  raw = (
583
595
  "has-dissent"
584
596
  if _is_dissent_downgraded(item, pbv, accepted_item_ids)
585
- else _classify_plan_item_gate(item)
597
+ else _classify_plan_item_gate(
598
+ item, rostered_analysers=_rostered_analysers(pbv)
599
+ )
586
600
  )
587
601
  if raw != "majority-disagree":
588
602
  return None
@@ -619,7 +633,9 @@ def _plan_item_gate_class(
619
633
  classification = (
620
634
  "has-dissent"
621
635
  if _is_dissent_downgraded(item, pbv, accepted_item_ids)
622
- else _classify_plan_item_gate(item)
636
+ else _classify_plan_item_gate(
637
+ item, rostered_analysers=_rostered_analysers(pbv)
638
+ )
623
639
  )
624
640
  if classification != "majority-disagree":
625
641
  return classification
@@ -6,7 +6,7 @@
6
6
  "mode": "static",
7
7
  "roleId": "planner",
8
8
  "dutyId": "planning-worker",
9
- "min": 2,
9
+ "min": 1,
10
10
  "recommended": 2,
11
11
  "max": 5
12
12
  },
@@ -7,14 +7,6 @@ Validation commands are executable inputs: preserve their newlines, quotes, and
7
7
  Plan for the actual worktree layout before approval. Shared documentation directories can be links to the main checkout. Choose a build command compatible with those links (for example, an installed Next.js version may provide `next build --webpack`); verify the available option rather than assuming it. Do not plan for a verifier to move links or repair its environment. Compare negative-case assertions with the brief and the script body: a requirement to cache existing assets does not establish that missing assets should be cached. Record any changed expectation in a new plan revision; preserve the earlier approved plan.
8
8
 
9
9
  - Purpose: turn an upstream-selected direction into an executable plan; legacy reruns may retain candidate comparison
10
- - Required workers:
11
- - claude
12
- - codex
13
- - report-writer
14
- - Optional workers (opt-in via `--workers`):
15
- - antigravity — when added to the roster it joins the analyser set; omitted by default
16
- - grok — optional adversarial analyser/critic through the Grok CLI wrapper
17
- - kimi — optional long-context analyser/critic through the Kimi CLI wrapper
18
10
  {{INCLUDE:_common-contract.md}}
19
11
  {{INCLUDE:_stage-discipline.md}}
20
12
  - Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
@@ -32,6 +24,11 @@ Plan for the actual worktree layout before approval. Shared documentation direct
32
24
  4. If current evidence requires changing the selected direction, emit `direction-invalidated` and stop planning.
33
25
  - identify requirement gaps and affected interfaces with file:line evidence, resolving codebase-answerable ambiguity before returning findings
34
26
  - surface migration, deployment, cross-project, and approval risks without drafting final-report headings or schema rows
27
+ - Census aspects:
28
+ - requirement plan-linkage: Which planned files and stages realize this requirement?
29
+ - requirement interface-impact: Which interfaces does realizing this requirement change, and who calls them?
30
+ - requirement validation: Which validation step proves this requirement once the plan is executed?
31
+ - task migration-deploy-approval-risk: Which migration, deployment, cross-project, or approval risk does the plan carry?
35
32
  - Legacy candidate-comparison-only responsibilities:
36
33
  - **Legacy candidate-comparison procedure** — only a legacy rerun without `selected-direction.json` compares feasible Option Candidates, preserves their trade-offs and Recommended Option, then produces stages, validation, rollback, and requirement coverage. This is the only branch that generates candidates, assigns candidate scores, recommends a direction, or awaits a user candidate choice.
37
34
  - **Spec-settled short-circuit** — when the brief already carries a decision-complete design the reporter has confirmed, do not re-litigate it. Preserve that design as the Recommended Option and its already-weighed alternatives as the remaining Option Candidates.
@@ -95,7 +92,7 @@ Plan for the actual worktree layout before approval. Shared documentation direct
95
92
  - **source code edits of any kind** — this run produces a plan document only; Edit/Write on project source files is forbidden until the plan is approved and a separate `implementation` run starts
96
93
  - executing builds, migrations, deployments, or any command that mutates project state outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`)
97
94
  - this run stays in `implementation-planning` regardless of user phrasing — the shared anti-escalation rule applies
98
- - dispatching parallel sub-agents beyond the required worker roster — okstra owns worker fan-out
95
+ - dispatching parallel sub-agents beyond the run's selected roster — okstra owns worker fan-out
99
96
  - writing artifacts anywhere except `<PROJECT_ROOT>/.okstra/` — the run's `reports/` directory is the canonical location for this phase
100
97
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
101
98
  {{INCLUDE:_clarification-recommendation.md}}
@@ -293,7 +290,7 @@ For a new `implementation-planning` run, the fixed order is initial verification
293
290
  ```
294
291
 
295
292
  An `AGREE` response records the considered counterexample and exclusion reason in its note; unverified external material is `verification-error`, not `DISAGREE`.
296
- 2. Dispatch a single plan-body reverify round to every analyser worker in the roster (`claude`, `codex`, and `antigravity` when opted in). `Report writer worker` is NOT a participant in this round.
293
+ 2. Dispatch a single plan-body reverify round to every analyser worker in the run's roster. `Report writer worker` is NOT a participant in this round.
297
294
  3. Record each verifier Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md> --round <N>`. Python validates every submitted `P-*` identifier against the current convergence state and overwrites only that round's verdicts. Then resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
298
295
  4. After `okstra plan-verify` succeeds, run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python reads the current worker assignments, atomically appends the convergence-owned history, and updates its nested final projection. This state is the only plan-verification input report assembly reads.
299
296
  5. Record every *in-scope execution* `majority-disagree` decision through `okstra approval-decision`; record its plan and clarification links only on activities. Do not promote `observed` / `deferred` / `record` items, and do not append `clarificationItems[]` directly.
@@ -6,7 +6,7 @@
6
6
  "mode": "static",
7
7
  "roleId": "analyser",
8
8
  "dutyId": "discovery-worker",
9
- "min": 3,
9
+ "min": 1,
10
10
  "recommended": 3,
11
11
  "max": 5
12
12
  },
@@ -1,15 +1,6 @@
1
1
  # Improvement Discovery Profile
2
2
 
3
3
  - Purpose: scan a codebase scope through a fixed lens whitelist and surface ranked improvement candidates with multi-worker consensus classification
4
- - Required workers:
5
- - claude
6
- - codex
7
- - antigravity
8
- - report-writer
9
- - Optional workers (opt-in via `--workers`):
10
- - grok — optional adversarial analyser/critic through the Grok CLI wrapper
11
- - kimi — optional long-context analyser/critic through the Kimi CLI wrapper
12
- - Roster guidance: the required block plus these opt-in providers form the full allowlist. As everywhere, `--workers` may narrow within it (`okstra_ctl.workers.validate_workers_against_profile` enforces the allowlist only, minimum 1), but narrowing this phase is strongly discouraged: cross-worker lens diversity is its load-bearing value, and a reduced roster produces candidates no second worker ever challenged.
13
4
  {{INCLUDE:_common-contract.md}}
14
5
  - Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
15
6
  - this phase REQUIRES a codebase-scan brief whose frontmatter contains `scope: codebase`. A brief without that marker is rejected before worker dispatch.
@@ -29,18 +20,20 @@
29
20
  - for every candidate, record lens, scope, severity, effort, recommended next phase, evidence, and a worker-local source item ID
30
21
  - identify overlap with other findings or linked tasks as duplicate, broader/narrower, conflicting, blocked-by, or follow-up instead of silently merging it
31
22
  - Worker diversity rule:
32
- - every analyser inspects every resolved priority lens. The Phase 1.5 grilling log assigns only the first pass: enumerate selected analyser worker instances in `requiredWorkerRoles` order and rotate them over resolved priority lenses in log order. Provider/model names never determine the position.
23
+ - every analyser inspects every resolved priority lens. The Phase 1.5 grilling log assigns only the first pass: enumerate selected analyser worker instances in `workerRoles` order and rotate them over resolved priority lenses in log order. Provider/model names never determine the position.
33
24
  - each worker, before broadening to the remaining lenses, must do one of: (a) produce at least one candidate from its primary pass, or (b) record a no-candidate rationale citing the highest-signal path:line evidence it inspected.
34
25
  - two workers' candidates are the same candidate only when they cite the same underlying design/code problem and the same remediation direction. Shared evidence paths alone are not enough to merge; keep distinct failure modes distinct.
35
26
  - when a candidate from one worker overlaps another worker's evidence, convergence must classify the relationship as one of: duplicate, broader/narrower, or conflicting. Do not collapse contested candidates just to meet the candidate cap.
36
27
  - when a candidate overlaps a brief `Related Task Graph` edge, convergence must classify whether the candidate is a duplicate of, blocked by, or follow-up to the linked task. Do not assign a new task-key without carrying the linked task reference into Recommended Next Steps.
28
+ - Census aspects:
29
+ - lens lens-review: Which improvement candidate, or which no-candidate rationale with path:line evidence, does this lens yield inside the scan scope?
37
30
  - Phase 1.5 — Lead reflect-back grilling (runs after Phase 1 context loading and before Phase 4 worker dispatch):
38
31
  - Lead inspects scan-scope paths via `ls` / `Grep` / `Read` to map modules, entry points, dependencies, and approximate LOC, and reviews recent git history to note which scan-scope files change most.
39
32
  - Lead emits a single reflect-back message covering: (a) understood scope per path (one-line summary), (b) understood meaning of each priority lens in this scope, (c) understood out-of-scope rationale, (d) ordered list of N open questions.
40
33
  - For each open question Lead asks ONE `AskUserQuestion` with a `(Recommended)` answer drawn from a codebase-first inspection. Budget: at most 12 questions in this phase.
41
34
  - Stop conditions (OR): all questions resolved / budget exhausted / user signals proceed.
42
35
  - Lead persists the round at `<RUN_DIR>/state/phase-1.5-grilling.md` with one section per question (question / recommended / user answer) and a closing `Resolved scope` / `Resolved lenses` block. Worker prompts use this resolved block as the authoritative scope and lens definition.
43
- - The same log includes `## Primary Pass Assignments` with a `Worker ID | Primary lens` table. It contains every selected analyser exactly once in `requiredWorkerRoles` order; the lead derives it from the resolved roster and lenses rather than provider catalog order.
36
+ - The same log includes `## Primary Pass Assignments` with a `Worker ID | Primary lens` table. It contains every selected analyser exactly once in `workerRoles` order; the lead derives it from the resolved roster and lenses rather than provider catalog order.
44
37
  - After writing the log and before Phase 4 dispatch, the lead injects its **absolute path** into every analyser prompt as the `**Phase 1.5 Grilling Log:** <absolute-path>` anchor header (see `okstra_ctl.worker_prompt_headers.worker_prompt_headers()`). This is the improvement-discovery counterpart to the `**Worktree:**` / `**Verification …:**` anchors that implementation / final-verification inject: workers read the log from this explicit path rather than re-deriving `<RUN_DIR>`. The path is byte-identical across all analysers, so it does not break the dispatch-prompt invariant.
45
38
  - Decision-tree walk (bounded):
46
39
  - 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.
@@ -6,7 +6,7 @@
6
6
  "mode": "static",
7
7
  "roleId": "analyser",
8
8
  "dutyId": "analysis-worker",
9
- "min": 2,
9
+ "min": 1,
10
10
  "recommended": 3,
11
11
  "max": 5
12
12
  },
@@ -1,14 +1,6 @@
1
1
  # Project Analysis Profile
2
2
 
3
3
  - Purpose: map a bounded project area so later work can navigate components, dependencies, entry points, repositories, and external integrations without changing the source
4
- - Required workers:
5
- - claude
6
- - codex
7
- - report-writer
8
- - Optional workers (opt-in via `--workers`):
9
- - antigravity
10
- - grok — optional adversarial analyser/critic through the Grok CLI wrapper
11
- - kimi — optional long-context analyser/critic through the Kimi CLI wrapper
12
4
  {{INCLUDE:_common-contract.md}}
13
5
  - Phase 1.5 questions:
14
6
  - Which repositories, directories, and external systems are inside the scan scope?
@@ -5,7 +5,7 @@
5
5
  shipped and in what order their PRs must be merged — the branch names do not say.
6
6
  - Purpose: take a release-ready single-stage final-verification verdict for each already-committed implementation stage and turn it into a delivered push and/or pull request, with explicit user selection at every mutating step. **One stage is one PR.** The PR head is that stage's stack branch (`<prefix>-<task-id>-s<N>`); nothing is squashed, rebased, or collected into a bundle branch.
7
7
  - **Execution model: single-lead, no worker dispatch.** This phase is a thin orchestrator over `git` / `gh`; it does NOT dispatch teammates, does NOT dispatch analysis or drafter sub-agents, and does NOT run convergence. The host-native Okstra lead performs every step inline (drafting PR text, asking the user, running git / gh, writing the final report) — see "Lead-only contract" below.
8
- - Worker roster: none — this profile intentionally has no `- Required workers:` block; the run is executed entirely by the Okstra lead.
8
+ - Worker roster: none — `profile.json` declares no roles; the run is executed entirely by the Okstra lead.
9
9
  - Lead-only contract (replaces the shared team contract for this phase):
10
10
  - The host-native Okstra lead is the sole agent for this run. No worker dispatch, no teammates, no parallel sub-agents, no convergence loop. Do NOT run `okstra convergence` in any form: prepare already wrote this run's convergence state as "not run — fewer than two analysers", which is the record report assembly reads. **Enforced:** `okstra_ctl.render._initialize_lead_only_convergence` writes it when the run has no analysers, and leaves an existing state alone.
11
11
  - The lead drafts each stage's PR title and PR body **inline** by reading the run brief, that stage's cited final-verification report, `git log --oneline <stage base>..<stage head>`, and `git diff <stage base>..<stage head> --stat`. No drafter worker is dispatched.
@@ -79,7 +79,7 @@ sequenceDiagram
79
79
  P-->>W: lead prompt for current session
80
80
  ```
81
81
 
82
- The profile has no `Required workers:` block, and `run.py` also empties the worker override for `release-handoff`. So not going through the general TeamCreate / convergence / report-writer flow of `prompts/lead/okstra-lead-contract.md` is the intended behavior.
82
+ `profile.json` declares no roles, so prepare resolves an empty roster for `release-handoff`. So not going through the general TeamCreate / convergence / report-writer flow of `prompts/lead/okstra-lead-contract.md` is the intended behavior.
83
83
 
84
84
  ## 4. entry gate
85
85
 
@@ -6,7 +6,7 @@
6
6
  "mode": "static",
7
7
  "roleId": "analyser",
8
8
  "dutyId": "discovery-worker",
9
- "min": 2,
9
+ "min": 1,
10
10
  "recommended": 3,
11
11
  "max": 5
12
12
  },
@@ -1,14 +1,6 @@
1
1
  # Requirements Discovery Profile
2
2
 
3
3
  - Purpose: classify the work request, identify missing requirement evidence, and route the task to the safest next lifecycle phase before implementation starts
4
- - Required workers:
5
- - claude
6
- - codex
7
- - report-writer
8
- - Optional workers (opt-in via `--workers`):
9
- - antigravity — when added to the roster it joins the analyser set; omitted by default
10
- - grok — optional adversarial analyser/critic through the Grok CLI wrapper
11
- - kimi — optional long-context analyser/critic through the Kimi CLI wrapper
12
4
  {{INCLUDE:_common-contract.md}}
13
5
  - Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
14
6
  - Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows use `Blocks=next-phase`.
@@ -22,6 +14,16 @@
22
14
  - identify independently startable decomposition candidates without publishing or rendering fan-out artifacts; preserve every directed dependency from `Related Task Graph`
23
15
  - resolve codebase-answerable ambiguity by inspection and record file:line evidence; return only human-owned decisions as clarification candidates with the evidence already checked
24
16
  - state the reporter's rejection criteria, missing routing inputs, and the evidence boundary behind each recommendation
17
+ - Census aspects:
18
+ - requirement interpretation: What does this requirement ask for, read against the brief and the code it touches?
19
+ - requirement scope-boundary: Where does this requirement stop, and which nearby behaviour does it leave out?
20
+ - requirement dependency: What must exist or happen first for this requirement, in code or in a related task?
21
+ - requirement verification-method: How would a later phase observe that this requirement is met?
22
+ - requirement human-decision: Does this requirement need a decision only a person can make? Name it, or cite the code that settles it.
23
+ - task classification: Which work category (bugfix, feature, improvement, refactor, ops) does the evidence support?
24
+ - task rejection-criteria: Which delivered outcome would the reporter reject?
25
+ - task missing-materials: Which missing material blocks reliable routing?
26
+ - task terminology: Which fuzzy or overloaded terms need one canonical form, and what decides it?
25
27
  - Primary focus areas:
26
28
  - classify the work as bugfix, feature, improvement, refactor, or ops
27
29
  - capture the reporter's **rejection criteria** — the delivered outcome that would make this work wrong or unacceptable — as a routing input. Consume it from the brief's `Desired Outcome` / `Out of Scope` / `Source Material` when present; when it is absent AND it would change the classification (e.g. bugfix vs feature) or the next-phase choice, raise it as one `decision` clarification row with `Evidence checked: none — reporter intent`. Never infer it — this is a reporter-intent signal, the mirror of improvement-discovery's `Anti-goals`
@@ -64,10 +64,10 @@ sequenceDiagram
64
64
  participant Git as worktree registry
65
65
  participant FS as task artifacts
66
66
 
67
- W->>P: task-type=requirements-discovery, brief, base-ref, workers
68
- P->>Prof: profile exists, Required workers parsed
67
+ W->>P: task-type=requirements-discovery, brief, base-ref, role selection
68
+ P->>Prof: profile.json role requirements loaded
69
69
  P->>P: verify installation, upsert project.json
70
- P->>P: resolve workers from profile + override
70
+ P->>P: resolve role assignments from selection + model defaults
71
71
  P->>Git: create or reuse task worktree
72
72
  P->>P: expand _common-contract include
73
73
  P->>FS: write instruction-set and manifests
@@ -6,7 +6,7 @@
6
6
  "mode": "static",
7
7
  "roleId": "analyser",
8
8
  "dutyId": "technical-verification-worker",
9
- "min": 2,
9
+ "min": 1,
10
10
  "recommended": 2,
11
11
  "max": 5
12
12
  },
@@ -1,10 +1,6 @@
1
1
  # Technical Verification Profile
2
2
 
3
3
  - Purpose: collect experimental evidence for unresolved technical facts before a new implementation comparison.
4
- - Required workers:
5
- - claude
6
- - codex
7
- - report-writer
8
4
  {{INCLUDE:_common-contract.md}}
9
5
 
10
6
  ## Input and experiment procedure
@@ -701,7 +701,7 @@ def critic_is_rostered(run_manifest: Mapping[str, Any]) -> bool:
701
701
  """이 run 이 critic 슬롯을 실제로 배정했는가.
702
702
 
703
703
  정본은 `invocationAssignments` 다 — run 이 해소한 critic 을 적는 곳이고
704
- (`okstra_ctl.render._optional_worker_roles`), `critic/*` 항목이 없으면
704
+ (`okstra_ctl.render._critic_role_rows`), `critic/*` 항목이 없으면
705
705
  critic 이 없다. `workerAssignments` 로는 판정할 수 없다: critic 은 분석
706
706
  워커 로스터에서 제외되므로(`okstra_ctl.run._analysis_worker_rows`)
707
707
  rostered 여부와 무관하게 그 배열에 나타나지 않는다.