okstra 0.172.0 → 0.174.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 (123) hide show
  1. package/README.md +8 -6
  2. package/docs/architecture/storage-model.md +24 -3
  3. package/docs/architecture.md +21 -35
  4. package/docs/cli.md +39 -7
  5. package/docs/container.md +1 -1
  6. package/docs/contributor-change-matrix.md +1 -1
  7. package/docs/performance-improvement-plan-v2.md +6 -5
  8. package/docs/project-structure-overview.md +33 -25
  9. package/docs/task-process/README.md +6 -4
  10. package/docs/task-process/error-analysis.md +2 -2
  11. package/docs/task-process/final-verification.md +2 -2
  12. package/docs/task-process/implementation-option-selection.md +70 -0
  13. package/docs/task-process/implementation-planning.md +24 -16
  14. package/docs/task-process/requirements-discovery.md +2 -2
  15. package/package.json +1 -1
  16. package/runtime/BUILD.json +2 -2
  17. package/runtime/agents/workers/claude-worker.md +1 -1
  18. package/runtime/agents/workers/report-writer-worker.md +30 -6
  19. package/runtime/bin/lib/okstra/cli.sh +5 -1
  20. package/runtime/bin/lib/okstra/globals.sh +2 -1
  21. package/runtime/bin/lib/okstra/usage.sh +3 -0
  22. package/runtime/bin/okstra-provider-exec.py +29 -12
  23. package/runtime/bin/okstra-trace-cleanup.sh +58 -129
  24. package/runtime/bin/okstra.sh +2 -0
  25. package/runtime/prompts/duties/direction-selection-worker.md +44 -0
  26. package/runtime/prompts/duties/planning-worker.md +12 -4
  27. package/runtime/prompts/lead/adapters/cmux.md +2 -0
  28. package/runtime/prompts/lead/context-loader.md +1 -1
  29. package/runtime/prompts/lead/convergence.md +5 -5
  30. package/runtime/prompts/lead/okstra-lead-contract.md +7 -6
  31. package/runtime/prompts/lead/plan-body-verification.md +23 -6
  32. package/runtime/prompts/lead/report-writer.md +33 -11
  33. package/runtime/prompts/profiles/_common-contract.md +3 -3
  34. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  35. package/runtime/prompts/profiles/_implementation-executor.md +2 -0
  36. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  37. package/runtime/prompts/profiles/error-analysis.md +4 -4
  38. package/runtime/prompts/profiles/final-verification.md +3 -3
  39. package/runtime/prompts/profiles/forbidden-actions.json +7 -0
  40. package/runtime/prompts/profiles/implementation-option-selection.md +35 -0
  41. package/runtime/prompts/profiles/implementation-planning.md +61 -46
  42. package/runtime/prompts/profiles/implementation.md +4 -2
  43. package/runtime/prompts/profiles/improvement-discovery.md +1 -1
  44. package/runtime/prompts/profiles/release-handoff.md +1 -1
  45. package/runtime/prompts/profiles/requirements-discovery.md +3 -3
  46. package/runtime/prompts/wizard/prompts.ko.json +9 -1
  47. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +1 -6
  48. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +4 -4
  49. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +5 -0
  50. package/runtime/python/okstra_ctl/agent_invocation.py +1 -0
  51. package/runtime/python/okstra_ctl/analysis_packet.py +6 -0
  52. package/runtime/python/okstra_ctl/conformance.py +68 -0
  53. package/runtime/python/okstra_ctl/dispatch_core.py +89 -39
  54. package/runtime/python/okstra_ctl/dispatch_state.py +142 -14
  55. package/runtime/python/okstra_ctl/doctor.py +2 -2
  56. package/runtime/python/okstra_ctl/domain/worker_exec.py +5 -0
  57. package/runtime/python/okstra_ctl/exact_coverage.py +128 -0
  58. package/runtime/python/okstra_ctl/final_report_schema.py +5 -4
  59. package/runtime/python/okstra_ctl/fix_cycles.py +3 -1
  60. package/runtime/python/okstra_ctl/implementation_direction.py +836 -0
  61. package/runtime/python/okstra_ctl/implementation_options.py +479 -0
  62. package/runtime/python/okstra_ctl/pane_reclaim.py +13 -22
  63. package/runtime/python/okstra_ctl/plan_items.py +51 -3
  64. package/runtime/python/okstra_ctl/render.py +1 -0
  65. package/runtime/python/okstra_ctl/render_final_report.py +16 -19
  66. package/runtime/python/okstra_ctl/report_contract.py +45 -14
  67. package/runtime/python/okstra_ctl/report_finalize.py +68 -9
  68. package/runtime/python/okstra_ctl/report_html/render.py +4 -2
  69. package/runtime/python/okstra_ctl/report_html/router.py +4 -0
  70. package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +32 -0
  71. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +25 -10
  72. package/runtime/python/okstra_ctl/report_views.py +148 -12
  73. package/runtime/python/okstra_ctl/run.py +393 -4
  74. package/runtime/python/okstra_ctl/schema_excerpt.py +1 -1
  75. package/runtime/python/okstra_ctl/scope_provenance.py +16 -10
  76. package/runtime/python/okstra_ctl/session.py +69 -12
  77. package/runtime/python/okstra_ctl/team.py +51 -25
  78. package/runtime/python/okstra_ctl/tmux.py +19 -149
  79. package/runtime/python/okstra_ctl/user_response.py +75 -0
  80. package/runtime/python/okstra_ctl/wizard.py +144 -0
  81. package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
  82. package/runtime/python/okstra_ctl/worker_request.py +2 -0
  83. package/runtime/python/okstra_ctl/workflow.py +29 -7
  84. package/runtime/python/okstra_ctl/worktree.py +69 -3
  85. package/runtime/python/okstra_token_usage/cli.py +1 -1
  86. package/runtime/python/okstra_token_usage/collect.py +66 -6
  87. package/runtime/schemas/final-report-v2.0.schema.json +1428 -137
  88. package/runtime/skills/okstra-setup/references/project-config.md +11 -0
  89. package/runtime/templates/reports/final-report-v2.template.md +4 -0
  90. package/runtime/templates/reports/final-verification-input.template.md +1 -1
  91. package/runtime/templates/reports/html/base.template.html +3 -2
  92. package/runtime/templates/reports/html/i18n/en.json +21 -1
  93. package/runtime/templates/reports/html/i18n/ko.json +21 -1
  94. package/runtime/templates/reports/html/macros/forms.html +21 -2
  95. package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +49 -0
  96. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +36 -2
  97. package/runtime/templates/reports/i18n/en.json +13 -0
  98. package/runtime/templates/reports/implementation-input.template.md +4 -2
  99. package/runtime/templates/reports/implementation-planning-input.template.md +18 -4
  100. package/runtime/templates/reports/improvement-discovery-input.template.md +1 -1
  101. package/runtime/templates/reports/md/tasks/implementation-option-selection.template.md +13 -0
  102. package/runtime/templates/reports/md/tasks/implementation-planning.template.md +17 -0
  103. package/runtime/templates/reports/report.js +111 -4
  104. package/runtime/templates/reports/settings.template.json +0 -24
  105. package/runtime/templates/reports/task-brief.template.md +9 -3
  106. package/runtime/templates/reports/user-response.template.md +25 -4
  107. package/runtime/templates/worker-prompt-preamble.md +8 -0
  108. package/runtime/validators/lib/fixtures.sh +49 -17
  109. package/runtime/validators/validate-implementation-plan-stages.py +169 -4
  110. package/runtime/validators/validate-report-views.py +2 -2
  111. package/runtime/validators/validate-run.py +149 -498
  112. package/runtime/validators/validate_improvement_report.py +5 -1
  113. package/runtime/validators/validate_session_conformance.py +1 -1
  114. package/src/cli-registry.mjs +8 -1
  115. package/src/commands/execute/codex-run.mjs +1 -0
  116. package/src/commands/execute/render-bundle.mjs +1 -0
  117. package/src/commands/execute/team.mjs +3 -3
  118. package/src/commands/execute/worktree-status.mjs +109 -0
  119. package/src/commands/lifecycle/install.mjs +0 -2
  120. package/src/commands/report/finalize.mjs +13 -6
  121. package/runtime/bin/okstra-subagent-reclaim.sh +0 -26
  122. package/runtime/schemas/final-report-v1.0.schema.json +0 -6366
  123. package/runtime/templates/reports/final-report.template.md +0 -1258
@@ -16,6 +16,17 @@ shared state. The built-in default is `.project-docs`, `.scratch`,
16
16
  okstra-owned context and writes still stay under `<PROJECT_ROOT>/.okstra/**`
17
17
  unless the task brief explicitly authorizes a non-okstra path.
18
18
 
19
+ `.claude` is the one entry materialised as a real directory whose children are
20
+ symlinked one by one, instead of a single symlink for the whole directory. git
21
+ does not follow a symlink, so a symlinked directory is one file to it, and a
22
+ project that ignores host config by its contents (`.claude/*`) matches every
23
+ child but never the bare `.claude` path — the directory would be invisible in
24
+ the main checkout and `?? .claude` in the task worktree. Linking the children
25
+ reproduces the main checkout's shape, so the project's own ignore rules decide
26
+ the outcome in both places. The other entries stay whole-directory symlinks
27
+ because okstra writes into them, and the directory symlink is what makes a
28
+ newly created top-level entry land in the main checkout.
29
+
19
30
  To override per-project, add a `worktreeSyncDirs` array to `project.json`.
20
31
  Empty array disables the feature; the field is preserved across the runtime's
21
32
  auto-upserts (only `projectId`, `projectRoot`, `createdAt`, `updatedAt` are
@@ -11,7 +11,11 @@ project-id: {{ frontmatter.projectId | yaml_scalar }}
11
11
  task-type: {{ frontmatter.taskType | yaml_scalar }}
12
12
  worker-id: {{ frontmatter.workerId | yaml_scalar }}
13
13
  approved: {{ "true" if frontmatter.approved else "false" }}
14
+ {% if implementationPlanning is defined and implementationPlanning.get("planningContract") == "selected-direction" -%}
15
+ selected-direction-ref: {{ frontmatter.selectedDirectionRef | yaml_scalar }}
16
+ {% else -%}
14
17
  implementation-option: {{ frontmatter.implementationOption | default("") | yaml_scalar }}
18
+ {% endif -%}
15
19
  schema-version: {{ schemaVersion | yaml_scalar }}
16
20
  ---
17
21
  {#
@@ -40,7 +40,7 @@ taskType: "{{FM_TASK_TYPE}}"
40
40
  - Path (project-relative) to the originating `implementation` final-report:
41
41
  - Quoted `Commit list` / `Diff summary` excerpt from the implementation report:
42
42
 
43
- > If the report path is empty or points to a missing report, final-verification ends with status `blocked` and routes to `implementation` or `implementation-planning`. The verification target (worktree/base) is resolved automatically by okstra, so a mismatched manual entry cannot cause a block.
43
+ > If the report path is empty or points to a missing report, final-verification ends with status `blocked`. Cause defects route to `error-analysis`, direction defects route to `implementation-option-selection`, and detailed-plan defects route to `implementation-planning`. The verification target (worktree/base) is resolved automatically by okstra, so a mismatched manual entry cannot cause a block.
44
44
 
45
45
  ## Requirement Coverage Source
46
46
 
@@ -87,18 +87,19 @@
87
87
  </section>
88
88
  {% endif %}
89
89
  </main>
90
- <footer class="human-report-footer">
90
+ {% if approval is not defined or not (approval and approval.reentry_command) %}<footer class="human-report-footer">
91
91
  <button type="button" data-action="export-user-response">{{ t('base.export-my-answers') }}</button>
92
92
  <button type="button" data-action="copy-user-response">{{ t('base.copy') }}</button>
93
93
  <button type="button" data-action="dismiss-user-response" hidden>{{ t('base.dismiss') }}</button>
94
94
  <p class="user-response-hint">{{ t('base.export-downloads') }} <code>user-response-{{ runMeta.task_type }}-{{ runMeta.seq }}.md</code>{{ t('base.drop-that-file-into') }} <code>runs/{{ runMeta.task_type }}/user-responses/</code> {{ t('base.and-the-next-run-picks-your-answers-up-on-it') }}</p>
95
95
  <pre id="user-response-output" aria-live="polite"></pre>
96
- </footer>
96
+ </footer>{% endif %}
97
97
  <script id="run-meta" type="application/json">{{ {
98
98
  "task-key": runMeta.task_key,
99
99
  "task-type": runMeta.task_type,
100
100
  "seq": runMeta.seq,
101
101
  "source-report": runMeta.source_report,
102
+ "source-data": sourceData,
102
103
  "source-data-sha256": dataSha256,
103
104
  "source-md-sha256": markdownSha256
104
105
  } | tojson }}</script>
@@ -159,7 +159,9 @@
159
159
  "request-changes-to-this-plan": "Send it back for changes",
160
160
  "reject-this-plan": "Reject this plan",
161
161
  "why-required-for-changes-or-rejection": "Why — required when you send it back or reject it",
162
- "answer-the-blockers-then-regenerate": "Answer {ids}, export your answers, then regenerate the report with the clarification resume command."
162
+ "answer-the-blockers-then-regenerate": "Answer {ids}, export your answers, then regenerate the report with the clarification resume command.",
163
+ "selected-direction-invalidated": "Selected direction invalidated",
164
+ "return-to-option-selection": "This plan cannot be approved. Re-enter implementation option selection with:"
163
165
  },
164
166
  "visualizations": {
165
167
  "component": "Component",
@@ -250,6 +252,24 @@
250
252
  "release-path": "Release path",
251
253
  "the-current-verdict-does-not-allow-release-h": "The current verdict does not allow release-handoff to start. Clear the blocker or the condition first."
252
254
  },
255
+ "implementation-option-selection": {
256
+ "original-requirements": "Original requirement coverage",
257
+ "requirement-ids": "Requirement IDs:",
258
+ "leading-causes": "Leading causes:",
259
+ "ranked-options": "Ranked implementation directions",
260
+ "weighted-score": "Weighted score:",
261
+ "requirement": "Requirement",
262
+ "satisfaction": "How it is satisfied",
263
+ "verification": "Expected verification",
264
+ "no-valid-options": "No valid implementation direction is available.",
265
+ "recommended-direction": "Recommended direction",
266
+ "no-recommendation": "No direction can be recommended.",
267
+ "candidate-audit": "Candidate audit",
268
+ "candidate": "Candidate",
269
+ "disposition": "Disposition",
270
+ "reason": "Reason",
271
+ "no-audit-entries": "No candidate was merged or rejected."
272
+ },
253
273
  "implementation-planning": {
254
274
  "what-the-plan-is-for": "What the plan is for",
255
275
  "implementation-options-compared": "Implementation options compared",
@@ -159,7 +159,9 @@
159
159
  "request-changes-to-this-plan": "고쳐서 다시 가져오게 합니다",
160
160
  "reject-this-plan": "이 계획을 반려합니다",
161
161
  "why-required-for-changes-or-rejection": "사유 — 반려하거나 다시 고치게 할 때는 반드시 적어야 합니다",
162
- "answer-the-blockers-then-regenerate": "{ids}에 답한 뒤 답변을 내보내고, clarification resume 명령으로 리포트를 다시 만드세요."
162
+ "answer-the-blockers-then-regenerate": "{ids}에 답한 뒤 답변을 내보내고, clarification resume 명령으로 리포트를 다시 만드세요.",
163
+ "selected-direction-invalidated": "선택 방향 무효화",
164
+ "return-to-option-selection": "이 계획은 승인할 수 없습니다. 다음 명령으로 구현 방향 선택 단계에 다시 진입하세요:"
163
165
  },
164
166
  "visualizations": {
165
167
  "component": "구성 요소",
@@ -250,6 +252,24 @@
250
252
  "release-path": "릴리스 경로",
251
253
  "the-current-verdict-does-not-allow-release-h": "현재 판정으로는 release-handoff 를 시작할 수 없습니다. 차단 항목이나 조건을 먼저 해소하세요."
252
254
  },
255
+ "implementation-option-selection": {
256
+ "original-requirements": "원본 요구사항 커버리지",
257
+ "requirement-ids": "요구사항 ID:",
258
+ "leading-causes": "주요 원인:",
259
+ "ranked-options": "구현 방향 순위",
260
+ "weighted-score": "가중 점수:",
261
+ "requirement": "요구사항",
262
+ "satisfaction": "충족 방식",
263
+ "verification": "예상 검증",
264
+ "no-valid-options": "유효한 구현 방향이 없습니다.",
265
+ "recommended-direction": "권장 방향",
266
+ "no-recommendation": "권장할 수 있는 방향이 없습니다.",
267
+ "candidate-audit": "후보 감사 기록",
268
+ "candidate": "후보",
269
+ "disposition": "처리",
270
+ "reason": "이유",
271
+ "no-audit-entries": "병합되거나 탈락한 후보가 없습니다."
272
+ },
253
273
  "implementation-planning": {
254
274
  "what-the-plan-is-for": "이 계획이 하려는 것",
255
275
  "implementation-options-compared": "구현 방식 비교",
@@ -18,21 +18,40 @@
18
18
 
19
19
  {% macro plan_approval(state) -%}
20
20
  <section id="plan-approval" data-report-section="plan-approval">
21
+ {% if state.reentry_command %}
22
+ <h2>{{ t('macros.forms.selected-direction-invalidated') }}</h2>
23
+ <p class="approval-disabled-reason">{{ t('macros.forms.return-to-option-selection') }} <code>{{ state.reentry_command }}</code></p>
24
+ {% else %}
21
25
  <h2>{{ t('macros.forms.decide-on-this-plan') }}</h2>
22
26
  <fieldset id="plan-decision"><legend>{{ t('macros.forms.verdict') }}</legend>
23
27
  <input id="plan-decision-approved" type="radio" name="plan-decision-status" value="approved"{% if state.disabled_reason %} disabled{% endif %}><label for="plan-decision-approved">{{ t('macros.forms.i-approve-this-plan') }}</label>
24
28
  <input id="plan-decision-revision-requested" type="radio" name="plan-decision-status" value="revision-requested"><label for="plan-decision-revision-requested">{{ t('macros.forms.request-changes-to-this-plan') }}</label>
25
29
  <input id="plan-decision-rejected" type="radio" name="plan-decision-status" value="rejected"><label for="plan-decision-rejected">{{ t('macros.forms.reject-this-plan') }}</label>
26
30
  </fieldset>
27
- <label for="approval-option">{{ t('macros.forms.implementation-option') }}</label>
31
+ {% if state.show_option_selector %}<label for="approval-option">{{ t('macros.forms.implementation-option') }}</label>
28
32
  <select id="approval-option"{% if state.disabled_reason %} disabled{% endif %}>
29
33
  {% for name in state.option_names %}
30
34
  {% if name == state.recommended_option %}<option data-recommended="true" selected value="{{ name }}">{{ name | inline_code }} (recommended)</option>{% else %}<option value="{{ name }}">{{ name | inline_code }}</option>{% endif %}
31
35
  {% endfor %}
32
- </select>
36
+ </select>{% endif %}
33
37
  <label for="plan-decision-reason">{{ t('macros.forms.why-required-for-changes-or-rejection') }}</label>
34
38
  <textarea id="plan-decision-reason" rows="4"></textarea>
35
39
  {% if state.disabled_reason %}<div class="approval-disabled-reason"><p>{{ t('macros.forms.approval-is-held') }} <strong>{{ state.disabled_reason }}</strong>.</p><p>{{ t('macros.forms.answer-the-blockers-then-regenerate') | replace('{ids}', state.blocker_ids | join(', ')) }}</p></div>{% endif %}
40
+ {% endif %}
41
+ </section>
42
+ {%- endmacro %}
43
+
44
+ {% macro direction_selection(state) -%}
45
+ <section id="direction-selection" data-report-section="direction-selection">
46
+ <h2>Select an implementation direction</h2>
47
+ <fieldset><legend>Direction</legend>
48
+ {% for option in state.rankedOptions %}
49
+ <input id="direction-selection-{{ option.id }}" type="radio" name="direction-selection-option" value="{{ option.id }}" data-option-name="{{ option.name }}" required><label for="direction-selection-{{ option.id }}">{{ option.id }} · {{ option.name | inline_code }}{% if option.id == state.recommendedOptionId %} (recommended){% endif %}</label>
50
+ {% endfor %}
51
+ </fieldset>
52
+ <input id="direction-selection-confirmed" type="checkbox" required><label for="direction-selection-confirmed">Confirmed</label>
53
+ <label for="direction-selection-note">Selection note</label><textarea id="direction-selection-note" rows="3"></textarea>
54
+ <label for="direction-selection-constraints">Constraints</label><textarea id="direction-selection-constraints" rows="3"></textarea>
36
55
  </section>
37
56
  {%- endmacro %}
38
57
 
@@ -0,0 +1,49 @@
1
+ {% extends "html/base.template.html" %}
2
+ {% from "html/macros/layout.html" import narrative as render_narrative, summary_card, row_key %}
3
+ {% from "html/macros/forms.html" import direction_selection %}
4
+
5
+ {% block human_content %}
6
+ <section data-report-section="selection-status">
7
+ <h2>Selection status</h2>
8
+ <dl><dt>Mode</dt><dd>{{ selection.mode }}</dd><dt>Routing</dt><dd>{{ selection.routing }}</dd></dl>
9
+ {{ render_narrative(narrative.selectionGuidance, "implementationOptionSelection.userNarrative.selectionGuidance") }}
10
+ {% if selection.mode == "preselected-validation" %}
11
+ <h3>Preselected direction</h3>
12
+ <dl><dt>Option ID</dt><dd>{{ selection.preselectedDirection.optionId }}</dd><dt>Direction</dt><dd>{{ selection.preselectedDirection.direction | inline_code }}</dd><dt>Confirmation evidence</dt><dd>{{ selection.preselectedDirection.confirmationEvidence | inline_code }}</dd><dt>Citation</dt><dd>{{ selection.preselectedDirection.citation | inline_code }}</dd></dl>
13
+ {% endif %}
14
+ </section>
15
+
16
+ {% if directionSelection %}{{ direction_selection(directionSelection) }}{% endif %}
17
+
18
+ <section data-report-section="decision-context">
19
+ <h2>{{ t('tasks.implementation-option-selection.original-requirements') }}</h2>
20
+ <p>{{ humanSummary.outcome | inline_code }}</p>
21
+ {{ render_narrative(narrative.comparisonOverview, "implementationOptionSelection.userNarrative.comparisonOverview") }}
22
+ <p data-report-field="implementationOptionSelection.decisionContext.originalRequirementIds"><strong>{{ t('tasks.implementation-option-selection.requirement-ids') }}</strong> {{ selection.decisionContext.originalRequirementIds | join(", ") | inline_code }}</p>
23
+ {% if selection.decisionContext.leadingCauseRefs %}<p><strong>{{ t('tasks.implementation-option-selection.leading-causes') }}</strong> {{ selection.decisionContext.leadingCauseRefs | join(", ") | inline_code }}</p>{% endif %}
24
+ </section>
25
+
26
+ <section data-report-section="ranked-options" data-report-field="implementationOptionSelection.rankedOptions">
27
+ <h2>{{ t('tasks.implementation-option-selection.ranked-options') }}</h2>
28
+ {{ render_narrative(narrative.rankingExplanation, "implementationOptionSelection.userNarrative.rankingExplanation") }}
29
+ <div class="summary-grid">{% for row in selection.rankedOptions %}<article class="summary-card" id="id-{{ row.id }}">
30
+ <p class="eyebrow">#{{ loop.index }} · {{ row.id }}</p>
31
+ <h3>{{ row.name | inline_code }}</h3>
32
+ <p>{{ row.goal | inline_code }}</p>
33
+ <div data-report-field="implementationOptionSelection.rankedOptions.coverageSummary"><p><strong>coveragePercent</strong> <span data-report-metric="coveragePercent">{{ row.coverageSummary.coveragePercent }}</span></p>
34
+ <p><strong>scopePrecisionPercent</strong> <span data-report-metric="scopePrecisionPercent">{{ row.coverageSummary.scopePrecisionPercent }}</span></p></div>
35
+ <p><strong>{{ t('tasks.implementation-option-selection.weighted-score') }}</strong> {{ row.weightedScore }}</p>
36
+ <table data-report-field="implementationOptionSelection.rankedOptions.requirementCoverage"><thead><tr><th>{{ t('tasks.implementation-option-selection.requirement') }}</th><th>{{ t('tasks.implementation-option-selection.satisfaction') }}</th><th>{{ t('tasks.implementation-option-selection.verification') }}</th></tr></thead><tbody>{% for coverage in row.requirementCoverage %}<tr>{{ row_key(pairs=[("ID", coverage.requirementId), ("Status", coverage.status)]) }}<td>{{ coverage.satisfaction | inline_code }}</td><td>{{ coverage.expectedVerification | inline_code }}</td></tr>{% endfor %}</tbody></table>
37
+ </article>{% else %}<p>{{ t('tasks.implementation-option-selection.no-valid-options') }}</p>{% endfor %}</div>
38
+ </section>
39
+
40
+ <section data-report-section="recommendation" data-report-field="implementationOptionSelection.recommendedOptionId">
41
+ <h2>{{ t('tasks.implementation-option-selection.recommended-direction') }}</h2>
42
+ {% if recommendedOption %}{{ summary_card(recommendedOption.id ~ " · " ~ recommendedOption.name, recommendedOption.coreMechanism, "important") }}{% else %}<p>{{ t('tasks.implementation-option-selection.no-recommendation') }}</p>{% endif %}
43
+ </section>
44
+
45
+ <section data-report-section="candidate-audit" data-report-field="implementationOptionSelection.candidateAudit">
46
+ <h2>{{ t('tasks.implementation-option-selection.candidate-audit') }}</h2>
47
+ <table><thead><tr><th>{{ t('tasks.implementation-option-selection.candidate') }}</th><th>{{ t('tasks.implementation-option-selection.disposition') }}</th><th>{{ t('tasks.implementation-option-selection.reason') }}</th><th>coveragePercent</th><th>scopePrecisionPercent</th></tr></thead><tbody>{% for row in selection.candidateAudit %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[("ID", row.id), ("Proposed by", row.proposedBy)]) }}<td>{{ row.disposition }}</td><td>{{ row.reason | inline_code }}</td><td>{{ row.coverageSummary.coveragePercent }}</td><td>{{ row.coverageSummary.scopePrecisionPercent }}</td></tr>{% else %}<tr><td colspan="6">{{ t('tasks.implementation-option-selection.no-audit-entries') }}</td></tr>{% endfor %}</tbody></table>
48
+ </section>
49
+ {% endblock %}
@@ -4,12 +4,37 @@
4
4
  {% from "html/macros/visualizations.html" import figure %}
5
5
 
6
6
  {% block human_content %}
7
- <section data-report-section="planning-goal">
7
+ <section data-report-section="planning-goal" data-report-field="implementationPlanning">
8
8
  <h2>{{ t('tasks.implementation-planning.what-the-plan-is-for') }}</h2>
9
9
  <p>{{ humanSummary.outcome | inline_code }}</p>
10
10
  <div class="summary-grid">{% for reason in humanSummary.whyItMatters %}{{ summary_card("", reason, "important") }}{% endfor %}</div>
11
11
  </section>
12
12
 
13
+ {% if planning.get("planningContract") == "selected-direction" %}
14
+ <section data-report-section="selected-direction" data-report-field="implementationPlanning.selectedDirectionRef">
15
+ <h2>Selected direction</h2>
16
+ <article class="summary-card tone-important"><h3>{{ planning.selectedDirectionRef.optionId | inline_code }}</h3><p><strong>Source report</strong> {{ planning.selectedDirectionRef.sourceReport | inline_code }}</p><p><strong>Snapshot</strong> {{ planning.selectedDirectionRef.snapshotPath | inline_code }}</p></article>
17
+ </section>
18
+ {% if planning.outcome == "plan-ready" %}
19
+ <section data-report-section="direction-realization" data-report-field="implementationPlanning.directionRealization">
20
+ <h2>Direction realization</h2>
21
+ <article class="summary-card"><h3>{{ planning.directionRealization.goal | inline_code }}</h3><p><strong>Core mechanism</strong> {{ planning.directionRealization.coreMechanism | inline_code }}</p><p><strong>Interfaces</strong> {{ planning.directionRealization.interfaces | inline_code }}</p><p><strong>Blast radius</strong> {{ planning.directionRealization.blastRadius | inline_code }}</p><ul>{% for file in planning.directionRealization.fileStructure %}<li id="id-{{ file.id }}">{{ file.action }} <code>{{ file.path }}</code> — {{ file.summary | inline_code }}</li>{% endfor %}</ul></article>
22
+ </section>
23
+ <section data-report-section="plan-coverage" data-report-field="implementationPlanning.coverageSummary">
24
+ <h2>Plan coverage</h2>
25
+ <p><strong>coveragePercent</strong> <span data-report-metric="coveragePercent">{{ planning.coverageSummary.coveragePercent }}</span></p>
26
+ <p><strong>scopePrecisionPercent</strong> <span data-report-metric="scopePrecisionPercent">{{ planning.coverageSummary.scopePrecisionPercent }}</span></p>
27
+ </section>
28
+ {% else %}
29
+ <section data-report-section="direction-invalidation" data-report-field="implementationPlanning.directionInvalidation">
30
+ <h2>Direction invalidated</h2>
31
+ <ul>{% for reason in planning.directionInvalidation.reasons %}<li>{{ reason | inline_code }}</li>{% endfor %}</ul>
32
+ <p><strong>Code evidence</strong></p>
33
+ <ul>{% for evidence in planning.directionInvalidation.codeEvidence %}<li>{{ evidence | inline_code }}</li>{% endfor %}</ul>
34
+ <p data-report-field="implementationPlanning.routing"><strong>Re-entry route</strong> {{ planning.routing | inline_code }}</p>
35
+ </section>
36
+ {% endif %}
37
+ {% else %}
13
38
  <section data-report-section="option-comparison">
14
39
  <h2>{{ t('tasks.implementation-planning.implementation-options-compared') }}</h2>
15
40
  {{ render_narrative(narrative.optionExplanation, "implementationPlanning.userNarrative.optionExplanation") }}
@@ -22,10 +47,14 @@
22
47
  {{ render_narrative(narrative.recommendationExplanation, "implementationPlanning.userNarrative.recommendationExplanation") }}
23
48
  <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>{{ t('tasks.implementation-planning.options-not-taken') }}</strong> {{ planning.recommendedOption.rejectedSummary | inline_code }}</p></article>
24
49
  </section>
50
+ {% endif %}
25
51
 
52
+ {% if planning.get("outcome") != "direction-invalidated" %}
26
53
  <section data-report-section="stage-map" data-report-field="implementationPlanning.stageMap">
27
54
  <h2>{{ t('tasks.implementation-planning.stage-map') }}</h2>
55
+ {% if narrative.get("stageStrategy") %}
28
56
  {{ render_narrative(narrative.stageStrategy, "implementationPlanning.userNarrative.stageStrategy") }}
57
+ {% endif %}
29
58
  {{ figure(stageFigure) }}
30
59
  <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>{{ t('tasks.implementation-planning.exit-contract') }}</strong> {{ row.acceptance | inline_code }}</p><p><strong>{{ t('tasks.implementation-planning.verification') }}</strong> {{ row.stageValidation | inline_code }}</p></article>{% endfor %}</div>
31
60
  </section>
@@ -64,7 +93,11 @@
64
93
  actually do what the brief asked" is the approval question itself. #}
65
94
  <section data-report-section="requirement-coverage" data-report-field="implementationPlanning.requirementCoverage">
66
95
  <h2>{{ t('tasks.implementation-planning.how-each-requirement-gets-met') }}</h2>
96
+ {% if planning.get("planningContract") == "selected-direction" %}
97
+ <table><thead><tr><th>Original requirement</th><th>Stages</th><th>Status</th></tr></thead><tbody>{% for row in planning.requirementCoverage %}<tr><td>{{ row.originalRequirementId | inline_code }}</td><td>{{ row.stageRefs | join(", ") | inline_code }}</td><td>{{ row.status | inline_code }}</td></tr>{% endfor %}</tbody></table>
98
+ {% else %}
67
99
  <table><thead><tr><th>{{ t('tasks.implementation-planning.requirement') }}</th><th>{{ t('tasks.implementation-planning.body') }}</th><th>{{ t('tasks.implementation-planning.covered-by') }}</th></tr></thead><tbody>{% for row in planning.requirementCoverage %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[("ID", row.id), ("Source", row.source)], status_name="Status", status_raw=row.status, status_text=row.status) }}<td>{{ row.requirement | inline_code }}</td><td>{{ row.coveredBy | inline_code }}</td></tr>{% endfor %}</tbody></table>
100
+ {% endif %}
68
101
  </section>
69
102
 
70
103
  {% if planning.get("supersessionLedger") is not none %}
@@ -90,12 +123,13 @@
90
123
 
91
124
  <section data-report-section="decision-drafts" data-report-field="implementationPlanning.decisionDrafts">
92
125
  <h2>{{ t('tasks.implementation-planning.decision-record-draft') }}</h2>
93
- {% for row in planning.decisionDrafts %}
126
+ {% for row in planning.get("decisionDrafts", []) %}
94
127
  <article class="summary-card"><h3>{{ row.number }} · {{ row.slug | inline_code }} ({{ row.status | inline_code }})</h3><p><strong>{{ t('tasks.implementation-planning.context') }}</strong> — {{ row.context | inline_code }}</p><p><strong>{{ t('tasks.implementation-planning.decision') }}</strong> — {{ row.decision | inline_code }}</p><p><strong>{{ t('tasks.implementation-planning.result') }}</strong> — {{ row.consequences | inline_code }}</p></article>
95
128
  {% else %}<p>{{ t('tasks.implementation-planning.there-is-no-decision-draft-to-record') }}</p>{% endfor %}
96
129
  {% if planning.get("skippedAdrCandidates") %}<h3>{{ t('tasks.implementation-planning.what-was-deliberately-not-recorded') }}</h3>
97
130
  <table data-report-field="implementationPlanning.skippedAdrCandidates"><thead><tr><th>{{ t('tasks.implementation-planning.topic') }}</th><th>{{ t('tasks.implementation-planning.why') }}</th></tr></thead><tbody>{% for row in planning.skippedAdrCandidates %}<tr><td>{{ row.topic | inline_code }}</td><td>{{ row.reason | inline_code }}</td></tr>{% endfor %}</tbody></table>{% endif %}
98
131
  </section>
132
+ {% endif %}
99
133
 
100
134
  {% if agentActivities %}
101
135
  <section data-report-section="agent-activity">
@@ -138,6 +138,19 @@
138
138
  "singleRoundPrefix": "Single round —",
139
139
  "noRoundsNote": "No reverify rounds executed (all findings reached consensus at grouping)."
140
140
  },
141
+ "implementationOptionSelection": {
142
+ "decisionContext": "Original Requirement Coverage",
143
+ "evaluationCriteria": "Evaluation Criteria",
144
+ "candidateAudit": "Candidate Audit",
145
+ "rankedOptions": "Ranked Options",
146
+ "recommendedOptionId": "Recommended Option",
147
+ "requirementCoverage": "Requirement Coverage",
148
+ "scopeCommitments": "Scope Commitments",
149
+ "coverageSummary": "Coverage Summary",
150
+ "criterionScores": "Criterion Scores",
151
+ "feasibilityVotes": "Feasibility Votes",
152
+ "planningInvariants": "Planning Invariants"
153
+ },
141
154
  "implementationPlanning": {
142
155
  "planBodyGateLegend": "Gate values — `passed`: agreed, no dissent · `passed-with-dissent`: a minority dissent remains but the gate passes (a majority dissent would block approval) · `blocked-by-disagreement`: majority dissent blocks approval · `aborted-non-result`: verification itself produced no result.",
143
156
  "planBodyBlockedByLegend": "Which input blocked the gate — `majority-disagree`: a worker majority dissent · `coverage-gap`: a Requirement Coverage gap / blocked row, independent of worker votes · `non-result`: verification produced no result. Absent means nothing blocked.",
@@ -28,10 +28,12 @@ taskType: "{{FM_TASK_TYPE}}"
28
28
 
29
29
  - Approved plan path: `runs/implementation-planning/<run-id>/reports/final-report.md`
30
30
  - Approval evidence (quoted exactly from the plan's YAML frontmatter, e.g. `approved: true`):
31
- - Recommended option name selected from the plan:
31
+ - Planning contract: `selected-direction` or `legacy-option-candidates`
32
+ - Selected-direction reference and snapshot digest (required for `selected-direction`):
33
+ - Legacy implementation option name (legacy plans only):
32
34
  - Plan's bite-sized step list (paste or reference by anchor):
33
35
 
34
- > The run MUST refuse to start (status `contract-violated`) if the approved plan path does not exist or does not contain an explicit user approval marker.
36
+ > The run MUST refuse to start (status `contract-violated`) if the approved plan path does not exist or does not contain an explicit user approval marker. A selected-direction plan also requires `outcome: plan-ready`, exact plan coverage, and a valid selected-direction reference. It rejects `--implementation-option`; an existing approved legacy plan keeps that option-selection path.
35
37
 
36
38
  ## Scope From Plan
37
39
 
@@ -55,8 +55,8 @@ taskType: "{{FM_TASK_TYPE}}"
55
55
 
56
56
  ## Planning Concerns
57
57
 
58
- - Possible implementation options:
59
- - Expected trade-offs:
58
+ - Selected direction realization concerns:
59
+ - Legacy-only implementation options and trade-offs:
60
60
  - Dependencies or migrations:
61
61
  - Validation approach:
62
62
  - Rollback approach:
@@ -76,6 +76,14 @@ taskType: "{{FM_TASK_TYPE}}"
76
76
 
77
77
  ## Questions for Analysers
78
78
 
79
+ When `selected-direction.json` is present:
80
+
81
+ 1. Which files, interfaces, and stages realize the snapshot without changing its direction?
82
+ 2. How does every file, stage, validation, and rollback link to the original requirements?
83
+ 3. Does current evidence require `direction-invalidated`?
84
+
85
+ Legacy candidate-comparison questions apply only when the snapshot is absent:
86
+
79
87
  1. What is the safest implementation direction?
80
88
  2. What are the main trade-offs between options?
81
89
  3. What should be validated before, during, and after implementation?
@@ -84,7 +92,13 @@ taskType: "{{FM_TASK_TYPE}}"
84
92
 
85
93
  ## Required Plan Deliverable
86
94
 
87
- The final report of an `implementation-planning` run MUST contain every section below. Missing any one of them is a contract violation.
95
+ ### Selected-direction input contract
96
+
97
+ When `instruction-set/selected-direction.json` is present, read it and the original requirements first. Concretize only the selected direction's files, interfaces, stages, validation, and rollback. Link every file and stage bidirectionally to the original requirements. If code evidence invalidates the direction, return `direction-invalidated` and stop. Candidate comparison and user selection are outside this contract.
98
+
99
+ ### Legacy candidate-comparison input contract
100
+
101
+ When no selected-direction snapshot exists on a compatibility rerun, the final report retains every section below.
88
102
 
89
103
  1. **Option Candidates** — at least two viable options, each with the exact list of files to create or modify and the principal change per file.
90
104
  2. **Trade-off Matrix** — comparison of the candidates across complexity, risk, reversibility, performance impact, scope, and required test surface.
@@ -94,7 +108,7 @@ The final report of an `implementation-planning` run MUST contain every section
94
108
  6. **Validation Checklist** — pre-execution, mid-execution, and post-execution checks (commands, expected outputs, observability points).
95
109
  7. **Rollback Strategy** — exact reverse procedure or compensating action for each significant step.
96
110
  8. **Scope Boundary** — an explicit list of adjacent areas, files, refactors, or quality improvements that this plan **does NOT** cover, each with a one-line reason (deferred, separate owner, separate decision, out of requirement). Any item the analysers were tempted to fold in but chose to exclude MUST appear here. An empty list is allowed only when the analysers explicitly state "no adjacent expansion was considered" — silence is not acceptable.
97
- 9. **Approval frontmatter** — the final report's YAML frontmatter MUST emit `approved: false` and `implementation-option:`. Do NOT create a `User Approval Request` body block; the next `implementation` run reads only the frontmatter gate.
111
+ 9. **Approval frontmatter** — the legacy final report's YAML frontmatter MUST emit `approved: false` and `implementation-option:`. Do NOT create a `User Approval Request` body block; the next `implementation` run reads only the frontmatter gate.
98
112
 
99
113
  ## Phase Boundary
100
114
 
@@ -69,7 +69,7 @@ Workers MUST read the resolved values from the log the lead points to via the
69
69
 
70
70
  1. Within the resolved scope and priority lenses, what are the highest-impact improvement candidates?
71
71
  2. Which candidates have full cross-worker consensus, and which are worker-unique?
72
- 3. For each candidate, what is the safest next phase (requirements-discovery / implementation-planning / error-analysis)?
72
+ 3. For each candidate, what is the safest next phase (requirements-discovery / implementation-option-selection / error-analysis)?
73
73
  4. Which candidates would you intentionally exclude despite being technically valid, and why?
74
74
  5. Are there any signals that the scope itself is mis-defined (and should be re-narrowed before discovery proceeds)?
75
75
 
@@ -0,0 +1,13 @@
1
+ {% import "md/macros/sections.md" as s with context -%}
2
+ {# Coverage denominator and every selectable direction precede the recommendation. #}
3
+ {{ s.section("implementationOptionSelection.decisionContext", "Original Requirement Coverage") }}
4
+
5
+ Coverage metrics: `coveragePercent` and `scopePrecisionPercent`.
6
+
7
+ {{ s.section("implementationOptionSelection.rankedOptions", "Ranked Options") }}
8
+ {{ s.section("implementationOptionSelection.recommendedOptionId", "Recommended Option") }}
9
+ {{ s.section("implementationOptionSelection.evaluationCriteria", "Evaluation Criteria") }}
10
+ {{ s.section("implementationOptionSelection.candidateAudit", "Candidate Audit") }}
11
+ {{ s.section("implementationOptionSelection.preselectedDirection", "Preselected Direction") }}
12
+ {{ s.section("implementationOptionSelection.routing", "Routing") }}
13
+ {{ md_rest("implementationOptionSelection", 3) }}
@@ -5,6 +5,22 @@
5
5
  Option candidates and the trade-off matrix answer "why this one" and sit
6
6
  after that, in the closing sweep.
7
7
  -#}
8
+ {% if implementationPlanning.get("planningContract") == "selected-direction" -%}
9
+ {{ s.section("implementationPlanning.selectedDirectionRef", "Selected Direction Reference") }}
10
+ {% if implementationPlanning.outcome == "plan-ready" -%}
11
+ {{ s.section("implementationPlanning.directionRealization", "Direction Realization") }}
12
+ {{ s.section("implementationPlanning.stageMap", "Stage Map") }}
13
+ {{ s.section("implementationPlanning.stages", "Stages") }}
14
+ {{ s.section("implementationPlanning.validationChecklist", "Validation Checklist") }}
15
+ {{ s.section("implementationPlanning.rollbackStrategy", "Rollback Strategy") }}
16
+ {{ s.section("implementationPlanning.requirementCoverage", "Requirement Coverage") }}
17
+ {{ s.section("implementationPlanning.coverageSummary", "Coverage Summary") }}
18
+ {% else -%}
19
+ {{ s.section("implementationPlanning.directionInvalidation", "Direction Invalidation") }}
20
+ {{ s.section("implementationPlanning.routing", "Re-entry Route") }}
21
+ {% endif -%}
22
+ {{ md_rest("implementationPlanning", 3) }}
23
+ {% else -%}
8
24
  {{ s.section("implementationPlanning.recommendedOption", "Recommended Option") }}
9
25
  {{ s.section("implementationPlanning.stageMap", "Stage Map") }}
10
26
  {{ s.section("implementationPlanning.stages", "Stages") }}
@@ -13,3 +29,4 @@
13
29
  {{ s.section("implementationPlanning.rollbackStrategy", "Rollback Strategy") }}
14
30
  {{ s.section("implementationPlanning.requirementCoverage", "Requirement Coverage") }}
15
31
  {{ md_rest("implementationPlanning", 3) }}
32
+ {% endif -%}