okstra 0.200.0 → 0.201.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 (96) hide show
  1. package/README.md +4 -2
  2. package/dist/cli-registry.mjs +6 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/docs/cli.md +14 -3
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/agents/workers/report-writer-worker.md +7 -3
  8. package/runtime/bin/okstra-spawn-followups.py +2 -2
  9. package/runtime/prompts/duties/technical-verification-worker.md +44 -0
  10. package/runtime/prompts/launch.template.md +7 -1
  11. package/runtime/prompts/lead/okstra-lead-contract.md +7 -2
  12. package/runtime/prompts/lead/plan-body-verification.md +3 -1
  13. package/runtime/prompts/lead/report-writer.md +11 -5
  14. package/runtime/prompts/lead/team-contract.md +6 -0
  15. package/runtime/prompts/profiles/_implementation-verifier.md +7 -1
  16. package/runtime/prompts/profiles/final-verification.md +5 -0
  17. package/runtime/prompts/profiles/forbidden-actions.json +6 -0
  18. package/runtime/prompts/profiles/implementation-option-selection.md +7 -1
  19. package/runtime/prompts/profiles/implementation-planning.md +1 -0
  20. package/runtime/prompts/profiles/technical-verification.md +53 -0
  21. package/runtime/prompts/wizard/prompts.ko.json +2 -1
  22. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -4
  23. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +2 -0
  24. package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +36 -5
  25. package/runtime/python/okstra_ctl/agent/invocation.py +14 -6
  26. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +4 -3
  27. package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +83 -22
  28. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +44 -2
  29. package/runtime/python/okstra_ctl/conformance.py +2 -20
  30. package/runtime/python/okstra_ctl/dispatch_core.py +25 -5
  31. package/runtime/python/okstra_ctl/dispatch_state.py +2 -0
  32. package/runtime/python/okstra_ctl/domain/provider.py +0 -1
  33. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  34. package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -1
  35. package/runtime/python/okstra_ctl/implementation_direction.py +64 -7
  36. package/runtime/python/okstra_ctl/implementation_options.py +58 -45
  37. package/runtime/python/okstra_ctl/model_pool.py +2 -5
  38. package/runtime/python/okstra_ctl/next_phase.py +3 -0
  39. package/runtime/python/okstra_ctl/plan_items.py +15 -0
  40. package/runtime/python/okstra_ctl/plan_items_cli.py +9 -3
  41. package/runtime/python/okstra_ctl/qa_commands.py +30 -0
  42. package/runtime/python/okstra_ctl/registry/provider_registry.py +11 -8
  43. package/runtime/python/okstra_ctl/render.py +3 -0
  44. package/runtime/python/okstra_ctl/render_final_report.py +1 -0
  45. package/runtime/python/okstra_ctl/report_assembly.py +8 -2
  46. package/runtime/python/okstra_ctl/report_contract.py +3 -0
  47. package/runtime/python/okstra_ctl/report_corrections.py +209 -93
  48. package/runtime/python/okstra_ctl/report_finalize.py +25 -8
  49. package/runtime/python/okstra_ctl/report_html/router.py +2 -0
  50. package/runtime/python/okstra_ctl/report_html/view_models/technical_verification.py +21 -0
  51. package/runtime/python/okstra_ctl/report_projections.py +4 -3
  52. package/runtime/python/okstra_ctl/report_synthesis_packet.py +181 -47
  53. package/runtime/python/okstra_ctl/run.py +82 -0
  54. package/runtime/python/okstra_ctl/team.py +4 -1
  55. package/runtime/python/okstra_ctl/technical_verification.py +195 -0
  56. package/runtime/python/okstra_ctl/usage_identity.py +54 -0
  57. package/runtime/python/okstra_ctl/usage_report.py +22 -8
  58. package/runtime/python/okstra_ctl/verification_target.py +74 -0
  59. package/runtime/python/okstra_ctl/wizard/__init__.py +1 -1
  60. package/runtime/python/okstra_ctl/wizard/cli.py +2 -1
  61. package/runtime/python/okstra_ctl/wizard/confirmation.py +38 -2
  62. package/runtime/python/okstra_ctl/wizard/engine.py +3 -0
  63. package/runtime/python/okstra_ctl/wizard/ids.py +1 -0
  64. package/runtime/python/okstra_ctl/wizard/outcome.py +63 -0
  65. package/runtime/python/okstra_ctl/wizard/picker_navigation.py +2 -2
  66. package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
  67. package/runtime/python/okstra_ctl/wizard/render.py +8 -55
  68. package/runtime/python/okstra_ctl/wizard/roles.py +11 -7
  69. package/runtime/python/okstra_ctl/wizard/sources.py +28 -2
  70. package/runtime/python/okstra_ctl/wizard/state.py +13 -6
  71. package/runtime/python/okstra_ctl/wizard/steps_plan.py +8 -0
  72. package/runtime/python/okstra_ctl/worker_liveness.py +52 -39
  73. package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
  74. package/runtime/python/okstra_ctl/workflow.py +8 -0
  75. package/runtime/python/okstra_ctl/write_policy.py +23 -0
  76. package/runtime/python/okstra_token_usage/blocks.py +50 -1
  77. package/runtime/python/okstra_token_usage/claude.py +42 -21
  78. package/runtime/python/okstra_token_usage/codex.py +17 -0
  79. package/runtime/python/okstra_token_usage/collect.py +299 -162
  80. package/runtime/python/okstra_token_usage/cursor.py +2 -3
  81. package/runtime/python/okstra_token_usage/report.py +35 -30
  82. package/runtime/python/okstra_token_usage/task_totals.py +3 -12
  83. package/runtime/schemas/final-report-v2.0.schema.json +298 -7
  84. package/runtime/schemas/final-report-v3.0.schema.json +298 -7
  85. package/runtime/schemas/report-narrative-v3.0.schema.json +1 -0
  86. package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +1 -1
  87. package/runtime/schemas/report-writer-corrections-v1.0.schema.json +30 -3
  88. package/runtime/skills/okstra-run/SKILL.md +10 -2
  89. package/runtime/skills/okstra-setup/SKILL.md +42 -7
  90. package/runtime/templates/report-writer-prompt-preamble.md +7 -3
  91. package/runtime/templates/reports/html/i18n/en.json +11 -0
  92. package/runtime/templates/reports/html/i18n/ko.json +11 -0
  93. package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +7 -3
  94. package/runtime/templates/reports/html/tasks/technical-verification.template.html +35 -0
  95. package/runtime/templates/reports/md/tasks/technical-verification.template.md +5 -0
  96. package/runtime/validators/validate-run.py +9 -4
@@ -446,6 +446,9 @@
446
446
  "verdict": "Verdict",
447
447
  "counterevidence": "Strongest counterevidence",
448
448
  "safety-blockers": "Safety blockers",
449
+ "technical-verification": "Technical verification pending",
450
+ "user-decision": "User decision record",
451
+ "classification-required": "Classification required before reassembly",
449
452
  "unresolved-facts": "Unresolved feasibility facts",
450
453
  "expected-outcome": "Expected outcome",
451
454
  "coverage-line": "Covers {covered} of {total} requirements ({verdict}).",
@@ -686,6 +689,14 @@
686
689
  "decision-needed": "Decision needed",
687
690
  "glossary": "Glossary",
688
691
  "decision-record": "Decision record"
692
+ },
693
+ "technical-verification": {
694
+ "source": "Source comparison",
695
+ "scope": "Technical evidence only; no implementation direction is approved.",
696
+ "checks": "Experiment plans and observations",
697
+ "confirming": "Confirming signal",
698
+ "rejecting": "Rejecting signal",
699
+ "next": "Return to comparison"
689
700
  }
690
701
  }
691
702
  }
@@ -446,6 +446,9 @@
446
446
  "verdict": "판정",
447
447
  "counterevidence": "가장 강한 반대 근거",
448
448
  "safety-blockers": "안전 차단 요인",
449
+ "technical-verification": "기술 검증 대기",
450
+ "user-decision": "사용자 결정 기록",
451
+ "classification-required": "재조립 전 해결 방식 분류 필요",
449
452
  "unresolved-facts": "미확정 실행 가능성 사실",
450
453
  "expected-outcome": "예상 결과",
451
454
  "coverage-line": "요구사항 {total}개 중 {covered}개를 충족합니다({verdict}).",
@@ -686,6 +689,14 @@
686
689
  "decision-needed": "결정 필요",
687
690
  "glossary": "용어집",
688
691
  "decision-record": "결정 기록"
692
+ },
693
+ "technical-verification": {
694
+ "source": "검증 대상 비교 보고서",
695
+ "scope": "기술적 근거만 수집하며 구현 방향을 승인하지 않습니다.",
696
+ "checks": "시험 계획과 관측 결과",
697
+ "confirming": "가설을 지지하는 신호",
698
+ "rejecting": "가설을 반박하는 신호",
699
+ "next": "구현 방향 재비교"
689
700
  }
690
701
  }
691
702
  }
@@ -33,8 +33,7 @@
33
33
  <table data-report-field="implementationOptionSelection.rankedOptions.feasibilityVotes"><thead><tr><th>{{ t('tasks.implementation-option-selection.worker') }}</th><th>{{ t('tasks.implementation-option-selection.reason') }}</th><th>{{ t('tasks.implementation-option-selection.counterevidence') }}</th></tr></thead><tbody>{% for vote in row.feasibilityVotes %}<tr>{{ row_key(pairs=[(t('tasks.implementation-option-selection.worker'), vote.worker)], status_name=t('tasks.implementation-option-selection.verdict'), status_raw=vote.verdict, status_text=vote.verdict | enum_label('feasibilityVerdict')) }}<td>{{ vote.rationale | inline_code(extra) }}</td><td>{{ vote.counterevidence | inline_code(extra) }}</td></tr>{% endfor %}</tbody></table>
34
34
  {% if row.safetyBlockers %}<p><strong>{{ t('tasks.implementation-option-selection.safety-blockers') }}</strong></p>
35
35
  <ul>{% for blocker in row.safetyBlockers %}<li><span class="status status-risk">{{ blocker.category | enum_label('safetyCategory') }}</span> {{ blocker.statement | inline_code(extra) }} <span class="evidence-refs">{{ t('macros.layout.evidence') }} {{ blocker.evidence | evidence_refs(extra) }}</span></li>{% endfor %}</ul>{% endif %}
36
- {% if row.unresolvedFeasibilityFacts %}<p><strong>{{ t('tasks.implementation-option-selection.unresolved-facts') }}</strong></p>
37
- <ul>{% for fact in row.unresolvedFeasibilityFacts %}<li>{{ fact.fact | inline_code(extra) }} — {{ t('tasks.implementation-option-selection.why-it-matters') }} {{ fact.whyItMatters | inline_code(extra) }} <span class="evidence-refs">{{ t('macros.layout.evidence') }} {{ fact.evidence | evidence_refs(extra) }}</span></li>{% endfor %}</ul>{% endif %}
36
+ {{ unresolved_facts(row, extra) }}
38
37
  </div>
39
38
  <div class="option-detail">
40
39
  <h4>{{ t('tasks.implementation-option-selection.expected-outcome') }}</h4>
@@ -48,6 +47,11 @@
48
47
  </article>
49
48
  {%- endmacro %}
50
49
 
50
+ {% macro unresolved_facts(row, extra) -%}
51
+ {% if row.unresolvedFeasibilityFacts %}<p><strong>{{ t('tasks.implementation-option-selection.unresolved-facts') }}</strong></p>
52
+ <ul>{% for fact in row.unresolvedFeasibilityFacts %}<li>{% if fact.get('resolutionKind') == 'technical-verification' %}{{ t('tasks.implementation-option-selection.technical-verification') }}: {% elif fact.get('resolutionKind') == 'user-decision' %}{{ t('tasks.implementation-option-selection.user-decision') }} ({{ fact.get('clarificationRefs', []) | join(', ') | inline_code(extra) }}): {% else %}{{ t('tasks.implementation-option-selection.classification-required') }}: {% endif %}{{ fact.fact | inline_code(extra) }} — {{ t('tasks.implementation-option-selection.why-it-matters') }} {{ fact.whyItMatters | inline_code(extra) }} <span class="evidence-refs">{{ t('macros.layout.evidence') }} {{ fact.evidence | evidence_refs(extra) }}</span></li>{% endfor %}</ul>{% endif %}
53
+ {%- endmacro %}
54
+
51
55
  {% block human_content %}
52
56
  <section data-report-section="selection-status">
53
57
  <h2>{{ t('tasks.implementation-option-selection.selection-status') }}</h2>
@@ -89,6 +93,6 @@
89
93
 
90
94
  <section data-report-section="candidate-audit" data-report-field="implementationOptionSelection.candidateAudit">
91
95
  <h2>{{ t('tasks.implementation-option-selection.candidate-audit') }}</h2>
92
- <table><thead><tr><th>{{ t('tasks.implementation-option-selection.candidate') }}</th><th>{{ t('tasks.implementation-option-selection.goal-column') }}</th><th>{{ t('tasks.implementation-option-selection.disposition') }}</th><th>{{ t('tasks.implementation-option-selection.reason') }}</th><th>{{ t('macros.layout.coverage-percent') }}</th><th>{{ t('macros.layout.scope-precision-percent') }}</th></tr></thead><tbody>{% for row in selection.candidateAudit %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[(t('macros.layout.id'), row.id), (t('macros.layout.proposed-by'), row.proposedBy)]) }}<td>{{ row.goal | inline_code(cardLinks.get(row.id, {})) }}{% if row.get("scopeCommitments") %}<ul class="audit-commitments">{% for item in row.scopeCommitments %}<li id="id-{{ row.id }}-{{ item.id }}">{{ item.id | inline_code(cardLinks.get(row.id, {})) }} · {{ item.commitment | inline_code(cardLinks.get(row.id, {})) }}</li>{% endfor %}</ul>{% endif %}</td><td>{{ row.disposition }}{% if row.mergedInto %} → {{ row.mergedInto | inline_code }}{% endif %}</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>
96
+ <table><thead><tr><th>{{ t('tasks.implementation-option-selection.candidate') }}</th><th>{{ t('tasks.implementation-option-selection.goal-column') }}</th><th>{{ t('tasks.implementation-option-selection.disposition') }}</th><th>{{ t('tasks.implementation-option-selection.reason') }}</th><th>{{ t('macros.layout.coverage-percent') }}</th><th>{{ t('macros.layout.scope-precision-percent') }}</th></tr></thead><tbody>{% for row in selection.candidateAudit %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[(t('macros.layout.id'), row.id), (t('macros.layout.proposed-by'), row.proposedBy)]) }}<td>{{ row.goal | inline_code(cardLinks.get(row.id, {})) }}{% if row.get("scopeCommitments") %}<ul class="audit-commitments">{% for item in row.scopeCommitments %}<li id="id-{{ row.id }}-{{ item.id }}">{{ item.id | inline_code(cardLinks.get(row.id, {})) }} · {{ item.commitment | inline_code(cardLinks.get(row.id, {})) }}</li>{% endfor %}</ul>{% endif %}</td><td>{{ row.disposition }}{% if row.mergedInto %} → {{ row.mergedInto | inline_code }}{% endif %}</td><td>{{ row.reason | inline_code }}{{ unresolved_facts(row, cardLinks.get(row.id, {})) }}</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>
93
97
  </section>
94
98
  {% endblock %}
@@ -0,0 +1,35 @@
1
+ {% extends "html/base.template.html" %}
2
+ {% block human_content %}
3
+ <section data-report-section="technical-source" data-report-field="technicalVerification.sourceReport">
4
+ <h2>{{ t('tasks.technical-verification.source') }}</h2>
5
+ <p>{{ verification.sourceReport | inline_code }}</p>
6
+ <p>{{ t('tasks.technical-verification.scope') }}</p>
7
+ </section>
8
+ <section data-report-section="technical-checks" data-report-field="technicalVerification.checks">
9
+ <h2>{{ t('tasks.technical-verification.checks') }}</h2>
10
+ {% for row in verification.checks %}
11
+ <article id="id-{{ row.id }}">
12
+ <h3>{{ row.id }} · {{ row.candidateId }} · {{ row.status }}</h3>
13
+ <p>{{ row.fact | inline_code }}</p>
14
+ <p>{{ row.hypothesis | inline_code }}</p>
15
+ <p>{{ row.procedure | inline_code }}</p>
16
+ <dl>
17
+ <dt>{{ t('tasks.technical-verification.confirming') }}</dt><dd>{{ row.confirmingSignal | inline_code }}</dd>
18
+ <dt>{{ t('tasks.technical-verification.rejecting') }}</dt><dd>{{ row.rejectingSignal | inline_code }}</dd>
19
+ </dl>
20
+ <p>{{ row.observation | inline_code }}</p>
21
+ {% for command in row.commands %}
22
+ <pre><code>{{ command.command }}</code></pre>
23
+ <p><code>{{ command.cwd }}</code> · {{ command.exitCode }}</p>
24
+ <p>{{ [command.logPath] | code_evidence }}</p>
25
+ {% endfor %}
26
+ <ul>{% for limitation in row.limitations %}<li>{{ limitation | inline_code }}</li>{% endfor %}</ul>
27
+ </article>
28
+ {% endfor %}
29
+ </section>
30
+ <section data-report-section="technical-routing" data-report-field="technicalVerification.routing">
31
+ <h2>{{ t('tasks.technical-verification.next') }}</h2>
32
+ <p>{{ verification.routing.nextTaskType | inline_code }}</p>
33
+ <p>{{ verification.routing.rationale | inline_code }}</p>
34
+ </section>
35
+ {% endblock %}
@@ -0,0 +1,5 @@
1
+ {% import "md/macros/sections.md" as s with context -%}
2
+ {{ s.section("technicalVerification.sourceReport", "Source Comparison") }}
3
+ {{ s.section("technicalVerification.checks", "Experiment Plans and Observations") }}
4
+ {{ s.section("technicalVerification.routing", "Return to Comparison") }}
5
+ {{ md_rest("technicalVerification", 3) }}
@@ -87,6 +87,7 @@ from okstra_ctl.stage_citations import enumerated_stage_numbers # noqa: E402
87
87
  from okstra_ctl.plan_items import ( # noqa: E402
88
88
  CRITIC_WORKER_ID,
89
89
  advisory_plan_body_gating,
90
+ requires_plan_repair,
90
91
  analyser_key as _analyser_key,
91
92
  is_critic_worker,
92
93
  lead_decision_basis,
@@ -116,6 +117,7 @@ from okstra_ctl.final_report_paths import ( # noqa: E402
116
117
  translation_sidecar_path,
117
118
  )
118
119
  from okstra_token_usage.report import _match_worker_index # noqa: E402
120
+ from okstra_ctl.technical_verification import validate_technical_verification_report
119
121
  from okstra_ctl.implementation_options import ( # noqa: E402
120
122
  validate_blocked_answer_channel,
121
123
  validate_implementation_option_selection,
@@ -3310,6 +3312,7 @@ def validate_final_report_data(
3310
3312
  _validate_verifier_discrepancy_names_checklist_phase(
3311
3313
  data, report_path, project_root, failures
3312
3314
  )
3315
+ failures.extend(validate_technical_verification_report(data, report_path, project_root or report_path.parent))
3313
3316
  if task_type == "implementation-option-selection":
3314
3317
  selection = data.get("implementationOptionSelection") or {}
3315
3318
  validation_root = project_root or report_path.parent
@@ -4163,7 +4166,7 @@ def _recompute_plan_body_gate(
4163
4166
  return None
4164
4167
  if all(c == "all-non-result" for c in classes):
4165
4168
  return "aborted-non-result"
4166
- if pbv.get("gating") is False:
4169
+ if pbv.get("gating") is False and not requires_plan_repair(pbv):
4167
4170
  if any(c in ("majority-disagree", "has-dissent", "needs-reverify", "all-non-result") for c in classes):
4168
4171
  return "passed-with-dissent"
4169
4172
  return "passed"
@@ -4297,7 +4300,7 @@ def _gate_blocking_causes(
4297
4300
  """Which inputs actually block approval, as `gateBlockedBy` enum values."""
4298
4301
  causes = set()
4299
4302
  recomputed = _recompute_plan_body_gate(pbv, accepted_item_ids)
4300
- if pbv.get("gating") is False:
4303
+ if pbv.get("gating") is False and not requires_plan_repair(pbv):
4301
4304
  if recomputed == "aborted-non-result":
4302
4305
  causes.add("non-result")
4303
4306
  return causes
@@ -6181,6 +6184,8 @@ def _validate_advisory_plan_body_gating(data: dict, failures: list[str]) -> None
6181
6184
  if not isinstance(pbv, dict) or pbv.get("gating") is not False:
6182
6185
  return
6183
6186
  facts = ip.get("designPreparation") is not None or ip.get("stageMap") or ip.get("stages")
6187
+ if requires_plan_repair(pbv):
6188
+ failures.append("plan-body-verification: objective verification defects require gating=true; repair the affected items")
6184
6189
  if facts and not advisory_plan_body_gating(ip):
6185
6190
  failures.append(
6186
6191
  "final-report data.json: implementationPlanning.planBodyVerification "
@@ -7109,7 +7114,7 @@ def _validate_plan_body_clarification_matching(
7109
7114
  # 게이트 계산은 이미 그것을 존중한다(`_recompute_plan_body_gate`,
7110
7115
  # `_gate_blocking_causes`). 이 검사만 그 상태를 안 보면 자문 라운드가
7111
7116
  # 승인 차단 행을 강제하게 되어, 막지 않기로 한 판정이 다시 막는다.
7112
- if pbv.get("gating") is False:
7117
+ if pbv.get("gating") is False and not requires_plan_repair(pbv):
7113
7118
  return
7114
7119
  accepted = (
7115
7120
  _resolved_noncritical_dissent_ids(data)
@@ -7178,7 +7183,7 @@ def _validate_self_fix_before_clarification(data: dict, failures: list[str]) ->
7178
7183
  # 자문 라운드에 planner-fixable 과반 반대가 하나라도 나오면 통과 가능한
7179
7184
  # 값이 없다. `okstra plan-items complete-round` 도 자문 라운드의 self-fix
7180
7185
  # 기록을 거부하므로(plan_items_cli.py) 우회로도 없다.
7181
- if pbv.get("gating") is False:
7186
+ if pbv.get("gating") is False and not requires_plan_repair(pbv):
7182
7187
  return
7183
7188
  if _self_fix_budget_exhausted(pbv):
7184
7189
  return