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
@@ -23,7 +23,6 @@ from .ids import (
23
23
  PICK_TYPE_CUSTOM,
24
24
  S_ANALYSIS_TARGET,
25
25
  S_ANALYSIS_TARGET_PICK,
26
- S_ANTIGRAVITY_MODEL,
27
26
  S_APPROVED_PLAN,
28
27
  S_APPROVED_PLAN_PICK,
29
28
  S_APPROVE_PLAN_CONFIRM,
@@ -35,27 +34,17 @@ from .ids import (
35
34
  S_BRIEF_PATH_PICK,
36
35
  S_CLARIFICATION,
37
36
  S_CLARIFICATION_PICK,
38
- S_CLAUDE_MODEL,
39
- S_CODEX_MODEL,
40
37
  S_CONFIRM,
41
- S_CRITIC_PICK,
42
- S_CRITIC_TEXT,
43
- S_DEFAULTS_OR_CUSTOM,
44
38
  S_DESIGN_PREP_CONFIRM,
45
39
  S_DESIGN_PREP_DECISION,
46
40
  S_DESIGN_PREP_OVERRIDES,
47
41
  S_DIRECTIVE,
48
42
  S_DIRECTIVE_PICK,
49
43
  S_EDIT_TARGET,
50
- S_EXECUTOR,
51
- S_EXECUTOR_MODEL,
52
44
  S_FEATURE_EVIDENCE,
53
45
  S_FEATURE_EVIDENCE_PICK,
54
46
  S_FIX_CYCLE_CONFIRM,
55
- S_GROK_MODEL,
56
47
  S_HANDOFF_STAGE_PICK,
57
- S_KIMI_MODEL,
58
- S_LEAD_MODEL,
59
48
  S_PROJECT_EVIDENCE,
60
49
  S_PROJECT_EVIDENCE_PICK,
61
50
  S_PR_TEMPLATE,
@@ -63,7 +52,6 @@ from .ids import (
63
52
  S_PR_TEMPLATE_SCOPE,
64
53
  S_RELATED_TASKS,
65
54
  S_RELATED_TASKS_PICK,
66
- S_REPORT_WRITER_MODEL,
67
55
  S_REPORT_LANGUAGE,
68
56
  S_REUSE_PREVIOUS,
69
57
  S_REVERIFY_SCOPE_PICK,
@@ -77,8 +65,6 @@ from .ids import (
77
65
  S_TASK_PICK,
78
66
  S_TASK_TYPE,
79
67
  S_TASK_TYPE_TEXT,
80
- S_WORKERS_CUSTOM,
81
- S_WORKERS_OVERRIDE,
82
68
  _BRIEF_ENTRY_TASK_TYPES,
83
69
  _RESUME_REUSE_PHASES,
84
70
  _STAGE_SCOPED_TASK_TYPES,
@@ -94,14 +80,12 @@ from .state import (
94
80
  _identity_ready,
95
81
  _is_role_selection_step,
96
82
  _reset_field,
97
- _role_selection_enabled,
98
83
  )
99
84
  from .prompts import _opt, _p
100
85
  from .sources import (
101
86
  _brief_resolved,
102
87
  _has_prior_run_inputs,
103
88
  _latest_implementation_planning_report,
104
- _resolved_roster,
105
89
  _reverify_scope_pick_required,
106
90
  _suggest_latest_final_report,
107
91
  )
@@ -198,38 +182,7 @@ from .steps_options import (
198
182
  _submit_related_tasks,
199
183
  _submit_related_tasks_pick,
200
184
  )
201
- from .steps_roles import (
202
- _build_antigravity_model,
203
- _build_claude_model,
204
- _build_codex_model,
205
- _build_critic_pick,
206
- _build_critic_text,
207
- _build_defaults_or_custom,
208
- _build_executor,
209
- _build_executor_model,
210
- _build_grok_model,
211
- _build_kimi_model,
212
- _build_lead_model,
213
- _build_report_writer_model,
214
- _build_reuse_previous,
215
- _build_workers_custom,
216
- _build_workers_override,
217
- _submit_antigravity_model,
218
- _submit_claude_model,
219
- _submit_codex_model,
220
- _submit_critic_pick,
221
- _submit_critic_text,
222
- _submit_defaults_or_custom,
223
- _submit_executor,
224
- _submit_executor_model,
225
- _submit_grok_model,
226
- _submit_kimi_model,
227
- _submit_lead_model,
228
- _submit_report_writer_model,
229
- _submit_reuse_previous,
230
- _submit_workers_custom,
231
- _submit_workers_override,
232
- )
185
+ from .steps_roles import _build_reuse_previous, _submit_reuse_previous
233
186
  from .confirmation import _build_confirm, _submit_confirm
234
187
 
235
188
 
@@ -298,8 +251,7 @@ STEPS: list[Step] = [
298
251
  applies=lambda s: s.is_new_task is None,
299
252
  build=_build_task_pick, submit=_submit_task_pick,
300
253
  owns=("is_new_task", "task_group", "task_id", "task_type",
301
- "existing_brief_path", "profile_workers",
302
- "profile_optional_workers",
254
+ "existing_brief_path",
303
255
  "task_group_suggestion", "task_id_suggestion",
304
256
  "task_group_pending_text", "task_id_pending_text")),
305
257
  Step(S_TASK_GROUP,
@@ -355,15 +307,13 @@ STEPS: list[Step] = [
355
307
  or (bool(s.task_group) and bool(s.brief_path)))
356
308
  and S_TASK_TYPE not in s.answered),
357
309
  build=_build_task_type, submit=_submit_task_type,
358
- owns=("task_type", "profile_workers", "profile_optional_workers",
359
- "reuse_worktree")),
310
+ owns=("task_type", "reuse_worktree")),
360
311
  Step(S_TASK_TYPE_TEXT,
361
312
  applies=lambda s: (S_TASK_TYPE in s.answered
362
313
  and not s.task_type
363
314
  and S_TASK_TYPE_TEXT not in s.answered),
364
315
  build=_build_task_type_text, submit=_submit_task_type_text,
365
- owns=("task_type", "profile_workers", "profile_optional_workers",
366
- "reuse_worktree")),
316
+ owns=("task_type", "reuse_worktree")),
367
317
  # downstream task-type 인데 carry-in 할 brief 가 없을 때의 fallback.
368
318
  # release-handoff 는 brief 자체가 없는 phase 라 대상에서 빠진다.
369
319
  Step(S_BRIEF_CARRY,
@@ -393,7 +343,6 @@ STEPS: list[Step] = [
393
343
  owns=("task_id", "task_id_pending_text")),
394
344
  Step(S_REUSE_PREVIOUS,
395
345
  applies=lambda s: (_identity_ready(s)
396
- and s.use_defaults is None
397
346
  and s.reuse_previous is None
398
347
  and s.task_type in _RESUME_REUSE_PHASES
399
348
  and _has_prior_run_inputs(s)),
@@ -522,126 +471,10 @@ STEPS: list[Step] = [
522
471
  and S_HANDOFF_STAGE_PICK not in s.answered),
523
472
  build=_build_handoff_stage_pick, submit=_submit_handoff_stage_pick,
524
473
  owns=("handoff_stages", "approved_plan_path")),
525
- Step(S_EXECUTOR,
526
- applies=lambda s: (s.task_type == "implementation"
527
- and not _role_selection_enabled(s)
528
- and bool(s.approved_plan_path)
529
- and not s.executor),
530
- build=_build_executor, submit=_submit_executor,
531
- owns=("executor",)),
532
- Step(S_CRITIC_PICK,
533
- applies=lambda s: (not _role_selection_enabled(s)
534
- and s.task_type in ("requirements-discovery", "error-analysis", "implementation-planning", "final-verification")
535
- and not s.critic
536
- and not s.critic_pending_text
537
- and S_CRITIC_PICK not in s.answered),
538
- build=_build_critic_pick, submit=_submit_critic_pick,
539
- owns=("critic", "critic_pending_text")),
540
- Step(S_CRITIC_TEXT,
541
- applies=lambda s: (not _role_selection_enabled(s)
542
- and s.critic_pending_text
543
- and S_CRITIC_TEXT not in s.answered),
544
- build=_build_critic_text, submit=_submit_critic_text,
545
- owns=("critic", "critic_pending_text")),
546
- Step(S_DEFAULTS_OR_CUSTOM,
547
- applies=lambda s: (_identity_ready(s)
548
- and not _role_selection_enabled(s)
549
- and s.use_defaults is None),
550
- build=_build_defaults_or_custom, submit=_submit_defaults_or_custom,
551
- owns=("use_defaults",)),
552
- # Worker roster is ALWAYS prompted (independent of the defaults /
553
- # customize branch) when the profile has analyser-candidate workers
554
- # (required ∪ optional, minus report-writer). Users repeatedly hit
555
- # the failure mode where the lead silently picked "Use defaults"
556
- # and the worker prompt never appeared — defaults should govern
557
- # model choice, not worker selection. `implementation` task-type is
558
- # still skipped because it runs lead + executor only.
559
- Step(S_WORKERS_OVERRIDE,
560
- applies=lambda s: (s.task_type != "implementation"
561
- and not _role_selection_enabled(s)
562
- and any(
563
- w != "report-writer"
564
- for w in (s.profile_workers
565
- + s.profile_optional_workers)
566
- )
567
- and S_WORKERS_OVERRIDE not in s.answered),
568
- build=_build_workers_override, submit=_submit_workers_override,
569
- owns=("workers_override", "workers_custom_pending")),
570
- # "직접 선택" 을 고른 run 에서만 등장한다 — 기본 로스터에서 워커를 빼는
571
- # 축소가 가능한 유일한 화면.
572
- Step(S_WORKERS_CUSTOM,
573
- applies=lambda s: (not _role_selection_enabled(s)
574
- and s.workers_custom_pending
575
- and S_WORKERS_CUSTOM not in s.answered),
576
- build=_build_workers_custom, submit=_submit_workers_custom,
577
- owns=("workers_override", "workers_custom_pending")),
578
- Step(S_LEAD_MODEL,
579
- applies=lambda s: (s.use_defaults is False
580
- and not _role_selection_enabled(s)
581
- and S_LEAD_MODEL not in s.answered),
582
- build=_build_lead_model, submit=_submit_lead_model,
583
- owns=("lead_provider", "lead_model")),
584
- Step(S_EXECUTOR_MODEL,
585
- applies=lambda s: (s.use_defaults is False
586
- and not _role_selection_enabled(s)
587
- and s.task_type == "implementation"
588
- and S_EXECUTOR_MODEL not in s.answered),
589
- build=_build_executor_model, submit=_submit_executor_model,
590
- owns=("claude_model", "codex_model", "antigravity_model")),
591
- Step(S_CLAUDE_MODEL,
592
- applies=lambda s: (s.use_defaults is False
593
- and not _role_selection_enabled(s)
594
- and s.task_type != "implementation"
595
- and "claude" in _resolved_roster(s)
596
- and S_CLAUDE_MODEL not in s.answered),
597
- build=_build_claude_model, submit=_submit_claude_model,
598
- owns=("claude_model",)),
599
- Step(S_CODEX_MODEL,
600
- applies=lambda s: (s.use_defaults is False
601
- and not _role_selection_enabled(s)
602
- and s.task_type != "implementation"
603
- and "codex" in _resolved_roster(s)
604
- and S_CODEX_MODEL not in s.answered),
605
- build=_build_codex_model, submit=_submit_codex_model,
606
- owns=("codex_model",)),
607
- Step(S_ANTIGRAVITY_MODEL,
608
- applies=lambda s: (s.use_defaults is False
609
- and not _role_selection_enabled(s)
610
- and s.task_type != "implementation"
611
- and "antigravity" in _resolved_roster(s)
612
- and S_ANTIGRAVITY_MODEL not in s.answered),
613
- build=_build_antigravity_model, submit=_submit_antigravity_model,
614
- owns=("antigravity_model",)),
615
- Step(S_GROK_MODEL,
616
- applies=lambda s: (s.use_defaults is False
617
- and not _role_selection_enabled(s)
618
- and s.task_type != "implementation"
619
- and "grok" in _resolved_roster(s)
620
- and S_GROK_MODEL not in s.answered),
621
- build=_build_grok_model, submit=_submit_grok_model,
622
- owns=("grok_model",)),
623
- Step(S_KIMI_MODEL,
624
- applies=lambda s: (s.use_defaults is False
625
- and not _role_selection_enabled(s)
626
- and s.task_type != "implementation"
627
- and "kimi" in _resolved_roster(s)
628
- and S_KIMI_MODEL not in s.answered),
629
- build=_build_kimi_model, submit=_submit_kimi_model,
630
- owns=("kimi_model",)),
631
- Step(S_REPORT_WRITER_MODEL,
632
- applies=lambda s: (s.use_defaults is False
633
- and not _role_selection_enabled(s)
634
- and (s.task_type == "implementation"
635
- or "report-writer" in _resolved_roster(s))
636
- and S_REPORT_WRITER_MODEL not in s.answered),
637
- build=_build_report_writer_model, submit=_submit_report_writer_model,
638
- owns=("report_writer_provider", "report_writer_model")),
639
- # directive(이번 run 의 추가 지시)는 기본값/커스터마이즈와 무관하게 항상
640
- # 묻는다 — 매 run 마다 줄 수 있는 입력이므로 'Use defaults' 분기 뒤에 숨기지
641
- # 않는다. (use_defaults is not None: defaults_or_custom 답 이후에만 등장)
474
+ # 역할 화면은 이 자리(directive 앞)에서 끼어든다 — `_ROLE_SELECTION_BOUNDARY`.
642
475
  Step(S_DIRECTIVE_PICK,
643
476
  applies=lambda s: (S_DIRECTIVE_PICK not in s.answered
644
- and s.use_defaults is not None),
477
+ and _identity_ready(s)),
645
478
  build=_build_directive_pick, submit=_submit_directive_pick,
646
479
  owns=("directive", "directive_pending_text")),
647
480
  Step(S_DIRECTIVE,
@@ -650,20 +483,16 @@ STEPS: list[Step] = [
650
483
  build=_build_directive, submit=_submit_directive,
651
484
  owns=("directive", "directive_pending_text")),
652
485
  Step(S_RELATED_TASKS_PICK,
653
- applies=lambda s: (s.use_defaults is False
486
+ applies=lambda s: (_identity_ready(s)
654
487
  and S_RELATED_TASKS_PICK not in s.answered),
655
488
  build=_build_related_tasks_pick, submit=_submit_related_tasks_pick,
656
489
  owns=("related_tasks_raw", "related_tasks_pending_text")),
657
490
  Step(S_RELATED_TASKS,
658
- applies=lambda s: (s.use_defaults is False
491
+ applies=lambda s: (_identity_ready(s)
659
492
  and s.related_tasks_pending_text
660
493
  and S_RELATED_TASKS not in s.answered),
661
494
  build=_build_related_tasks, submit=_submit_related_tasks,
662
495
  owns=("related_tasks_raw", "related_tasks_pending_text")),
663
- # clarification(직전 phase final-report 입력)은 customize 전용이 아니다.
664
- # 이어가기에 필수인 직전 final-report 가 존재하면 use_defaults 와 무관하게
665
- # 입력 기회를 노출한다. (과거: use_defaults 게이트 뒤에 숨어 "Use defaults"
666
- # 를 고르면 직전 리포트가 통째로 누락됐다.)
667
496
  # implementation-planning 의 `--clarification-response` 는 같은 phase 의
668
497
  # 계획서만 받는다(`run._validate_planning_entry_inputs`). 그 계획서가 없는
669
498
  # 첫 계획 run 에서 이 화면은 `없음 / 직접 입력` 뿐이고, 직접 입력으로 넣을
@@ -673,11 +502,9 @@ STEPS: list[Step] = [
673
502
  # 함께 전달하므로, 계획 run 은 이어받을 계획서가 있을 때만 여기를 묻는다.
674
503
  Step(S_CLARIFICATION_PICK,
675
504
  applies=lambda s: (S_CLARIFICATION_PICK not in s.answered
676
- and s.use_defaults is not None
677
- and (bool(_suggest_latest_final_report(s))
678
- if s.task_type == "implementation-planning"
679
- else (s.use_defaults is False
680
- or bool(_suggest_latest_final_report(s))))),
505
+ and _identity_ready(s)
506
+ and (s.task_type != "implementation-planning"
507
+ or bool(_suggest_latest_final_report(s)))),
681
508
  build=_build_clarification_pick, submit=_submit_clarification_pick,
682
509
  owns=("clarification_response_path", "clarification_pending_text")),
683
510
  Step(S_CLARIFICATION,
@@ -711,20 +538,20 @@ STEPS: list[Step] = [
711
538
  submit=_submit_selected_direction_pick,
712
539
  owns=("selected_direction_path",)),
713
540
  Step(S_PR_TEMPLATE_PICK,
714
- applies=lambda s: (s.use_defaults is False
541
+ applies=lambda s: (_identity_ready(s)
715
542
  and s.task_type == "release-handoff"
716
543
  and S_PR_TEMPLATE_PICK not in s.answered),
717
544
  build=_build_pr_template_pick, submit=_submit_pr_template_pick,
718
545
  owns=("pr_template_path", "pr_template_scope", "pr_template_pending_text")),
719
546
  Step(S_PR_TEMPLATE,
720
- applies=lambda s: (s.use_defaults is False
547
+ applies=lambda s: (_identity_ready(s)
721
548
  and s.task_type == "release-handoff"
722
549
  and s.pr_template_pending_text
723
550
  and S_PR_TEMPLATE not in s.answered),
724
551
  build=_build_pr_template, submit=_submit_pr_template,
725
552
  owns=("pr_template_path", "pr_template_scope", "pr_template_pending_text")),
726
553
  Step(S_PR_TEMPLATE_SCOPE,
727
- applies=lambda s: (s.use_defaults is False
554
+ applies=lambda s: (_identity_ready(s)
728
555
  and s.task_type == "release-handoff"
729
556
  and bool(s.pr_template_path)
730
557
  and S_PR_TEMPLATE_SCOPE not in s.answered),
@@ -771,48 +598,30 @@ STEPS: list[Step] = [
771
598
  STEP_BY_ID = {s.id: s for s in STEPS}
772
599
 
773
600
 
601
+ # 역할 화면이 끼어드는 STEPS 위치. 이 앞의 단계로 되감으면 역할 선택도 비운다.
602
+ _ROLE_SELECTION_BOUNDARY = S_DIRECTIVE_PICK
603
+
604
+
774
605
  def _ready_for_confirm(s: WizardState) -> bool:
775
- if _role_selection_enabled(s):
776
- try:
777
- if next_role_prompt(s) is not None:
778
- return False
779
- except WizardError:
780
- return False
781
- followup_ids = [
782
- S_DIRECTIVE_PICK,
783
- S_DIRECTIVE,
784
- S_RELATED_TASKS_PICK,
785
- S_RELATED_TASKS,
786
- S_CLARIFICATION_PICK,
787
- S_CLARIFICATION,
788
- S_PR_TEMPLATE_PICK,
789
- S_PR_TEMPLATE,
790
- S_PR_TEMPLATE_SCOPE,
791
- ]
792
- return not any(STEP_BY_ID[step_id].applies(s) for step_id in followup_ids)
793
- if s.use_defaults is None:
794
- return False
795
- # Worker roster is required regardless of the defaults / customize
796
- # branch. Skipping this check let the lead drop into confirm with no
797
- # worker prompt ever shown — the very failure mode this gate exists
798
- # to prevent.
799
- workers_step = STEP_BY_ID[S_WORKERS_OVERRIDE]
800
- if workers_step.applies(s):
606
+ if not _identity_ready(s):
801
607
  return False
802
- if STEP_BY_ID[S_WORKERS_CUSTOM].applies(s):
803
- return False
804
- if s.use_defaults:
805
- return True
806
- # customize: every customize-branch step must be answered or not-applicable.
807
- custom_ids = [S_LEAD_MODEL, S_EXECUTOR_MODEL,
808
- S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL,
809
- S_REPORT_WRITER_MODEL, S_DIRECTIVE, S_RELATED_TASKS,
810
- S_CLARIFICATION, S_PR_TEMPLATE, S_PR_TEMPLATE_SCOPE]
811
- for sid in custom_ids:
812
- step = STEP_BY_ID[sid]
813
- if step.applies(s):
608
+ try:
609
+ if next_role_prompt(s) is not None:
814
610
  return False
815
- return True
611
+ except WizardError:
612
+ return False
613
+ followup_ids = [
614
+ S_DIRECTIVE_PICK,
615
+ S_DIRECTIVE,
616
+ S_RELATED_TASKS_PICK,
617
+ S_RELATED_TASKS,
618
+ S_CLARIFICATION_PICK,
619
+ S_CLARIFICATION,
620
+ S_PR_TEMPLATE_PICK,
621
+ S_PR_TEMPLATE,
622
+ S_PR_TEMPLATE_SCOPE,
623
+ ]
624
+ return not any(STEP_BY_ID[step_id].applies(s) for step_id in followup_ids)
816
625
 
817
626
 
818
627
  def _reset_from(state: WizardState, target_step: str) -> None:
@@ -823,7 +632,7 @@ def _reset_from(state: WizardState, target_step: str) -> None:
823
632
  return
824
633
  role_boundary = next(
825
634
  i for i, step in enumerate(STEPS)
826
- if step.id == S_DEFAULTS_OR_CUSTOM
635
+ if step.id == _ROLE_SELECTION_BOUNDARY
827
636
  )
828
637
  if idx < role_boundary:
829
638
  _clear_role_selection(state)
@@ -854,7 +663,6 @@ def _reset_from(state: WizardState, target_step: str) -> None:
854
663
  S_FEATURE_EVIDENCE: "feature_evidence_pending_text",
855
664
  S_PROJECT_EVIDENCE: "project_evidence_pending_text",
856
665
  S_ANALYSIS_TARGET: "analysis_target_pending_text",
857
- S_WORKERS_CUSTOM: "workers_custom_pending",
858
666
  }
859
667
  pending_field = direct_input_pending.get(target_step)
860
668
  if pending_field is not None:
@@ -15,7 +15,7 @@ from .state import (
15
15
  WizardError,
16
16
  WizardState,
17
17
  _load_role_profile_for_state,
18
- _role_selection_enabled,
18
+ _role_selection_ready,
19
19
  )
20
20
  from .roles import _host_session_context, _selectable_static_requirements
21
21
 
@@ -40,7 +40,7 @@ def _stage_intent(state: WizardState) -> WizardStageIntent:
40
40
  def render_role_args(state: WizardState) -> list[str]:
41
41
  """Return ordered canonical role count/model CLI tokens."""
42
42
  role_argv: list[str] = []
43
- if _role_selection_enabled(state):
43
+ if _role_selection_ready(state):
44
44
  profile = _load_role_profile_for_state(state)
45
45
  requirements = _selectable_static_requirements(state, profile)
46
46
  for requirement in profile.roles:
@@ -52,7 +52,6 @@ from .state import (
52
52
  _identity_ready,
53
53
  _is_role_selection_step,
54
54
  _load_role_profile_for_state,
55
- _role_selection_enabled,
56
55
  )
57
56
  from .prompts import _opt, _p
58
57
 
@@ -531,9 +530,8 @@ def _load_context(state: WizardState) -> tuple[AssignmentContext, ModelDefaultSc
531
530
 
532
531
  def next_role_prompt(state: WizardState) -> Prompt | None:
533
532
  """프로필 순서대로 역할마다 화면 하나 — 답이 없는 첫 역할의 화면을 낸다."""
534
- if not _role_selection_enabled(state) or not _identity_ready(state):
533
+ if not _identity_ready(state):
535
534
  return None
536
- state.use_defaults = False
537
535
  profile = _load_role_profile_for_state(state)
538
536
  requirements = _static_requirements(profile)
539
537
  context, scopes = _load_context(state)
@@ -8,7 +8,7 @@ from collections.abc import Mapping
8
8
  from typing import Any, Optional
9
9
 
10
10
  from okstra_ctl.ids import slugify_task_segment
11
- from okstra_ctl.brief_frontmatter import read_brief_frontmatter
11
+ from okstra_ctl.brief_frontmatter import brief_task_id, read_brief_frontmatter
12
12
  from okstra_ctl.analysis_inputs import (
13
13
  ANALYSIS_TASK_TYPES,
14
14
  AnalysisInputError,
@@ -24,11 +24,6 @@ from okstra_ctl.incremental_scope import (
24
24
  from okstra_ctl.final_report_paths import final_report_data_path
25
25
  from okstra_ctl.plan_run_root import list_implementation_planning_reports
26
26
  from okstra_ctl.stage_map import StageMapError, parse_stage_map_file
27
- from okstra_ctl.workers import (
28
- normalize_workers,
29
- resolve_optional_workers,
30
- resolve_profile_workers,
31
- )
32
27
  from okstra_ctl.workflow import PHASE_SEQUENCE
33
28
  from okstra_ctl import worktree_registry
34
29
  from okstra_ctl.worktree import is_git_work_tree, main_worktree_path
@@ -49,7 +44,7 @@ from .ids import (
49
44
  _BRIEF_HEAD_LINES,
50
45
  _BRIEF_NEXT_PHASE_RE,
51
46
  )
52
- from .state import WizardError, WizardState, _profile_path, _slug_or_die
47
+ from .state import WizardError, WizardState, _slug_or_die
53
48
  from .prompts import _p
54
49
 
55
50
 
@@ -89,18 +84,18 @@ def _brief_suggestions(path: Path) -> tuple[str, str]:
89
84
  the brief's frontmatter, or empty strings when no usable value exists.
90
85
 
91
86
  - ``task_group`` ← frontmatter ``task-group``.
92
- - ``task_id`` ← frontmatter ``brief-id`` (which matches the
93
- filename stem in okstra-brief-gen output and is the
94
- strongest single identifier of the task).
87
+ - ``task_id`` ← frontmatter ``task-id`` (okstra-manager child
88
+ briefs), else ``brief-id`` (the filename stem in
89
+ okstra-brief-gen output).
95
90
 
96
91
  A brief without frontmatter, or with placeholder values, yields two
97
92
  empty strings — callers fall back to plain-text input.
98
93
  """
99
94
  fm = read_brief_frontmatter(path)
100
95
  tg_raw = fm.get("task-group", "")
101
- bid_raw = fm.get("brief-id", "")
96
+ tid_raw = brief_task_id(fm)
102
97
  tg = "" if _looks_like_template_placeholder(tg_raw) else tg_raw
103
- tid = "" if _looks_like_template_placeholder(bid_raw) else bid_raw
98
+ tid = "" if _looks_like_template_placeholder(tid_raw) else tid_raw
104
99
  return tg, tid
105
100
 
106
101
 
@@ -145,8 +140,8 @@ def _accept_brief_path(state: WizardState, path: Path) -> None:
145
140
 
146
141
  New-task runs now ask for ``task_group`` before the brief so the wizard
147
142
  can offer same-group brief candidates. If the selected brief carries a
148
- conflicting frontmatter ``task-group``, fail fast; otherwise keep using
149
- the brief's ``brief-id`` as the task-id suggestion.
143
+ conflicting frontmatter ``task-group``, fail fast; otherwise use the
144
+ brief's task-id (``_brief_suggestions``) as the task-id suggestion.
150
145
  """
151
146
  tg_suggestion, tid_suggestion = _brief_suggestions(path)
152
147
  if state.task_group and tg_suggestion:
@@ -188,32 +183,6 @@ def _git_main_worktree(project_root: Path) -> Path:
188
183
  return main_worktree_path(project_root)
189
184
 
190
185
 
191
- def _load_profile_workers(workspace_root: Path, task_type: str) -> list[str]:
192
- return resolve_profile_workers(_profile_path(workspace_root, task_type))
193
-
194
-
195
- def _load_profile_optional_workers(
196
- workspace_root: Path, task_type: str
197
- ) -> list[str]:
198
- return resolve_optional_workers(_profile_path(workspace_root, task_type))
199
-
200
-
201
- def _resolved_roster(state: WizardState) -> list[str]:
202
- """Effective worker list AFTER override. Implementation: profile default
203
- (caller never asks for override). Others: override or profile default."""
204
- if state.task_type == "implementation":
205
- roster = list(state.profile_workers)
206
- # implementation 은 roster override 단계를 건너뛰므로, executor 로 명시
207
- # 선택한 워커(예: antigravity)가 프로필 기본 roster 에 없으면 여기서
208
- # 합류시켜야 render-bundle 의 executor∈roster 가드를 통과한다.
209
- if state.executor and state.executor not in roster:
210
- roster.append(state.executor)
211
- return roster
212
- if state.workers_override.strip():
213
- return normalize_workers(state.workers_override)
214
- return list(state.profile_workers)
215
-
216
-
217
186
  # ---- Worktree resolution ------------------------------------------------
218
187
 
219
188
  def _resolve_reuse_worktree(state: WizardState) -> bool: