okstra 0.147.0 → 0.148.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 (116) hide show
  1. package/README.md +21 -7
  2. package/docs/architecture/storage-model.md +34 -61
  3. package/docs/architecture.md +51 -49
  4. package/docs/cli.md +38 -21
  5. package/docs/for-ai/skills/okstra-run.md +13 -34
  6. package/docs/performance-improvement-plan-v2.md +2 -2
  7. package/docs/pr-template-usage.md +1 -1
  8. package/docs/project-structure-overview.md +10 -8
  9. package/docs/task-process/README.md +4 -4
  10. package/docs/task-process/common-flow.md +12 -12
  11. package/docs/task-process/final-verification.md +2 -2
  12. package/docs/task-process/implementation.md +1 -1
  13. package/docs/task-process/release-handoff.md +1 -1
  14. package/package.json +2 -2
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/agents/workers/antigravity-worker.md +2 -2
  17. package/runtime/agents/workers/claude-worker.md +1 -1
  18. package/runtime/agents/workers/codex-worker.md +2 -2
  19. package/runtime/agents/workers/grok-worker.md +256 -0
  20. package/runtime/agents/workers/kimi-worker.md +256 -0
  21. package/runtime/agents/workers/report-writer-worker.md +2 -2
  22. package/runtime/bin/lib/okstra/cli.sh +13 -1
  23. package/runtime/bin/lib/okstra/globals.sh +3 -0
  24. package/runtime/bin/lib/okstra/usage.sh +17 -12
  25. package/runtime/bin/okstra-grok-exec.sh +5 -0
  26. package/runtime/bin/okstra-kimi-exec.sh +5 -0
  27. package/runtime/bin/okstra-provider-exec.py +235 -0
  28. package/runtime/bin/okstra.sh +3 -0
  29. package/runtime/prompts/lead/adapters/antigravity.md +48 -0
  30. package/runtime/prompts/lead/adapters/claude-code.md +13 -11
  31. package/runtime/prompts/lead/adapters/codex.md +7 -7
  32. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  33. package/runtime/prompts/lead/report-writer.md +1 -1
  34. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  35. package/runtime/prompts/profiles/_common-contract.md +4 -4
  36. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  37. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  38. package/runtime/prompts/profiles/_implementation-executor.md +12 -12
  39. package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
  40. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  41. package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
  42. package/runtime/prompts/profiles/error-analysis.md +2 -0
  43. package/runtime/prompts/profiles/feature-analysis.md +2 -0
  44. package/runtime/prompts/profiles/final-verification.md +3 -1
  45. package/runtime/prompts/profiles/forbidden-actions.json +4 -4
  46. package/runtime/prompts/profiles/implementation-planning.md +3 -1
  47. package/runtime/prompts/profiles/implementation.md +2 -2
  48. package/runtime/prompts/profiles/improvement-discovery.md +3 -1
  49. package/runtime/prompts/profiles/project-analysis.md +2 -0
  50. package/runtime/prompts/profiles/release-handoff.md +7 -7
  51. package/runtime/prompts/profiles/requirements-discovery.md +2 -0
  52. package/runtime/prompts/wizard/prompts.ko.json +9 -1
  53. package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
  54. package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
  55. package/runtime/python/okstra_ctl/lead_events.py +1 -1
  56. package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
  57. package/runtime/python/okstra_ctl/models.py +156 -8
  58. package/runtime/python/okstra_ctl/path_hints.py +9 -25
  59. package/runtime/python/okstra_ctl/paths.py +1 -1
  60. package/runtime/python/okstra_ctl/render.py +172 -74
  61. package/runtime/python/okstra_ctl/report_html/common.py +38 -2
  62. package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
  63. package/runtime/python/okstra_ctl/report_html/render.py +7 -0
  64. package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
  65. package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
  66. package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
  67. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
  68. package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
  69. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
  70. package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
  71. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
  72. package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
  73. package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
  74. package/runtime/python/okstra_ctl/run.py +264 -45
  75. package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
  76. package/runtime/python/okstra_ctl/team.py +2 -7
  77. package/runtime/python/okstra_ctl/wizard.py +194 -21
  78. package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
  79. package/runtime/python/okstra_ctl/workers.py +3 -1
  80. package/runtime/python/okstra_ctl/workflow.py +4 -2
  81. package/runtime/python/okstra_token_usage/__init__.py +1 -0
  82. package/runtime/python/okstra_token_usage/collect.py +32 -23
  83. package/runtime/python/okstra_token_usage/pricing.py +35 -3
  84. package/runtime/schemas/final-report-v2.0.schema.json +2 -2
  85. package/runtime/skills/okstra-run/SKILL.md +31 -42
  86. package/runtime/templates/prd/pr-body.template.md +1 -1
  87. package/runtime/templates/reports/html/assets/base.css +6 -3
  88. package/runtime/templates/reports/html/base.template.html +22 -8
  89. package/runtime/templates/reports/html/macros/forms.html +2 -2
  90. package/runtime/templates/reports/html/macros/layout.html +5 -5
  91. package/runtime/templates/reports/html/macros/visualizations.html +11 -1
  92. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
  93. package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
  94. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
  95. package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
  96. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
  97. package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
  98. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
  99. package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
  100. package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
  101. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
  102. package/runtime/templates/reports/report.js +21 -4
  103. package/runtime/templates/reports/settings.template.json +4 -0
  104. package/runtime/templates/reports/task-brief.template.md +7 -7
  105. package/runtime/validators/validate-run.py +11 -6
  106. package/runtime/validators/validate_session_conformance.py +2 -1
  107. package/src/cli-registry.mjs +4 -4
  108. package/src/commands/execute/codex-dispatch.mjs +7 -10
  109. package/src/commands/execute/render-bundle.mjs +3 -3
  110. package/src/commands/execute/run.mjs +17 -52
  111. package/src/commands/execute/wizard.mjs +4 -1
  112. package/src/commands/lifecycle/doctor.mjs +6 -3
  113. package/src/commands/lifecycle/install.mjs +31 -8
  114. package/src/lib/runtime-manifest.mjs +1 -1
  115. package/src/lib/runtime-resolver.mjs +2 -2
  116. package/src/lib/worker-agent-render.mjs +50 -0
@@ -6,34 +6,34 @@
6
6
  <section data-report-section="verification-verdict">
7
7
  <h2>검증 판정</h2>
8
8
  <p><span class="status status-{{ verdict.verdictToken }}">{{ verdict.verdictToken }}</span></p>
9
- {{ render_narrative(narrative.verdictExplanation, "finalVerification.userNarrative.verdictExplanation") }}
9
+ {{ render_narrative(narrative.verdictExplanation, "finalVerification.userNarrative.verdictExplanation", evidenceIndex) }}
10
10
  </section>
11
11
 
12
12
  <section data-report-section="requirement-results" data-report-field="finalVerification.validationEvidence">
13
13
  <h2>요구사항별 결과</h2>
14
- {{ render_narrative(narrative.coverageExplanation, "finalVerification.userNarrative.coverageExplanation") }}
14
+ {{ render_narrative(narrative.coverageExplanation, "finalVerification.userNarrative.coverageExplanation", evidenceIndex) }}
15
15
  {{ figure(coverageFigure) }}
16
16
  </section>
17
17
 
18
18
  <section data-report-section="blockers" data-report-field="finalVerification.acceptanceBlockers">
19
19
  <h2>수락 차단 항목</h2>
20
- {{ render_narrative(narrative.blockerExplanation, "finalVerification.userNarrative.blockerExplanation") }}
20
+ {{ render_narrative(narrative.blockerExplanation, "finalVerification.userNarrative.blockerExplanation", evidenceIndex) }}
21
21
  <div class="summary-grid">{% for row in final.acceptanceBlockers %}{{ summary_card(row.id ~ " · " ~ row.severity, row.statement, "important") }}{% else %}<p>수락을 막는 항목이 없습니다.</p>{% endfor %}</div>
22
22
  </section>
23
23
 
24
24
  <section data-report-section="residual-risk" data-report-field="finalVerification.residualRisk">
25
25
  <h2>잔여 위험</h2>
26
- <div class="summary-grid">{% for row in final.residualRisk %}<article class="summary-card"><h3>{{ row.id }}</h3><p>{{ row.item }}</p><p><strong>담당:</strong> {{ row.owner }}</p><p><strong>상향 조건:</strong> {{ row.escalationTrigger }}</p></article>{% else %}<p>기록된 잔여 위험이 없습니다.</p>{% endfor %}</div>
26
+ <div class="summary-grid">{% for row in final.residualRisk %}<article class="summary-card"><h3>{{ row.id }}</h3><p>{{ row.item | inline_code }}</p><p><strong>담당:</strong> {{ row.owner | inline_code }}</p><p><strong>상향 조건:</strong> {{ row.escalationTrigger | inline_code }}</p></article>{% else %}<p>기록된 잔여 위험이 없습니다.</p>{% endfor %}</div>
27
27
  </section>
28
28
 
29
29
  <section data-report-section="manual-results" data-report-field="finalVerification.manualUserTest">
30
30
  <h2>수동 사용자 테스트 결과</h2>
31
- {% if final.manualUserTest.applicable %}{% for row in final.manualUserTest.results %}<article class="summary-card tone-{{ row.result }}"><h3>{{ row.target }} · {{ row.result }}</h3><p>{{ row.performed }}</p><p>{{ row.observed }}</p></article>{% endfor %}{% else %}<p>{{ final.manualUserTest.exemptionReaffirm }}</p>{% endif %}
31
+ {% if final.manualUserTest.applicable %}{% for row in final.manualUserTest.results %}<article class="summary-card tone-{{ row.result }}"><h3>{{ row.target | inline_code }} · {{ row.result }}</h3><p>{{ row.performed | inline_code }}</p><p>{{ row.observed | inline_code }}</p></article>{% endfor %}{% else %}<p>{{ final.manualUserTest.exemptionReaffirm | inline_code }}</p>{% endif %}
32
32
  </section>
33
33
 
34
34
  <section data-report-section="release-route">
35
35
  <h2>릴리스 경로</h2>
36
- {{ render_narrative(narrative.releaseReadiness, "finalVerification.userNarrative.releaseReadiness") }}
37
- {% if releaseAllowed %}<p>{{ final.routingRecommendation }}</p>{% else %}<p>현재 판정으로는 release-handoff를 시작할 수 없습니다. 차단 또는 조건을 먼저 해소하세요.</p>{% endif %}
36
+ {{ render_narrative(narrative.releaseReadiness, "finalVerification.userNarrative.releaseReadiness", evidenceIndex) }}
37
+ {% if releaseAllowed %}<p>{{ final.routingRecommendation | inline_code }}</p>{% else %}<p>현재 판정으로는 release-handoff를 시작할 수 없습니다. 차단 또는 조건을 먼저 해소하세요.</p>{% endif %}
38
38
  </section>
39
39
  {% endblock %}
@@ -6,35 +6,35 @@
6
6
  {% block human_content %}
7
7
  <section data-report-section="planning-goal">
8
8
  <h2>계획 목표</h2>
9
- <p>{{ humanSummary.outcome }}</p>
10
- <div class="summary-grid">{% for reason in humanSummary.whyItMatters %}{{ summary_card("왜 중요한가", reason, "important") }}{% endfor %}</div>
9
+ <p>{{ humanSummary.outcome | inline_code }}</p>
10
+ <div class="summary-grid">{% for reason in humanSummary.whyItMatters %}{{ summary_card("왜 중요한가 " ~ loop.index, reason, "important") }}{% endfor %}</div>
11
11
  </section>
12
12
 
13
13
  <section data-report-section="option-comparison">
14
14
  <h2>구현 대안 비교</h2>
15
- {{ render_narrative(narrative.optionExplanation, "implementationPlanning.userNarrative.optionExplanation") }}
16
- <div class="summary-grid" data-report-field="implementationPlanning.optionCandidates">{% for row in planning.optionCandidates %}<article class="summary-card"><h3>{{ row.name }}</h3><p><strong>인터페이스:</strong> {{ row.interfaces }}</p><p><strong>영향 범위:</strong> {{ row.blastRadius }}</p><ul>{% for file in row.fileStructure %}<li>{{ file.action }} <code>{{ file.path }}</code> — {{ file.summary }}</li>{% endfor %}</ul></article>{% endfor %}</div>
17
- <table data-report-field="implementationPlanning.tradeoffMatrix"><thead><tr><th>옵션</th><th>복잡도</th><th>위험</th><th>가역성</th><th>테스트 비용</th><th>배포 비용</th></tr></thead><tbody>{% for row in planning.tradeoffMatrix %}<tr><td>{{ row.option }}</td><td>{{ row.complexity }}</td><td>{{ row.risk }}</td><td>{{ row.reversibility }}</td><td>{{ row.testCoverageCost }}</td><td>{{ row.rolloutCost }}</td></tr>{% endfor %}</tbody></table>
15
+ {{ render_narrative(narrative.optionExplanation, "implementationPlanning.userNarrative.optionExplanation", evidenceIndex) }}
16
+ <div class="summary-grid" data-report-field="implementationPlanning.optionCandidates">{% for row in planning.optionCandidates %}<article class="summary-card"><h3>{{ row.name | inline_code }}</h3><p><strong>인터페이스:</strong> {{ row.interfaces | inline_code }}</p><p><strong>영향 범위:</strong> {{ row.blastRadius | inline_code }}</p><ul>{% for file in row.fileStructure %}<li>{{ file.action }} <code>{{ file.path }}</code> — {{ file.summary | inline_code }}</li>{% endfor %}</ul></article>{% endfor %}</div>
17
+ <table data-report-field="implementationPlanning.tradeoffMatrix"><thead><tr><th>옵션</th><th>복잡도</th><th>위험</th><th>가역성</th><th>테스트 비용</th><th>배포 비용</th></tr></thead><tbody>{% for row in planning.tradeoffMatrix %}<tr><td>{{ row.option | inline_code }}</td><td>{{ row.complexity | inline_code }}</td><td>{{ row.risk | inline_code }}</td><td>{{ row.reversibility | inline_code }}</td><td>{{ row.testCoverageCost | inline_code }}</td><td>{{ row.rolloutCost | inline_code }}</td></tr>{% endfor %}</tbody></table>
18
18
  </section>
19
19
 
20
20
  <section data-report-section="recommendation" data-report-field="implementationPlanning.recommendedOption">
21
21
  <h2>권장안</h2>
22
- {{ render_narrative(narrative.recommendationExplanation, "implementationPlanning.userNarrative.recommendationExplanation") }}
23
- <article class="summary-card tone-important"><h3>{{ planning.recommendedOption.name }}</h3><p>{{ planning.recommendedOption.coreReason }}</p><p>{{ planning.recommendedOption.rationale }}</p><p><strong>선택하지 않은 안:</strong> {{ planning.recommendedOption.rejectedSummary }}</p></article>
22
+ {{ render_narrative(narrative.recommendationExplanation, "implementationPlanning.userNarrative.recommendationExplanation", evidenceIndex) }}
23
+ <article class="summary-card tone-important"><h3>{{ planning.recommendedOption.name | inline_code }}</h3><p>{{ planning.recommendedOption.coreReason | inline_code }}</p><p>{{ planning.recommendedOption.rationale | inline_code }}</p><p><strong>선택하지 않은 안:</strong> {{ planning.recommendedOption.rejectedSummary | inline_code }}</p></article>
24
24
  </section>
25
25
 
26
26
  <section data-report-section="stage-map" data-report-field="implementationPlanning.stageMap">
27
27
  <h2>단계별 실행 지도</h2>
28
- {{ render_narrative(narrative.stageStrategy, "implementationPlanning.userNarrative.stageStrategy") }}
28
+ {{ render_narrative(narrative.stageStrategy, "implementationPlanning.userNarrative.stageStrategy", evidenceIndex) }}
29
29
  {{ figure(stageFigure) }}
30
- <div class="summary-grid">{% for row in planning.stages %}<article class="summary-card"><h3>Stage {{ row.stage }} · {{ row.title }}</h3><p>{{ row.sliceValue }}</p><p><strong>완료 조건:</strong> {{ row.acceptance }}</p><p><strong>검증:</strong> {{ row.stageValidation }}</p></article>{% endfor %}</div>
30
+ <div class="summary-grid">{% for row in planning.stages %}<article class="summary-card"><h3>Stage {{ row.stage }} · {{ row.title | inline_code }}</h3><p>{{ row.sliceValue | inline_code }}</p><p><strong>완료 조건:</strong> {{ row.acceptance | inline_code }}</p><p><strong>검증:</strong> {{ row.stageValidation | inline_code }}</p></article>{% endfor %}</div>
31
31
  </section>
32
32
 
33
33
  <section data-report-section="validation-and-rollback">
34
34
  <h2>검증과 롤백</h2>
35
- {{ render_narrative(narrative.validationAndRollback, "implementationPlanning.userNarrative.validationAndRollback") }}
36
- <h3>검증 체크리스트</h3><table data-report-field="implementationPlanning.validationChecklist"><thead><tr><th>ID</th><th>시점</th><th>확인</th><th>명령</th><th>기대 결과</th></tr></thead><tbody>{% for row in planning.validationChecklist %}<tr><td>{{ row.id }}</td><td>{{ row.phase }}</td><td>{{ row.check }}</td><td><code>{{ row.commandOrObservation }}</code></td><td>{{ row.expectedOutcome }}</td></tr>{% endfor %}</tbody></table>
37
- <h3>롤백 전략</h3><div class="summary-grid" data-report-field="implementationPlanning.rollbackStrategy">{% for row in planning.rollbackStrategy %}{{ summary_card(row.id ~ " · " ~ row.triggerSignal, row.action, "important") }}{% endfor %}</div>
35
+ {{ render_narrative(narrative.validationAndRollback, "implementationPlanning.userNarrative.validationAndRollback", evidenceIndex) }}
36
+ <h3>검증 체크리스트</h3><table data-report-field="implementationPlanning.validationChecklist"><thead><tr><th>ID</th><th>시점</th><th>확인</th><th>명령</th><th>기대 결과</th></tr></thead><tbody>{% for row in planning.validationChecklist %}<tr><td>{{ row.id }}</td><td>{{ row.phase }}</td><td>{{ row.check | inline_code }}</td><td><code>{{ row.commandOrObservation }}</code></td><td>{{ row.expectedOutcome | inline_code }}</td></tr>{% endfor %}</tbody></table>
37
+ <h3>롤백 전략</h3><div class="summary-grid" data-report-field="implementationPlanning.rollbackStrategy">{% for row in planning.rollbackStrategy %}{{ summary_card(row.id ~ " · " ~ (row.triggerSignal | inline_code), row.action, "important") }}{% endfor %}</div>
38
38
  </section>
39
39
 
40
40
  <section data-report-section="open-decisions">
@@ -5,36 +5,36 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="delivered-outcome">
7
7
  <h2>전달된 결과</h2>
8
- {{ render_narrative(narrative.deliveredOutcome, "implementation.userNarrative.deliveredOutcome") }}
9
- <p>{{ humanSummary.outcome }}</p>
8
+ {{ render_narrative(narrative.deliveredOutcome, "implementation.userNarrative.deliveredOutcome", evidenceIndex) }}
9
+ <p>{{ humanSummary.outcome | inline_code }}</p>
10
10
  </section>
11
11
 
12
12
  <section data-report-section="change-map" data-report-field="implementation.diffSummary">
13
13
  <h2>무엇이 달라졌는가</h2>
14
- {{ render_narrative(narrative.changeExplanation, "implementation.userNarrative.changeExplanation") }}
14
+ {{ render_narrative(narrative.changeExplanation, "implementation.userNarrative.changeExplanation", evidenceIndex) }}
15
15
  {{ figure(changeFigure) }}
16
16
  </section>
17
17
 
18
18
  <section data-report-section="requirement-coverage">
19
19
  <h2>요구사항 충족</h2>
20
- <table data-report-field="implementation.requirementCoverage"><thead><tr><th>ID</th><th>요구사항</th><th>구현·증거</th><th>상태</th></tr></thead><tbody>{% for row in implementation.requirementCoverage %}<tr><td>{{ row.id }}</td><td>{{ row.requirement }}</td><td>{{ row.coveredBy }}</td><td><span class="status status-{{ row.status }}">{{ row.status }}</span></td></tr>{% endfor %}</tbody></table>
20
+ <table data-report-field="implementation.requirementCoverage"><thead><tr><th>ID</th><th>요구사항</th><th>구현·증거</th><th>상태</th></tr></thead><tbody>{% for row in implementation.requirementCoverage %}<tr><td>{{ row.id }}</td><td>{{ row.requirement | inline_code }}</td><td>{{ row.coveredBy | inline_code }}</td><td><span class="status status-{{ row.status }}">{{ row.status }}</span></td></tr>{% endfor %}</tbody></table>
21
21
  </section>
22
22
 
23
23
  <section data-report-section="validation" data-report-field="implementation.validationEvidence">
24
24
  <h2>검증 결과</h2>
25
- {{ render_narrative(narrative.validationExplanation, "implementation.userNarrative.validationExplanation") }}
25
+ {{ render_narrative(narrative.validationExplanation, "implementation.userNarrative.validationExplanation", evidenceIndex) }}
26
26
  <div class="summary-grid">{% for row in implementation.validationEvidence %}{{ summary_card(row.phase ~ " · 종료 코드 " ~ row.exitCode, row.outputTail, "important" if row.exitCode else "neutral") }}{% endfor %}</div>
27
27
  <p><strong>독립 검증:</strong> {% for row in implementation.verifierResults %}<span class="status status-{{ row.verdict | lower }}">{{ row.verdict }}</span>{% if not loop.last %}, {% endif %}{% endfor %}</p>
28
28
  </section>
29
29
 
30
30
  <section data-report-section="remaining-issues">
31
31
  <h2>남은 문제</h2>
32
- {{ render_narrative(narrative.remainingIssues, "implementation.userNarrative.remainingIssues") }}
32
+ {{ render_narrative(narrative.remainingIssues, "implementation.userNarrative.remainingIssues", evidenceIndex) }}
33
33
  <div class="summary-grid">{% for row in implementation.outOfPlanEdits %}{{ summary_card(row.id ~ " · " ~ row.file, row.rationale, "important") }}{% else %}<p>계획 밖 수정 사항이 없습니다.</p>{% endfor %}</div>
34
34
  </section>
35
35
 
36
36
  <section data-report-section="manual-test" data-report-field="implementation.manualUserTest">
37
37
  <h2>수동 사용자 테스트</h2>
38
- {% if implementation.manualUserTest.applicable %}{% for row in implementation.manualUserTest["items"] %}<article class="summary-card"><h3>{{ row.target }}</h3><p><strong>환경:</strong> {{ row.environmentSetup }}</p><ol>{% for step in row.steps %}<li>{{ step }}</li>{% endfor %}</ol><p><strong>기대 결과:</strong> {{ row.expectedResult }}</p></article>{% endfor %}{% else %}<p>{{ implementation.manualUserTest.exemptionReason }}</p>{% endif %}
38
+ {% if implementation.manualUserTest.applicable %}{% for row in implementation.manualUserTest["items"] %}<article class="summary-card"><h3>{{ row.target | inline_code }}</h3><p><strong>환경:</strong> {{ row.environmentSetup | inline_code }}</p><ol>{% for step in row.steps %}<li>{{ step | inline_code }}</li>{% endfor %}</ol><p><strong>기대 결과:</strong> {{ row.expectedResult | inline_code }}</p></article>{% endfor %}{% else %}<p>{{ implementation.manualUserTest.exemptionReason | inline_code }}</p>{% endif %}
39
39
  </section>
40
40
  {% endblock %}
@@ -5,14 +5,14 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="opportunities">
7
7
  <h2>발견한 개선 기회</h2>
8
- {{ render_narrative(narrative.opportunityOverview, "improvementDiscovery.userNarrative.opportunityOverview") }}
9
- <table data-report-field="improvementDiscovery.lensCoverage"><thead><tr><th>렌즈</th><th>결과</th><th>설명</th><th>근거</th></tr></thead><tbody>{% for row in improvement.lensCoverage %}<tr><td>{{ row.lens }}</td><td>{{ row.status }}</td><td>{{ row.rationale }}</td><td>{{ row.evidence | join(', ') }}</td></tr>{% endfor %}</tbody></table>
8
+ {{ render_narrative(narrative.opportunityOverview, "improvementDiscovery.userNarrative.opportunityOverview", evidenceIndex) }}
9
+ <table data-report-field="improvementDiscovery.lensCoverage"><thead><tr><th>렌즈</th><th>결과</th><th>설명</th><th>근거</th></tr></thead><tbody>{% for row in improvement.lensCoverage %}<tr><td>{{ row.lens }}</td><td>{{ row.status }}</td><td>{{ row.rationale | inline_code }}</td><td>{{ row.evidence | evidence_refs(evidenceIndex) }}</td></tr>{% endfor %}</tbody></table>
10
10
  </section>
11
11
 
12
12
  <section data-report-section="ranked-candidates">
13
13
  <h2>우선순위 후보</h2>
14
- {{ render_narrative(narrative.prioritizationExplanation, "improvementDiscovery.userNarrative.prioritizationExplanation") }}
15
- <div class="summary-grid" data-report-field="improvementDiscovery.candidates">{% for row in improvement.candidates %}<article class="summary-card tone-{{ row.severity }}"><p class="eyebrow">{{ loop.index }}순위 · {{ row.id }} · 영향 {{ row.severity }} · 노력 {{ row.effort }}</p><h3>{{ row.title }}</h3><p>{{ row.expectedBehaviorAfter }}</p><p><strong>다음 단계:</strong> {{ row.recommendedNextPhase }}</p><p class="evidence-refs">근거: {{ row.evidence | join(', ') }}</p></article>{% else %}<p>선택할 개선 후보가 없습니다.</p>{% endfor %}</div>
14
+ {{ render_narrative(narrative.prioritizationExplanation, "improvementDiscovery.userNarrative.prioritizationExplanation", evidenceIndex) }}
15
+ <div class="summary-grid" data-report-field="improvementDiscovery.candidates">{% for row in improvement.candidates %}<article class="summary-card tone-{{ row.severity }}"><p class="eyebrow">{{ loop.index }}순위 · {{ row.id }} · 영향 {{ row.severity }} · 노력 {{ row.effort }}</p><h3>{{ row.title | inline_code }}</h3><p>{{ row.expectedBehaviorAfter | inline_code }}</p><p><strong>다음 단계:</strong> {{ row.recommendedNextPhase }}</p><p class="evidence-refs">근거: {{ row.evidence | evidence_refs(evidenceIndex) }}</p></article>{% else %}<p>선택할 개선 후보가 없습니다.</p>{% endfor %}</div>
16
16
  </section>
17
17
 
18
18
  <section data-report-section="impact-effort">
@@ -22,8 +22,8 @@
22
22
 
23
23
  <section data-report-section="selection">
24
24
  <h2>선택 안내</h2>
25
- {{ render_narrative(narrative.selectionGuidance, "improvementDiscovery.userNarrative.selectionGuidance") }}
25
+ {{ render_narrative(narrative.selectionGuidance, "improvementDiscovery.userNarrative.selectionGuidance", evidenceIndex) }}
26
26
  <p><strong>선택 상한:</strong> 최대 {{ improvement.selectionLimit }}개</p>
27
- <ul>{% for row in improvement.candidates %}<li><label><input type="checkbox" name="improvement-candidate" value="{{ row.id }}"> {{ row.id }} · {{ row.title }}</label></li>{% endfor %}</ul>
27
+ <ul>{% for row in improvement.candidates %}<li><label><input type="checkbox" name="improvement-candidate" value="{{ row.id }}"> {{ row.id }} · {{ row.title | inline_code }}</label></li>{% endfor %}</ul>
28
28
  </section>
29
29
  {% endblock %}
@@ -5,15 +5,15 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="project-overview">
7
7
  <h2>프로젝트 개요</h2>
8
- {{ render_narrative(narrative.projectOverview, "projectAnalysis.userNarrative.projectOverview") }}
8
+ {{ render_narrative(narrative.projectOverview, "projectAnalysis.userNarrative.projectOverview", evidenceIndex) }}
9
9
  <div class="summary-grid">
10
- {% for reason in humanSummary.whyItMatters %}{{ summary_card("왜 중요한가", reason, "important") }}{% endfor %}
10
+ {% for reason in humanSummary.whyItMatters %}{{ summary_card("왜 중요한가 " ~ loop.index, reason, "important") }}{% endfor %}
11
11
  </div>
12
12
  </section>
13
13
 
14
14
  <section data-report-section="architecture" data-report-field="projectAnalysis.components">
15
15
  <h2>아키텍처와 의존성</h2>
16
- {{ render_narrative(narrative.architectureExplanation, "projectAnalysis.userNarrative.architectureExplanation") }}
16
+ {{ render_narrative(narrative.architectureExplanation, "projectAnalysis.userNarrative.architectureExplanation", evidenceIndex) }}
17
17
  {{ figure(architectureFigure) }}
18
18
  </section>
19
19
 
@@ -23,35 +23,35 @@
23
23
  {% for row in project.entryPoints %}{{ summary_card(row.symbol, row.role) }}{% endfor %}
24
24
  </div>
25
25
  <h3>데이터 저장소</h3>
26
- <ul data-report-field="projectAnalysis.dataStores">{% for row in project.dataStores %}<li><strong>{{ row.name }}</strong> — {{ row.readBoundary }} / {{ row.writeBoundary }}</li>{% endfor %}</ul>
26
+ <ul data-report-field="projectAnalysis.dataStores">{% for row in project.dataStores %}<li><strong>{{ row.name | inline_code }}</strong> — {{ row.readBoundary | inline_code }} / {{ row.writeBoundary | inline_code }}</li>{% endfor %}</ul>
27
27
  <h3>외부 시스템</h3>
28
- <ul data-report-field="projectAnalysis.externalSystems">{% for row in project.externalSystems %}<li><strong>{{ row.name }}</strong> — {{ row.adapter }} ({{ row.direction }})</li>{% endfor %}</ul>
28
+ <ul data-report-field="projectAnalysis.externalSystems">{% for row in project.externalSystems %}<li><strong>{{ row.name | inline_code }}</strong> — {{ row.adapter | inline_code }} ({{ row.direction | inline_code }})</li>{% endfor %}</ul>
29
29
  </section>
30
30
 
31
31
  <section data-report-section="hotspots" data-report-field="projectAnalysis.rankedHotspots">
32
32
  <h2>우선 검토할 핫스팟</h2>
33
- {{ render_narrative(narrative.hotspotExplanation, "projectAnalysis.userNarrative.hotspotExplanation") }}
33
+ {{ render_narrative(narrative.hotspotExplanation, "projectAnalysis.userNarrative.hotspotExplanation", evidenceIndex) }}
34
34
  <div class="summary-grid">
35
35
  {% for row in project.rankedHotspots %}
36
- <article class="summary-card tone-{{ row.risk }}"><p class="eyebrow">{{ row.id }} · 영향 {{ row.impact }} · 위험 {{ row.risk }}</p><h3>{{ row.title }}</h3><p>{{ row.reason }}</p><p class="evidence-refs">근거: {{ row.evidenceRefs | join(', ') }}</p></article>
36
+ <article class="summary-card tone-{{ row.risk }}"><p class="eyebrow">{{ row.id }} · 영향 {{ row.impact }} · 위험 {{ row.risk }}</p><h3>{{ row.title | inline_code }}</h3><p>{{ row.reason | inline_code }}</p><p class="evidence-refs">근거: {{ row.evidenceRefs | evidence_refs(evidenceIndex) }}</p></article>
37
37
  {% endfor %}
38
38
  </div>
39
39
  </section>
40
40
 
41
41
  <section data-report-section="quality-and-tests" data-report-field="projectAnalysis.qualityCoverage">
42
42
  <h2>품질과 테스트 상태</h2>
43
- {{ render_narrative(narrative.qualityExplanation, "projectAnalysis.userNarrative.qualityExplanation") }}
44
- <table><thead><tr><th>영역</th><th>상태</th><th>설명</th><th>근거</th></tr></thead><tbody>{% for row in project.qualityCoverage %}<tr><td>{{ row.area }}</td><td><span class="status status-{{ row.status }}">{{ row.status }}</span></td><td>{{ row.summary }}</td><td>{{ row.evidenceRefs | join(', ') }}</td></tr>{% endfor %}</tbody></table>
43
+ {{ render_narrative(narrative.qualityExplanation, "projectAnalysis.userNarrative.qualityExplanation", evidenceIndex) }}
44
+ <table><thead><tr><th>영역</th><th>상태</th><th>설명</th><th>근거</th></tr></thead><tbody>{% for row in project.qualityCoverage %}<tr><td>{{ row.area | inline_code }}</td><td><span class="status status-{{ row.status }}">{{ row.status }}</span></td><td>{{ row.summary | inline_code }}</td><td>{{ row.evidenceRefs | evidence_refs(evidenceIndex) }}</td></tr>{% endfor %}</tbody></table>
45
45
  </section>
46
46
 
47
47
  <section data-report-section="feature-map" data-report-field="projectAnalysis.featureIndex">
48
48
  <h2>기능 지도</h2>
49
- <div class="summary-grid">{% for row in project.featureIndex %}{{ summary_card(row.id ~ " · " ~ row.name, row.description) }}{% endfor %}</div>
49
+ <div class="summary-grid">{% for row in project.featureIndex %}{{ summary_card(row.id ~ " · " ~ (row.name | inline_code), row.description) }}{% endfor %}</div>
50
50
  </section>
51
51
 
52
52
  <section data-report-section="user-actions">
53
53
  <h2>다음 행동</h2>
54
54
  {{ action_list(humanSummary.actions) }}
55
- {% if humanSummary.blockers %}<h3>차단 사항</h3><ul>{% for item in humanSummary.blockers %}<li>{{ item }}</li>{% endfor %}</ul>{% endif %}
55
+ {% if humanSummary.blockers %}<h3>차단 사항</h3><ul>{% for item in humanSummary.blockers %}<li>{{ item | inline_code }}</li>{% endfor %}</ul>{% endif %}
56
56
  </section>
57
57
  {% endblock %}
@@ -5,7 +5,7 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="handoff-outcome">
7
7
  <h2>인계 결과</h2>
8
- {{ render_narrative(narrative.handoffOutcome, "releaseHandoff.userNarrative.handoffOutcome") }}
8
+ {{ render_narrative(narrative.handoffOutcome, "releaseHandoff.userNarrative.handoffOutcome", evidenceIndex) }}
9
9
  {{ figure(handoffFigure) }}
10
10
  </section>
11
11
 
@@ -16,21 +16,21 @@
16
16
 
17
17
  <section data-report-section="branch-and-pr" data-report-field="releaseHandoff.featureBranchState">
18
18
  <h2>브랜치와 PR</h2>
19
- {{ render_narrative(narrative.branchAndPrExplanation, "releaseHandoff.userNarrative.branchAndPrExplanation") }}
19
+ {{ render_narrative(narrative.branchAndPrExplanation, "releaseHandoff.userNarrative.branchAndPrExplanation", evidenceIndex) }}
20
20
  <p><strong>브랜치:</strong> {{ handoff.featureBranchState.branchName }}</p>
21
21
  <p data-report-field="releaseHandoff.pullRequestOutcome"><strong>PR 결과:</strong> {{ handoff.pullRequestOutcome.kind }}{% if prUrl %} · <a href="{{ prUrl }}" rel="noopener noreferrer">Pull request 열기</a>{% endif %}</p>
22
22
  </section>
23
23
 
24
24
  <section data-report-section="conflicts" data-report-field="releaseHandoff.mergeConflictProbe">
25
25
  <h2>충돌 판정</h2>
26
- {{ render_narrative(narrative.conflictExplanation, "releaseHandoff.userNarrative.conflictExplanation") }}
26
+ {{ render_narrative(narrative.conflictExplanation, "releaseHandoff.userNarrative.conflictExplanation", evidenceIndex) }}
27
27
  {{ summary_card(handoff.mergeConflictProbe.kind, "기준 브랜치: " ~ (handoff.mergeConflictProbe.baseBranch | default('not-run'))) }}
28
28
  {% if handoff.mergeConflictProbe.conflictingPaths %}<ul>{% for path in handoff.mergeConflictProbe.conflictingPaths %}<li>{{ path }}</li>{% endfor %}</ul>{% endif %}
29
29
  </section>
30
30
 
31
31
  <section data-report-section="next-action">
32
32
  <h2>다음 행동</h2>
33
- {{ render_narrative(narrative.nextAction, "releaseHandoff.userNarrative.nextAction") }}
34
- <p>{{ handoff.routingRecommendation }}</p>
33
+ {{ render_narrative(narrative.nextAction, "releaseHandoff.userNarrative.nextAction", evidenceIndex) }}
34
+ <p>{{ handoff.routingRecommendation | inline_code }}</p>
35
35
  </section>
36
36
  {% endblock %}
@@ -5,26 +5,26 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="request">
7
7
  <h2>요청 이해</h2>
8
- {{ render_narrative(narrative.requestUnderstanding, "requirementsDiscovery.userNarrative.requestUnderstanding") }}
8
+ {{ render_narrative(narrative.requestUnderstanding, "requirementsDiscovery.userNarrative.requestUnderstanding", evidenceIndex) }}
9
9
  <h3>사용자 요청 원문</h3>
10
- <p data-report-field="requirementsDiscovery.requestVerbatim">{{ requirements.requestVerbatim }}</p>
10
+ <p data-report-field="requirementsDiscovery.requestVerbatim">{{ requirements.requestVerbatim | inline_code }}</p>
11
11
  <h3>시스템 해석</h3>
12
- <p data-report-field="requirementsDiscovery.systemInterpretation">{{ requirements.systemInterpretation }}</p>
13
- <dl data-report-field="requirementsDiscovery.classification">{% for key, value in requirements.classification.items() %}<dt>{{ key }}</dt><dd>{{ value }}</dd>{% endfor %}</dl>
12
+ <p data-report-field="requirementsDiscovery.systemInterpretation">{{ requirements.systemInterpretation | inline_code }}</p>
13
+ <dl data-report-field="requirementsDiscovery.classification">{% for key, value in requirements.classification.items() %}<dt>{{ key }}</dt><dd>{{ value | inline_code }}</dd>{% endfor %}</dl>
14
14
  </section>
15
15
 
16
16
  <section data-report-section="requirements">
17
17
  <h2>확정 요구사항과 미확정 요구사항</h2>
18
- {{ render_narrative(narrative.confirmedRequirements, "requirementsDiscovery.userNarrative.confirmedRequirements") }}
18
+ {{ render_narrative(narrative.confirmedRequirements, "requirementsDiscovery.userNarrative.confirmedRequirements", evidenceIndex) }}
19
19
  <div class="summary-grid" data-report-field="requirementsDiscovery.confirmedRequirements">{% for row in requirements.confirmedRequirements %}{{ summary_card(row.id ~ " · 확정", row.statement) }}{% endfor %}</div>
20
- {{ render_narrative(narrative.unresolvedRequirements, "requirementsDiscovery.userNarrative.unresolvedRequirements") }}
20
+ {{ render_narrative(narrative.unresolvedRequirements, "requirementsDiscovery.userNarrative.unresolvedRequirements", evidenceIndex) }}
21
21
  <div class="summary-grid" data-report-field="requirementsDiscovery.unresolvedRequirements">{% for row in requirements.unresolvedRequirements %}{{ summary_card(row.id ~ " · 결정 필요", row.statement, "important") }}{% else %}<p>미확정 요구사항이 없습니다.</p>{% endfor %}</div>
22
22
  </section>
23
23
 
24
24
  <section data-report-section="routing">
25
25
  <h2>다음 태스크와 의존성</h2>
26
- {{ render_narrative(narrative.routingExplanation, "requirementsDiscovery.userNarrative.routingExplanation") }}
27
- <p><strong>다음 태스크:</strong> {{ requirements.routing.nextTaskType }} · {{ requirements.routing.rationale }}</p>
26
+ {{ render_narrative(narrative.routingExplanation, "requirementsDiscovery.userNarrative.routingExplanation", evidenceIndex) }}
27
+ <p><strong>다음 태스크:</strong> {{ requirements.routing.nextTaskType }} · {{ requirements.routing.rationale | inline_code }}</p>
28
28
  {{ figure(routingFigure) }}
29
29
  {% if not requirements.taskDependencies %}<p class="empty-state">이 요구사항 태스크는 다른 태스크에 의존하지 않는 단일 작업입니다.</p>{% endif %}
30
30
  </section>
@@ -249,6 +249,16 @@
249
249
  setTimeout(function () { URL.revokeObjectURL(url); }, 0);
250
250
  }
251
251
 
252
+ // The output element lives in the footer, many screens below the header
253
+ // export button, so writing text into it alone reads as "nothing happened".
254
+ function showOutput(out, text) {
255
+ if (!out) return;
256
+ out.textContent = text;
257
+ out.setAttribute("tabindex", "-1");
258
+ out.scrollIntoView({ block: "center" });
259
+ out.focus();
260
+ }
261
+
252
262
  function exportUserResponse() {
253
263
  var runMeta = readRunMeta();
254
264
  var entries = collectEntries();
@@ -258,14 +268,14 @@
258
268
  try {
259
269
  analysisReview = collectAnalysisReview();
260
270
  } catch (e) {
261
- if (out) out.textContent = e.message || String(e);
271
+ showOutput(out, e.message || String(e));
262
272
  showDismiss(true);
263
273
  return "";
264
274
  }
265
275
  var md = buildUserResponseMarkdown(
266
276
  runMeta, entries, isoNowUtc(), approval, analysisReview
267
277
  );
268
- if (out) out.textContent = md;
278
+ showOutput(out, md);
269
279
  showDismiss(true);
270
280
  // Nothing answered yet — show the empty serialisation as feedback but
271
281
  // don't download a useless file.
@@ -315,7 +325,10 @@
315
325
  if (/Reader Summary|Verdict Card|Clarification Items|Recommended Next Steps|Follow-up Tasks/.test(text)) {
316
326
  return "action";
317
327
  }
318
- if (/Evidence|Cross Verification|Execution Status|Token Usage|Plan Body Verification|Round History/.test(text)) {
328
+ // "토큰 사용량" is tokenSummary.heading from templates/reports/i18n/ko.json —
329
+ // the only audit heading the ko dictionary translates away from its English
330
+ // form. tests/report/test_report_views.py pins the two in sync.
331
+ if (/Evidence|Cross Verification|Execution Status|Token Usage|토큰 사용량|Plan Body Verification|Round History/.test(text)) {
319
332
  return "audit";
320
333
  }
321
334
  return "detail";
@@ -333,7 +346,11 @@
333
346
  current = "action";
334
347
  continue;
335
348
  }
336
- if (/^H[12]$/.test(node.tagName || "")) {
349
+ // Only h2 opens a reader section. The single h1 is the report title, and
350
+ // the run-metadata list and table of contents that trail it are document
351
+ // chrome — classifying them would hide the title in action mode, because
352
+ // the title text ("… Cross Verification Final Report") reads as audit.
353
+ if ((node.tagName || "") === "H2") {
337
354
  current = readerSectionKind(node.textContent || "");
338
355
  }
339
356
  node.setAttribute("data-reader-section", current);
@@ -25,6 +25,10 @@
25
25
  "Bash(agy:*)",
26
26
  "Bash(claude)",
27
27
  "Bash(claude:*)",
28
+ "Bash(grok)",
29
+ "Bash(grok:*)",
30
+ "Bash(kimi)",
31
+ "Bash(kimi:*)",
28
32
  "Bash(docker)",
29
33
  "Bash(docker:*)"
30
34
  ]
@@ -131,8 +131,8 @@ taskType: "{{FM_TASK_TYPE}}"
131
131
  - Should the same task key continue to be used after this run?
132
132
  - What is the likely work category for this task right now?
133
133
  - Which lifecycle phase should probably happen next after this run?
134
- - What prior decision or report should Claude compare against?
135
- - What related tasks should Claude keep in mind but not merge with this task?
134
+ - What prior decision or report should the Okstra lead compare against?
135
+ - What related tasks should the Okstra lead keep in mind but not merge with this task?
136
136
  - Which config files and deployment manifests carry the expected state for this task?
137
137
 
138
138
  ## Phase Boundary
@@ -145,27 +145,27 @@ taskType: "{{FM_TASK_TYPE}}"
145
145
 
146
146
  ## Available MCP Servers
147
147
 
148
- The MCP servers available to this run are declared in `.okstra/project.json`'s `mcpServers` array and rendered into the Claude lead's launch prompt under `## Available MCP Servers`. They may be invoked **as needed** by Claude lead, Claude worker, and Report writer worker. The lead is responsible for forwarding the rendered list verbatim into the worker prompts (Phase 2) so workers know which tools they are allowed to call.
148
+ The MCP servers available to this run are declared in `.okstra/project.json`'s `mcpServers` array and rendered into the Okstra lead's launch prompt under `## Available MCP Servers`. The lead is responsible for forwarding the rendered list verbatim into worker prompts (Phase 2) so each assignment can determine whether its selected runner exposes the named tools.
149
149
 
150
150
  To declare servers, add entries shaped `{ "name": "<server>", "description": "...", "tools": ["..."], "notes": "..." }` to that array. If the array is empty or absent, treat MCP as unavailable for this run.
151
151
 
152
152
  How to invoke (worker-by-worker):
153
153
 
154
- - **Claude lead / Claude worker / Report writer worker**: invoke the MCP tool **directly by its tool name** (e.g. `mcp__<server>__<tool>`) through the host's tool interface. **Do NOT call it via `Bash`** — these names are MCP tools, not shell commands; running them in a shell will always fail with `command not found` regardless of permission settings.
155
- - **Codex worker / Antigravity worker**: invoke through the external CLI's own MCP transport (e.g. `codex mcp call <server> <tool> <args>` for Codex CLI; the equivalent Antigravity CLI MCP invocation for Antigravity). If the worker's CLI has no matching MCP config, treat the server as unavailable for this run and record `MCP not available in this CLI` in `Missing Information or Assumptions` — do **not** attempt a shell fallback such as `mysql -h ...` or piping a tool name into `bash`.
154
+ - **Native-session assignments**: invoke an MCP tool directly by its tool name (e.g. `mcp__<server>__<tool>`) only when the selected host adapter exposes it. Do not call an MCP tool name through a shell.
155
+ - **CLI-wrapper assignments**: use only the MCP or tool configuration explicitly supported and forwarded by that provider wrapper. If the wrapper has no matching MCP configuration, record `MCP not available in this CLI` in `Missing Information or Assumptions`; do not attempt a shell fallback.
156
156
  - All workers: cite the exact server, tool, and SELECT (or `WHERE` filters) used in the result file. Tool-call failures must be logged in the worker's `*-errors.json` (commandKind `mcp_call`) so the lead can decide whether to retry under a different worker.
157
157
 
158
158
  Usage policy:
159
159
 
160
160
  - **Allowed phases**: `requirements-discovery`, `error-analysis`, `implementation-planning`, `final-verification`. Use only when local schema/data evidence improves the answer. Always cite the server, table, and the SELECT used as evidence in worker output.
161
161
  - **`implementation` phase**: read-only MCP queries are permitted as cross-checks; MCP MUST NOT be used as a write path even if a write tool becomes available — schema/data mutations belong in the codebase migration files reviewed by humans.
162
- - **External CLI workers (Codex, Antigravity)**: can use these MCP servers ONLY if their CLI's own MCP configuration is set up to mirror the same servers. If not configured, the worker should record `MCP not available in this CLI` in `Missing Information or Assumptions` rather than guessing.
162
+ - **CLI-wrapper workers**: can use these MCP servers only if their provider CLI configuration mirrors the same servers. If not configured, the worker should record `MCP not available in this CLI` in `Missing Information or Assumptions` rather than guessing.
163
163
  - **Forbidden**: connecting to non-listed databases, running anything that mutates state (server is read-only — flagged write attempts are a contract violation), persisting query results outside the run's own artifact directories.
164
164
  - **Limits**: `maxRows: 1000` per query (server-enforced). For larger scans, page via `WHERE` predicates and document the strategy.
165
165
 
166
166
  If MCP evidence contradicts a claim sourced from documentation, code, or prior reports, flag the conflict explicitly in the final report and prefer the live MCP evidence unless the brief says otherwise.
167
167
 
168
- ## Notes for Main Claude
168
+ ## Notes for Okstra Lead
169
169
 
170
170
  - Which workers should be used?
171
171
  - Should all workers receive the same brief or different emphasis?
@@ -51,6 +51,7 @@ from okstra_ctl.conformance import ( # noqa: E402
51
51
  validate_conformance_manifest,
52
52
  )
53
53
  from okstra_ctl.paths import RunRef # noqa: E402
54
+ from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES # noqa: E402
54
55
  from okstra_ctl.build_tools import ( # noqa: E402
55
56
  command_invokes_build_tool,
56
57
  resolve_build_tool_tokens,
@@ -156,7 +157,7 @@ def _session_accounting(team_state: dict) -> str:
156
157
  return value
157
158
  return (
158
159
  "artifact-only"
159
- if team_state.get("leadRuntime") in {"codex", "external"}
160
+ if team_state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES
160
161
  else "claude-jsonl"
161
162
  )
162
163
 
@@ -415,20 +416,24 @@ def extract_contract(
415
416
  required_worker_roles = []
416
417
  failures.append("requiredWorkerRoles is missing from run/task manifest")
417
418
 
419
+ lead_role = (
420
+ run_contract.get("leadRole")
421
+ or task_contract.get("leadRole")
422
+ or "Okstra lead"
423
+ )
424
+
418
425
  required_agent_status_entries = run_contract.get("requiredAgentStatusEntries")
419
426
  if not isinstance(required_agent_status_entries, list):
420
427
  required_agent_status_entries = task_contract.get("requiredAgentStatusEntries")
421
428
  if not isinstance(required_agent_status_entries, list):
422
- required_agent_status_entries = ["Claude lead"] + [
429
+ required_agent_status_entries = [lead_role] + [
423
430
  item.get("role", "")
424
431
  for item in required_worker_roles
425
432
  if isinstance(item, dict) and item.get("role")
426
433
  ]
427
434
 
428
435
  return {
429
- "lead_role": run_contract.get("leadRole")
430
- or task_contract.get("leadRole")
431
- or "Claude lead",
436
+ "lead_role": lead_role,
432
437
  "lead_agent": run_contract.get("leadAgent")
433
438
  or task_contract.get("leadAgent")
434
439
  or "claude",
@@ -722,7 +727,7 @@ def validate_team_state(
722
727
  dispatch_mode = str(team_state.get("dispatchMode", "")).strip()
723
728
  if (
724
729
  dispatch_mode in WORKER_DISPATCH_MODES
725
- or team_state.get("leadRuntime") in {"codex", "external"}
730
+ or team_state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES
726
731
  ):
727
732
  if dispatch_mode not in WORKER_DISPATCH_MODES:
728
733
  expected = ", ".join(sorted(WORKER_DISPATCH_MODES))
@@ -38,6 +38,7 @@ from okstra_ctl.worker_heartbeat import ( # noqa: E402
38
38
  HEARTBEAT_LINE_RE,
39
39
  max_gap_seconds_after,
40
40
  )
41
+ from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES # noqa: E402
41
42
  from okstra_ctl.wrapper_status import read_wrapper_status # noqa: E402
42
43
 
43
44
  _DISPATCHED_STATUSES = {"completed", "timeout", "error", "in-progress"}
@@ -366,7 +367,7 @@ def _conformance_evidence_source(
366
367
  return None, f"unsupported sessionAccounting: `{accounting}`"
367
368
 
368
369
  legacy_runtime = str(team_state.get("leadRuntime", "") or "claude-code")
369
- if legacy_runtime in {"codex", "external"}:
370
+ if legacy_runtime in ARTIFACT_ONLY_LEAD_RUNTIMES:
370
371
  return "artifact-only", None
371
372
  return "claude-jsonl", None
372
373
 
@@ -415,18 +415,18 @@ export const COMMANDS = new Map(
415
415
  ]),
416
416
  );
417
417
 
418
- const PREAMBLE = `okstra — multi-agent cross-verification orchestrator for Claude Code
418
+ const PREAMBLE = `okstra — host-aware multi-provider cross-verification orchestrator
419
419
 
420
420
  This CLI is the installer/admin tool. Day-to-day usage happens inside a
421
- Claude Code session via slash commands (/okstra-setup, /okstra-run, ...).
421
+ supported agent host through installed skills (okstra-setup, okstra-run, ...).
422
422
 
423
423
  Quick start (CLI):
424
424
  1. npx -y okstra@latest install # one-time, this machine
425
425
  2. cd <your project>
426
426
  3. npx -y okstra@latest setup --project-id <id> # one-time, this project
427
- 4. open a Claude Code session and run /okstra-run # start a task
427
+ 4. open Claude Code, Codex, or Antigravity and run okstra-run # start a task
428
428
 
429
- Inside a Claude Code session you can replace step 3 with /okstra-setup —
429
+ Inside a supported agent host you can replace step 3 with okstra-setup —
430
430
  both write the same <PROJECT_ROOT>/.okstra/project.json.
431
431
 
432
432
  Usage:
@@ -5,18 +5,15 @@ const USAGE = `okstra codex-dispatch — dispatch CLI-backed workers for a prepa
5
5
 
6
6
  Usage:
7
7
  okstra codex-dispatch --project-root <dir> --run-manifest <path> \\
8
- [--workers codex[,antigravity[,report-writer]]] [--dry-run] \\
9
- [--idle-timeout-seconds <n>] \\
10
- [--enable-codex-report-writer --report-writer-codex-model <model>]
8
+ [--workers <cli-assigned-worker-ids>] [--dry-run] \\
9
+ [--idle-timeout-seconds <n>]
11
10
 
12
11
  This command reads an existing run manifest produced by 'okstra codex-run' and
13
- dispatches only Codex-side supported workers. When --workers is omitted, the
14
- command selects the supported subset of the run roster (codex/antigravity, plus
15
- report-writer only when explicitly opted in). Codex and antigravity use their
16
- existing wrappers. The report-writer role is available only through explicit
17
- Codex opt-in because the prepared manifest still records the Claude-side report
18
- writer model by default. Explicitly requesting unsupported workers such as
19
- claude fails.
12
+ dispatches only assignments whose persisted runner is 'cli-wrapper'. When
13
+ --workers is omitted, it selects that subset from the run roster. Each worker,
14
+ including report-writer, uses its persisted provider, model, and registered
15
+ wrapper. Native-session assignments remain owned by the current Codex host;
16
+ explicitly requesting one here fails.
20
17
 
21
18
  Missing worker prompt files are generated automatically from the run manifest
22
19
  and active-run-context. Existing prompt files are never overwritten.
@@ -2,7 +2,7 @@ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
  import { resolvePaths } from "../../lib/paths.mjs";
3
3
  import { resolveRuntime } from "../../lib/runtime-resolver.mjs";
4
4
 
5
- const USAGE = `okstra render-bundle — preview the task bundle without launching claude
5
+ const USAGE = `okstra render-bundle — preview the task bundle without launching a lead
6
6
 
7
7
  This is a thin shim over \`python3 -m okstra_ctl.run\` that forces
8
8
  \`--render-only\`. Use it to preview where prepare_task_bundle() would
@@ -18,7 +18,7 @@ Usage:
18
18
  [--analysis-target <text>] [--evidence-inputs <csv>] \\
19
19
  [--lead-model <m>] [--claude-model <m>] [--codex-model <m>] \\
20
20
  [--antigravity-model <m>] [--report-writer-model <m>] \\
21
- [--lead-runtime claude-code|codex] \\
21
+ [--lead-runtime claude-code|codex|antigravity|external] \\
22
22
  [--related-tasks <list>] [--base-ref <ref>] \\
23
23
  [--clarification-response <path>] [--work-category <cat>] \\
24
24
  [--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>] \\
@@ -26,7 +26,7 @@ Usage:
26
26
 
27
27
  --stage implementation / final-verification only
28
28
  --stages release-handoff only: PR stage bundle csv (empty = whole-task)
29
- --lead-runtime lead adapter marker; codex is render-only in this version
29
+ --lead-runtime host-native lead adapter marker
30
30
  --pr-template-path release-handoff only
31
31
  --fix-cycle entry phase only: record this re-entry of a done task as a
32
32
  bug-fix cycle (yes opens/continues the cycle)