okstra 0.49.0 → 0.51.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 (86) hide show
  1. package/README.kr.md +8 -7
  2. package/README.md +8 -7
  3. package/bin/okstra +2 -0
  4. package/docs/kr/architecture.md +23 -24
  5. package/docs/kr/cli.md +6 -6
  6. package/docs/project-structure-overview.md +13 -9
  7. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +559 -0
  8. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +121 -0
  9. package/docs/task-process/error-analysis.md +1 -1
  10. package/docs/task-process/final-verification.md +1 -1
  11. package/docs/task-process/release-handoff.md +1 -1
  12. package/docs/task-process/requirements-discovery.md +1 -1
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/agents/SKILL.md +18 -14
  16. package/runtime/agents/workers/claude-worker.md +4 -4
  17. package/runtime/agents/workers/codex-worker.md +3 -3
  18. package/runtime/agents/workers/gemini-worker.md +3 -3
  19. package/runtime/agents/workers/report-writer-worker.md +3 -3
  20. package/runtime/bin/lib/okstra/cli.sh +8 -1
  21. package/runtime/bin/lib/okstra/globals.sh +3 -0
  22. package/runtime/bin/lib/okstra/interactive.sh +14 -12
  23. package/runtime/bin/lib/okstra/usage.sh +6 -0
  24. package/runtime/bin/okstra-render-report-views.py +1 -1
  25. package/runtime/bin/okstra-team-reconcile.sh +28 -0
  26. package/runtime/bin/okstra.sh +2 -0
  27. package/runtime/prompts/launch.template.md +4 -2
  28. package/runtime/prompts/profiles/_common-contract.md +15 -15
  29. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  30. package/runtime/prompts/profiles/_implementation-executor.md +3 -3
  31. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  32. package/runtime/prompts/profiles/error-analysis.md +1 -1
  33. package/runtime/prompts/profiles/final-verification.md +2 -2
  34. package/runtime/prompts/profiles/implementation-planning.md +10 -9
  35. package/runtime/prompts/profiles/implementation.md +1 -1
  36. package/runtime/prompts/profiles/improvement-discovery.md +5 -5
  37. package/runtime/prompts/profiles/release-handoff.md +2 -2
  38. package/runtime/prompts/profiles/requirements-discovery.md +2 -2
  39. package/runtime/python/okstra_ctl/analysis_packet.py +259 -0
  40. package/runtime/python/okstra_ctl/clarification_items.py +11 -11
  41. package/runtime/python/okstra_ctl/context_cost.py +308 -0
  42. package/runtime/python/okstra_ctl/migrate.py +2 -12
  43. package/runtime/python/okstra_ctl/paths.py +22 -0
  44. package/runtime/python/okstra_ctl/render.py +285 -126
  45. package/runtime/python/okstra_ctl/render_final_report.py +32 -1
  46. package/runtime/python/okstra_ctl/report_views.py +12 -12
  47. package/runtime/python/okstra_ctl/run.py +510 -248
  48. package/runtime/python/okstra_ctl/sequence.py +2 -5
  49. package/runtime/python/okstra_ctl/team_reconcile.py +131 -0
  50. package/runtime/python/okstra_ctl/wizard.py +219 -136
  51. package/runtime/python/okstra_ctl/workflow.py +1 -1
  52. package/runtime/python/okstra_ctl/worktree.py +13 -5
  53. package/runtime/schemas/final-report-v1.0.schema.json +4 -0
  54. package/runtime/skills/okstra-brief/SKILL.md +1 -1
  55. package/runtime/skills/okstra-coding-preflight/SKILL.md +69 -0
  56. package/runtime/skills/okstra-coding-preflight/architecture/hexagonal.md +116 -0
  57. package/runtime/skills/okstra-coding-preflight/clean-code.md +254 -0
  58. package/runtime/skills/okstra-coding-preflight/languages/java.md +64 -0
  59. package/runtime/skills/okstra-coding-preflight/languages/javascript-typescript.md +87 -0
  60. package/runtime/skills/okstra-coding-preflight/languages/kotlin.md +69 -0
  61. package/runtime/skills/okstra-coding-preflight/languages/nodejs.md +66 -0
  62. package/runtime/skills/okstra-coding-preflight/languages/python.md +179 -0
  63. package/runtime/skills/okstra-coding-preflight/languages/rust.md +105 -0
  64. package/runtime/skills/okstra-coding-preflight/languages/sql.md +68 -0
  65. package/runtime/skills/okstra-context-loader/SKILL.md +12 -6
  66. package/runtime/skills/okstra-convergence/SKILL.md +8 -8
  67. package/runtime/skills/okstra-inspect/SKILL.md +100 -1
  68. package/runtime/skills/okstra-report-writer/SKILL.md +27 -23
  69. package/runtime/skills/okstra-run/SKILL.md +3 -1
  70. package/runtime/skills/okstra-team-contract/SKILL.md +8 -5
  71. package/runtime/templates/reports/final-report.template.md +188 -187
  72. package/runtime/templates/reports/i18n/en.json +4 -4
  73. package/runtime/templates/reports/i18n/ko.json +4 -4
  74. package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
  75. package/runtime/templates/reports/release-handoff-input.template.md +1 -1
  76. package/runtime/templates/reports/user-response.template.md +1 -1
  77. package/runtime/templates/worker-prompt-preamble.md +4 -4
  78. package/runtime/validators/lib/fixtures.sh +2 -2
  79. package/runtime/validators/validate-implementation-plan-stages.py +9 -9
  80. package/runtime/validators/validate-report-views.py +10 -10
  81. package/runtime/validators/validate-run.py +36 -36
  82. package/runtime/validators/validate_improvement_report.py +8 -8
  83. package/src/_python-helper.mjs +3 -3
  84. package/src/context-cost.mjs +27 -0
  85. package/src/install.mjs +1 -0
  86. package/src/uninstall.mjs +1 -0
@@ -15,8 +15,8 @@
15
15
  "declinedFixRecommendations": "- None.",
16
16
  "discrepancy": "- None.",
17
17
  "lingeringRisks": "- No tracked lingering risks.",
18
- "noClarification": "- No additional information requested. The Section 2 verdict stands as-is.",
19
- "noFollowUp": "- No follow-up tasks. The next phase for this run is in §6 (Recommended Next Steps)."
18
+ "noClarification": "- No additional information requested. The Section 7 verdict stands as-is.",
19
+ "noFollowUp": "- No follow-up tasks. The next phase for this run is in §3 (Recommended Next Steps)."
20
20
  },
21
21
  "columns": {
22
22
  "recordMeta": "Record",
@@ -39,7 +39,7 @@
39
39
  "stepwiseExecutionOrder": "Stepwise Execution Order"
40
40
  },
41
41
  "sectionIntro": {
42
- "verdictCard": "At-a-glance verdict card — a summary that mirrors the values in `## 2. Final Verdict` and `## 6. Recommended Next Steps`.",
42
+ "verdictCard": "At-a-glance verdict card — a summary that mirrors the values in `## 7. Final Verdict` and `## 3. Recommended Next Steps`.",
43
43
  "clarificationCarryIn": "Unresolved `Clarification Items` from the prior report, re-examined against new evidence and carried in with updated status.",
44
44
  "ticketCoverage": "Summary of the core problems, requirements, and verification targets this run covered.",
45
45
  "executionStatus": "At-a-glance table of each worker's status, assigned model, and key findings.",
@@ -71,7 +71,7 @@
71
71
  "intro": "Final conclusion and recommended direction. `Direction` values: `continue-investigation / begin-implementation / approve / reject / hold`. When `task-type` is `final-verification`, `Verdict Token` is one of `accepted / conditional-accept / blocked` and serves as the `release-handoff` entry gate. For all other task-types: `not-applicable`."
72
72
  },
73
73
  "evidence": {
74
- "sourceItemsColumnNote": "The `Source items` column is described in §1.1."
74
+ "sourceItemsColumnNote": "The `Source items` column is described in §6.1."
75
75
  },
76
76
  "roundHistory": {
77
77
  "round2SkippedReasonNote": "(one of: `queue-empty`, `max-rounds-1`, `all-reverify-non-result`, `not-skipped`, `convergence-disabled`, `single-analyser-only`)",
@@ -15,8 +15,8 @@
15
15
  "declinedFixRecommendations": "- 없음.",
16
16
  "discrepancy": "- 없음.",
17
17
  "lingeringRisks": "- 추적 대상 잔존 위험 없음.",
18
- "noClarification": "- 추가 정보 요청 없음. Section 2 의 최종 판단이 그대로 유효합니다.",
19
- "noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §6 (Recommended Next Steps) 참고."
18
+ "noClarification": "- 추가 정보 요청 없음. Section 7 의 최종 판단이 그대로 유효합니다.",
19
+ "noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §3 (Recommended Next Steps) 참고."
20
20
  },
21
21
  "columns": {
22
22
  "recordMeta": "항목",
@@ -39,7 +39,7 @@
39
39
  "stepwiseExecutionOrder": "단계별 실행 순서"
40
40
  },
41
41
  "sectionIntro": {
42
- "verdictCard": "한눈에 보는 결과 카드 — `## 2. Final Verdict` 와 `## 6. Recommended Next Steps` 의 값을 그대로 옮긴 요약입니다.",
42
+ "verdictCard": "한눈에 보는 결과 카드 — `## 7. Final Verdict` 와 `## 3. Recommended Next Steps` 의 값을 그대로 옮긴 요약입니다.",
43
43
  "clarificationCarryIn": "이전 보고서의 미해결 `Clarification Items` 를 새 증거로 재검토해 해소·갱신한 뒤 이어받은 항목입니다.",
44
44
  "ticketCoverage": "이 run 이 다룬 핵심 문제·요구사항·검증 대상 요약입니다.",
45
45
  "executionStatus": "각 worker 의 상태·배정 모델·핵심 finding 을 한눈에 보는 표입니다.",
@@ -71,7 +71,7 @@
71
71
  "intro": "최종 결론과 권장 방향입니다. `Direction` 값: `continue-investigation / begin-implementation / approve / reject / hold`. `task-type` 이 `final-verification` 일 때 `Verdict Token` 은 `accepted / conditional-accept / blocked` 중 하나이며 `release-handoff` 진입 게이트로 쓰입니다. 그 외 task-type 에서는 `not-applicable`."
72
72
  },
73
73
  "evidence": {
74
- "sourceItemsColumnNote": "`Source items` 열 설명은 §1.1 과 동일합니다."
74
+ "sourceItemsColumnNote": "`Source items` 열 설명은 §6.1 과 동일합니다."
75
75
  },
76
76
  "roundHistory": {
77
77
  "round2SkippedReasonNote": "(허용 값: `queue-empty`, `max-rounds-1`, `all-reverify-non-result`, `not-skipped`, `convergence-disabled`, `single-analyser-only`)",
@@ -108,7 +108,7 @@ The final report of an `implementation-planning` run MUST contain every section
108
108
 
109
109
  ## Stage Output Shape (reference)
110
110
 
111
- This run's final report MUST emit `## 4.5 Stage Map` and `## 4.5.<i> Stage <i>` sections per the implementation-planning profile §"Required deliverable shape". Two illustrative shapes:
111
+ This run's final report MUST emit `## 5.5 Stage Map` and `## 5.5.<i> Stage <i>` sections per the implementation-planning profile §"Required deliverable shape". Two illustrative shapes:
112
112
 
113
113
  ### Shape A — single stage (small work)
114
114
  | stage | title | depends-on | step-count | exit-contract-summary |
@@ -26,7 +26,7 @@ taskType: "{{FM_TASK_TYPE}}"
26
26
  ## Source Verification Report
27
27
 
28
28
  - Path (project-relative) to the `final-verification` final-report whose verdict authorises this handoff:
29
- - Verbatim quoted `Verdict Token` row from that report's `## 2. Final Verdict` table (MUST have value `accepted`):
29
+ - Verbatim quoted `Verdict Token` row from that report's `## 7. Final Verdict` table (MUST have value `accepted`):
30
30
  - Run timestamp of that final-verification run:
31
31
 
32
32
  > If this section is empty or cites a `Verdict Token` value other than `accepted`, the lead MUST end the run immediately and route back to `final-verification`. Release-handoff never operates on `conditional-accept` or `blocked` outcomes.
@@ -18,7 +18,7 @@ created-at: <ISO 8601 UTC timestamp>
18
18
 
19
19
  ## Body 스키마
20
20
 
21
- 본문은 `# User Response` 단일 H1 헤딩 아래, 각 응답을 `## <Response ID>` 헤딩으로 구분합니다. Response ID 는 final-report `## 5. Clarification Items` 표의 `ID` 컬럼(`C-NNN`)을 그대로 인용합니다.
21
+ 본문은 `# User Response` 단일 H1 헤딩 아래, 각 응답을 `## <Response ID>` 헤딩으로 구분합니다. Response ID 는 final-report `## 1. Clarification Items` 표의 `ID` 컬럼(`C-NNN`)을 그대로 인용합니다.
22
22
 
23
23
  각 응답 블록의 schema:
24
24
 
@@ -8,7 +8,7 @@ It replaces the previous practice of inlining ~80 lines of identical boilerplate
8
8
 
9
9
  ## Required reading (analysis workers + report-writer worker)
10
10
 
11
- You are required to read every input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable. Each file may contain decisive context that is not surfaced in its summary or first page.
11
+ You are required to read every primary input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable. Source files listed as fallback/evidence paths are read on demand when you need to verify a citation, resolve ambiguity, or inspect material the packet says it omitted.
12
12
 
13
13
  ### Audience-scoped enumeration (BLOCKING — performance optimization)
14
14
 
@@ -16,16 +16,16 @@ Different recipients need different files. Do NOT include `final-report-template
16
16
 
17
17
  | Recipient | Files included in `[Required reading]` |
18
18
  |---|---|
19
- | Claude / Codex / Gemini analysis workers | task-brief, analysis-profile, analysis-material (if present), reference-expectations, clarification-response (if carry-in) |
19
+ | Claude / Codex / Gemini analysis workers | analysis-packet.md as the primary compact input; task-brief, analysis-profile, analysis-material, reference-expectations, and clarification-response remain source/fallback paths, not automatic first-read files |
20
20
  | Report writer worker (Phase 6) | all of the above **plus** the instruction-set-local `final-report-template.md` (phase-stripped) and `final-report-schema.json` (per-task-type excerpt) — NOT the full `templates/reports/...` / `schemas/...` sources |
21
21
  | Reverify dispatches (Phase 5.5, lightweight mode) | **do NOT inject `[Required reading]` at all** — see [okstra-convergence](../skills/okstra-convergence/SKILL.md) "Reverify prompt: required-reading suppression". |
22
22
 
23
23
  ### Reading rules
24
24
 
25
25
  - Use a single `Read` tool call per file with no `offset` and no `limit`. If a file is genuinely too large for one read, page through with explicit `offset` / `limit` covering the entire file, and state the page boundaries in your Findings.
26
- - For the carry-in clarification response, walk every row of `## 5. Clarification Items` (`C-001`, `C-002`, ...) in full, including rows whose `User input` cell is blank — a blank `User input` with `Status=open` is itself a signal you must surface. The structural similarity between the prior final report and the upcoming output is NOT a license to skim.
26
+ - For the carry-in clarification response, walk every row of `## 1. Clarification Items` (`C-001`, `C-002`, ...) in full, including rows whose `User input` cell is blank — a blank `User input` with `Status=open` is itself a signal you must surface. The structural similarity between the prior final report and the upcoming output is NOT a license to skim.
27
27
  - Write the Reading Confirmation block to your **audit sidecar** at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md` (sibling to the main worker-results file). One short line per input file confirming end-to-end reading. Do NOT include a `## 0. Reading Confirmation` heading in the main worker-results file — the validator fails worker-results that contain one. If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
28
- - Do not collapse multiple input files into a single mental summary before reading them all individually. Each file has its own canonical role: brief = the user's request, profile = the lead's rules for this phase, reference-expectations = ground-truth config/deployment values, clarification-response = prior run's open questions and the user's answers, final-report-template = the structure your eventual writeup must conform to. Conflating them loses signal.
28
+ - Treat `analysis-packet.md` as the canonical primary analysis input. It preserves the source files' frontmatter and extracts the task-specific brief, phase focus, reference expectations, and carry-in clarification rows. If the packet appears incomplete or a finding depends on a source citation, open the corresponding source file and cite it directly.
29
29
 
30
30
  ---
31
31
 
@@ -350,13 +350,13 @@ report_lines.extend(
350
350
  "| **전체 합계** | **`2`** | **`2`** | **`$0.02`** |",
351
351
  "| Codex/Gemini CLI 추가 비용 | | | `$0.00` |",
352
352
  "",
353
- "## 2. Final Verdict",
353
+ "## 7. Final Verdict",
354
354
  "",
355
355
  "| 항목 | 값 |",
356
356
  "|------|----|",
357
357
  "| Verdict Token | `accepted` |",
358
358
  "",
359
- "## 4.8 Final Verification Deliverables",
359
+ "## 5.8 Final Verification Deliverables",
360
360
  "",
361
361
  "Source Implementation Report / Acceptance Blockers / Residual Risk / "
362
362
  "Validation Evidence / Read-only Command Log / Conditional Acceptance "
@@ -12,9 +12,9 @@ from dataclasses import dataclass
12
12
  from pathlib import Path
13
13
  from typing import List, Tuple
14
14
 
15
- STAGE_MAP_HEADING = re.compile(r"^##\s+4\.5\s+Stage\s+Map\b", re.M)
15
+ STAGE_MAP_HEADING = re.compile(r"^##\s+5\.5\s+Stage\s+Map\b", re.M)
16
16
  STAGE_SECTION = re.compile(
17
- r"^##\s+4\.5\.(\d+)\s+Stage\s+\1\s*:\s*(.+)$", re.M
17
+ r"^##\s+5\.5\.(\d+)\s+Stage\s+\1\s*:\s*(.+)$", re.M
18
18
  )
19
19
  REQUIRED_SUBSECTIONS = (
20
20
  "Carry-In",
@@ -48,7 +48,7 @@ class ValidationError:
48
48
  def _check_stage_map_present(text: str) -> List[ValidationError]:
49
49
  if not STAGE_MAP_HEADING.search(text):
50
50
  return [ValidationError("S1", 0,
51
- "section '## 4.5 Stage Map' is missing")]
51
+ "section '## 5.5 Stage Map' is missing")]
52
52
  return []
53
53
 
54
54
 
@@ -91,15 +91,15 @@ def _parse_stage_map(text: str) -> Tuple[List[StageMeta], List[ValidationError]]
91
91
 
92
92
 
93
93
  def _slice_stage_section(text: str, stage_number: int) -> str:
94
- """Return the body of `## 4.5.<n> Stage <n>:` up to the next stage heading."""
94
+ """Return the body of `## 5.5.<n> Stage <n>:` up to the next stage heading."""
95
95
  start_m = re.search(
96
- rf"^##\s+4\.5\.{stage_number}\s+Stage\s+{stage_number}\s*:", text, re.M
96
+ rf"^##\s+5\.5\.{stage_number}\s+Stage\s+{stage_number}\s*:", text, re.M
97
97
  )
98
98
  if not start_m:
99
99
  return ""
100
100
  start = start_m.end()
101
101
  nxt = re.search(
102
- rf"^##\s+4\.5\.{stage_number + 1}\s+Stage\s+", text[start:], re.M
102
+ rf"^##\s+5\.5\.{stage_number + 1}\s+Stage\s+", text[start:], re.M
103
103
  )
104
104
  return text[start: start + nxt.start()] if nxt else text[start:]
105
105
 
@@ -134,10 +134,10 @@ def _check_each_stage_section(text: str, stages: List[StageMeta]) -> List[Valida
134
134
  errs: List[ValidationError] = []
135
135
  for s in stages:
136
136
  if not re.search(
137
- rf"^##\s+4\.5\.{s.stage_number}\s+Stage\s+{s.stage_number}\s*:", text, re.M
137
+ rf"^##\s+5\.5\.{s.stage_number}\s+Stage\s+{s.stage_number}\s*:", text, re.M
138
138
  ):
139
139
  errs.append(ValidationError("S3", s.stage_number,
140
- f"stage section '## 4.5.{s.stage_number} Stage {s.stage_number}:' missing"))
140
+ f"stage section '## 5.5.{s.stage_number} Stage {s.stage_number}:' missing"))
141
141
  continue
142
142
  section = _slice_stage_section(text, s.stage_number)
143
143
 
@@ -231,7 +231,7 @@ def _check_parallel_safety(text: str, stages: List[StageMeta]) -> List[Validatio
231
231
  def collect_validation_errors(text: str) -> List[ValidationError]:
232
232
  """All S1–S9 checks against the report text; empty list means valid.
233
233
 
234
- S1 (missing `## 4.5 Stage Map` heading) makes the rest unparseable, so it
234
+ S1 (missing `## 5.5 Stage Map` heading) makes the rest unparseable, so it
235
235
  short-circuits. Shared by `main()` (CLI / implementation entry) and the
236
236
  implementation-planning boundary check in validate-run.py — one validator,
237
237
  one reference point, enforced at both produce-time and consume-time."""
@@ -6,12 +6,12 @@ Checks, for a given final-report MD path:
6
6
  1. ``*.html`` sibling exists.
7
7
  2. HTML's ``source-sha256`` in run-meta matches the current MD body —
8
8
  stale html detection.
9
- 3. HTML's §4.6 / §4.7 / §4.8 deliverable regions contain no
9
+ 3. HTML's §5.6 / §5.7 / §5.8 deliverable regions contain no
10
10
  ``<textarea>`` / ``<input>`` / ``<select>`` (form-attach is
11
- restricted to §5 clarification rows).
11
+ restricted to §1 clarification rows).
12
12
  4. HTML has no external URLs in ``<script src=>`` / ``<link href=>`` /
13
13
  ``<img src=>`` — self-contained guarantee.
14
- 5. Every Response ID in HTML matches the §5 Clarification Items table
14
+ 5. Every Response ID in HTML matches the §1 Clarification Items table
15
15
  of the source MD (1:1).
16
16
 
17
17
  Exit codes: 0 on success, 1 on any failure. Failures are printed one
@@ -54,7 +54,7 @@ def _main_body(html_text: str) -> str:
54
54
 
55
55
  def _no_form_sections(html_body: str) -> list[str]:
56
56
  """Return a list of strings, each being the rendered chunk of a
57
- no-form section (4.6 / 4.7 / 4.8) up to the next h2/h3. Used to
57
+ no-form section (5.6 / 5.7 / 5.8) up to the next h2/h3. Used to
58
58
  check that no form controls live inside them.
59
59
  """
60
60
  chunks: list[str] = []
@@ -65,7 +65,7 @@ def _no_form_sections(html_body: str) -> list[str]:
65
65
  ]
66
66
  for i, m in enumerate(headings):
67
67
  text = m.group(2).strip()
68
- if not text.startswith(("4.6", "4.7", "4.8")):
68
+ if not text.startswith(("5.6", "5.7", "5.8")):
69
69
  continue
70
70
  start = m.end()
71
71
  end = headings[i + 1].start() if i + 1 < len(headings) else len(html_body)
@@ -82,9 +82,9 @@ def validate(report_path: Path) -> list[str]:
82
82
  html_path = report_path.with_name(report_path.stem + ".html")
83
83
  md_ids = _md_response_ids(md)
84
84
 
85
- # (1) sibling artifact exists — conditional on §5 clarification rows.
85
+ # (1) sibling artifact exists — conditional on §1 clarification rows.
86
86
  # The html view's sole value over the MD is its embedded form widgets
87
- # for §5 C-* rows, so a clarification-free report intentionally has no
87
+ # for §1 C-* rows, so a clarification-free report intentionally has no
88
88
  # html sibling (see report_views.report_has_clarification_items). When
89
89
  # there are no C-* rows and no html, that is the expected skip state.
90
90
  if not html_path.is_file():
@@ -115,7 +115,7 @@ def validate(report_path: Path) -> list[str]:
115
115
  for chunk in _no_form_sections(html_body):
116
116
  if "<textarea" in chunk or "<input" in chunk or "<select" in chunk:
117
117
  failures.append(
118
- "html §4.6/§4.7/§4.8 deliverable section contains a form control"
118
+ "html §5.6/§5.7/§5.8 deliverable section contains a form control"
119
119
  )
120
120
  break
121
121
 
@@ -123,14 +123,14 @@ def validate(report_path: Path) -> list[str]:
123
123
  if _EXTERNAL_URL_RE.search(html_text):
124
124
  failures.append("html contains external URL in script/link/img — must be self-contained")
125
125
 
126
- # (5) Response ID parity: HTML form rows ↔ §5 C-* rows in MD.
126
+ # (5) Response ID parity: HTML form rows ↔ §1 C-* rows in MD.
127
127
  # Bidirectional — catches both "MD has C-* the HTML lost" AND
128
128
  # "HTML has stale C-* that the current MD no longer declares".
129
129
  # (md_ids computed once at the top.)
130
130
  html_ids = sorted(set(_RESPONSE_ID_ATTR_RE.findall(html_text)))
131
131
  if md_ids != html_ids:
132
132
  failures.append(
133
- f"Response ID mismatch: MD §5 has {md_ids}, HTML has {html_ids}"
133
+ f"Response ID mismatch: MD §1 has {md_ids}, HTML has {html_ids}"
134
134
  )
135
135
 
136
136
  return failures
@@ -644,32 +644,32 @@ _DEPRECATED_FINAL_REPORT_PATTERNS: tuple[tuple[re.Pattern, str], ...] = (
644
644
  "the YAML frontmatter `approved: true|false` field. Delete the body section.",
645
645
  ),
646
646
  (
647
- re.compile(r"^###[ \t]+4\.5\.8[ \t]+User Approval Request\b", re.MULTILINE),
648
- "deprecated `### 4.5.8 User Approval Request` stub — approval gate moved "
649
- "to the YAML frontmatter `approved: true|false` field. Delete the §4.5.8 heading + body.",
647
+ re.compile(r"^###[ \t]+5\.5\.8[ \t]+User Approval Request\b", re.MULTILINE),
648
+ "deprecated `### 5.5.8 User Approval Request` stub — approval gate moved "
649
+ "to the YAML frontmatter `approved: true|false` field. Delete the §5.5.8 heading + body.",
650
650
  ),
651
651
  (
652
- re.compile(r"^###[ \t]+4\.5\.9[ \t]+Open Questions\b", re.MULTILINE),
653
- "deprecated `### 4.5.9 Open Questions` block — promote each row into "
654
- "`## 5. Clarification Items` with `Kind=decision` (and `Blocks=approval` "
652
+ re.compile(r"^###[ \t]+5\.5\.9[ \t]+Open Questions\b", re.MULTILINE),
653
+ "deprecated `### 5.5.9 Open Questions` block — promote each row into "
654
+ "`## 1. Clarification Items` with `Kind=decision` (and `Blocks=approval` "
655
655
  "if it gates the frontmatter approval flag).",
656
656
  ),
657
657
  (
658
658
  re.compile(
659
- r"^###[ \t]+5\.1[ \t]+(?:추가 자료 요청|Additional Materials)\b",
659
+ r"^###[ \t]+1\.1[ \t]+(?:추가 자료 요청|Additional Materials)\b",
660
660
  re.MULTILINE,
661
661
  ),
662
- "deprecated `### 5.1 추가 자료 요청` / `Additional Materials` sub-section — "
663
- "every clarification item lives as one row of the unified `## 5. "
662
+ "deprecated `### 1.1 추가 자료 요청` / `Additional Materials` sub-section — "
663
+ "every clarification item lives as one row of the unified `## 1. "
664
664
  "Clarification Items` 8-column table (`Kind=material`).",
665
665
  ),
666
666
  (
667
667
  re.compile(
668
- r"^###[ \t]+5\.2[ \t]+(?:사용자 확인 질문|Questions for the User)\b",
668
+ r"^###[ \t]+1\.2[ \t]+(?:사용자 확인 질문|Questions for the User)\b",
669
669
  re.MULTILINE,
670
670
  ),
671
- "deprecated `### 5.2 사용자 확인 질문` / `Questions for the User` "
672
- "sub-section — collapse into the unified `## 5. Clarification Items` "
671
+ "deprecated `### 1.2 사용자 확인 질문` / `Questions for the User` "
672
+ "sub-section — collapse into the unified `## 1. Clarification Items` "
673
673
  "8-column table (`Kind=decision`).",
674
674
  ),
675
675
  )
@@ -708,7 +708,7 @@ def validate_report(
708
708
  "final report is missing the top-of-report `## Verdict Card` block — "
709
709
  "render it between the report header and the (conditional) Approval "
710
710
  "block. Its Verdict Token / Direction / Next Step cells must byte-match "
711
- "the corresponding cells in `## 2. Final Verdict` and `## 6.` first item."
711
+ "the corresponding cells in `## 7. Final Verdict` and `## 3.` first item."
712
712
  )
713
713
 
714
714
  # Reading Confirmation belongs in the worker audit sidecar, not the
@@ -860,7 +860,7 @@ PLANNING_REQUIRED_SECTIONS = (
860
860
  "Plan Body Verification",
861
861
  )
862
862
 
863
- # §4.7 implementation deliverables — substring scan against report body.
863
+ # §5.7 implementation deliverables — substring scan against report body.
864
864
  IMPLEMENTATION_REQUIRED_SECTIONS = (
865
865
  "Approved Plan Reference",
866
866
  "Commit List",
@@ -872,7 +872,7 @@ IMPLEMENTATION_REQUIRED_SECTIONS = (
872
872
  "Routing Recommendation",
873
873
  )
874
874
 
875
- # §4.8 final-verification deliverables — substring scan against report body.
875
+ # §5.8 final-verification deliverables — substring scan against report body.
876
876
  FINAL_VERIFICATION_REQUIRED_SECTIONS = (
877
877
  "Source Implementation Report",
878
878
  "Acceptance Blockers",
@@ -892,7 +892,7 @@ FINAL_VERIFICATION_VERDICT_TOKENS = (
892
892
  "blocked",
893
893
  )
894
894
 
895
- # `## 2. Final Verdict` Verdict Token cell — captures the value between
895
+ # `## 7. Final Verdict` Verdict Token cell — captures the value between
896
896
  # backticks on the `Verdict Token` row. Tolerant to extra column whitespace
897
897
  # and to leading bold/italic markers in the label cell.
898
898
  _FINAL_VERDICT_TOKEN_RE = re.compile(
@@ -909,20 +909,20 @@ _VERDICT_CARD_BLOCK_RE = re.compile(
909
909
  re.DOTALL | re.MULTILINE,
910
910
  )
911
911
 
912
- # `## 2. Final Verdict` block scope — used to scope the Verdict Token
912
+ # `## 7. Final Verdict` block scope — used to scope the Verdict Token
913
913
  # regex so that we don't accidentally match a Verdict Token row that
914
914
  # lives in the Verdict Card or anywhere else.
915
915
  _FINAL_VERDICT_BLOCK_RE = re.compile(
916
- r"^##[ \t]+2\.[ \t]+Final Verdict[ \t]*$\n(?P<body>.*?)(?=^##[ \t]|\Z)",
916
+ r"^##[ \t]+7\.[ \t]+Final Verdict[ \t]*$\n(?P<body>.*?)(?=^##[ \t]|\Z)",
917
917
  re.DOTALL | re.MULTILINE,
918
918
  )
919
919
 
920
- # `## 4.6 Release Handoff Deliverables` and `## 4.6.6 Merge Conflict
920
+ # `## 5.6 Release Handoff Deliverables` and `## 5.6.6 Merge Conflict
921
921
  # Probe` are required when task-type == release-handoff. The probe sub-
922
922
  # section was retro-added to the template; old runs that predate it ship
923
923
  # without it, but new runs must include it.
924
924
  _MERGE_CONFLICT_PROBE_HEADING_RE = re.compile(
925
- r"^###[ \t]+4\.6\.6[ \t]+Merge Conflict Probe\b", re.MULTILINE
925
+ r"^###[ \t]+5\.6\.6[ \t]+Merge Conflict Probe\b", re.MULTILINE
926
926
  )
927
927
 
928
928
  PLAN_VERIFY_GATE_VALUES = (
@@ -932,7 +932,7 @@ PLAN_VERIFY_GATE_VALUES = (
932
932
  "aborted-non-result",
933
933
  )
934
934
 
935
- # `Gate result:` line in §4.5.9 of the final report — captures the value
935
+ # `Gate result:` line in §5.5.9 of the final report — captures the value
936
936
  # token that follows. Tolerates arbitrary markdown formatting between the
937
937
  # label and the value (backticks for inline code, double-asterisks for
938
938
  # bold, colons, hyphens, whitespace). The captured value is then
@@ -954,8 +954,8 @@ _FRONTMATTER_BLOCK_RE = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL)
954
954
 
955
955
 
956
956
  def _extract_final_verdict_token(content: str) -> str | None:
957
- """Return the `Verdict Token` cell value from the `## 2. Final Verdict`
958
- block, or None when the row is absent. Scoped to §2 so the Verdict
957
+ """Return the `Verdict Token` cell value from the `## 7. Final Verdict`
958
+ block, or None when the row is absent. Scoped to §7 so the Verdict
959
959
  Card row (which has the same shape) does not shadow the authoritative
960
960
  value.
961
961
  """
@@ -980,7 +980,7 @@ def _extract_verdict_card_token(content: str) -> str | None:
980
980
 
981
981
 
982
982
  def _validate_verdict_card_consistency(content: str, failures: list[str]) -> None:
983
- """Verdict Card is a non-authoritative index of §2. If both blocks
983
+ """Verdict Card is a non-authoritative index of §7. If both blocks
984
984
  carry a Verdict Token row, the values MUST byte-match (modulo case
985
985
  and surrounding whitespace) — divergence is a contract violation per
986
986
  `okstra-report-writer` SKILL.md "Authoring Contract".
@@ -988,15 +988,15 @@ def _validate_verdict_card_consistency(content: str, failures: list[str]) -> Non
988
988
  card_value = _extract_verdict_card_token(content)
989
989
  final_value = _extract_final_verdict_token(content)
990
990
  if card_value is None or final_value is None:
991
- # Missing-Card and missing-§2 are surfaced by other checks; this
991
+ # Missing-Card and missing-§7 are surfaced by other checks; this
992
992
  # function only enforces the consistency contract between the two.
993
993
  return
994
994
  if card_value.strip().lower() != final_value.strip().lower():
995
995
  failures.append(
996
996
  "Verdict Card `Verdict Token` value "
997
- f"`{card_value}` does not match `## 2. Final Verdict` value "
997
+ f"`{card_value}` does not match `## 7. Final Verdict` value "
998
998
  f"`{final_value}` — the Card is a non-authoritative index and "
999
- "MUST byte-match §2. Either fix the Card or update §2; do not "
999
+ "MUST byte-match §7. Either fix the Card or update §7; do not "
1000
1000
  "ship divergent values."
1001
1001
  )
1002
1002
 
@@ -1161,7 +1161,7 @@ def _load_stage_validator():
1161
1161
 
1162
1162
  def _append_stage_structure_failures(content: str, failures: list[str]) -> None:
1163
1163
  """Enforce the Stage Map structural contract at the implementation-planning
1164
- boundary. Without this, a plan missing `## 4.5 Stage Map` passes the
1164
+ boundary. Without this, a plan missing `## 5.5 Stage Map` passes the
1165
1165
  planning gate, gets approved, and only fails later at the `implementation`
1166
1166
  entry (validators/validate-implementation-plan-stages.py via
1167
1167
  prepare_task_bundle). Running the same validator here moves the failure to
@@ -1189,7 +1189,7 @@ def validate_phase_boundary(
1189
1189
  skipped its core outputs (or an implementation run that ran under the
1190
1190
  wrong task type).
1191
1191
 
1192
- Additionally enforces the Plan Body Verification gate (§4.5.9):
1192
+ Additionally enforces the Plan Body Verification gate (§5.5.9):
1193
1193
  - gate ∈ {passed, passed-with-dissent} → top-level Approval checkbox
1194
1194
  MUST be present.
1195
1195
  - gate ∈ {blocked-by-disagreement, aborted-non-result} → checkbox
@@ -1200,7 +1200,7 @@ def validate_phase_boundary(
1200
1200
  return
1201
1201
  content = report_path.read_text()
1202
1202
 
1203
- # Verdict Card vs §2. Final Verdict Verdict Token consistency. The Card
1203
+ # Verdict Card vs §7. Final Verdict Verdict Token consistency. The Card
1204
1204
  # is a non-authoritative index; divergence is a contract violation.
1205
1205
  _validate_verdict_card_consistency(content, failures)
1206
1206
 
@@ -1208,7 +1208,7 @@ def validate_phase_boundary(
1208
1208
  for needle in IMPLEMENTATION_REQUIRED_SECTIONS:
1209
1209
  if needle not in content:
1210
1210
  failures.append(
1211
- "implementation report is missing required §4.7 "
1211
+ "implementation report is missing required §5.7 "
1212
1212
  f"deliverable section: `{needle}`"
1213
1213
  )
1214
1214
 
@@ -1216,13 +1216,13 @@ def validate_phase_boundary(
1216
1216
  for needle in FINAL_VERIFICATION_REQUIRED_SECTIONS:
1217
1217
  if needle not in content:
1218
1218
  failures.append(
1219
- "final-verification report is missing required §4.8 "
1219
+ "final-verification report is missing required §5.8 "
1220
1220
  f"deliverable section: `{needle}`"
1221
1221
  )
1222
1222
  token_value = _extract_final_verdict_token(content)
1223
1223
  if token_value is None:
1224
1224
  failures.append(
1225
- "final-verification report `## 2. Final Verdict` table is "
1225
+ "final-verification report `## 7. Final Verdict` table is "
1226
1226
  "missing the `Verdict Token` row — required by the release-"
1227
1227
  "handoff entry gate."
1228
1228
  )
@@ -1236,7 +1236,7 @@ def validate_phase_boundary(
1236
1236
  if task_type == "release-handoff":
1237
1237
  if _MERGE_CONFLICT_PROBE_HEADING_RE.search(content) is None:
1238
1238
  failures.append(
1239
- "release-handoff report is missing `### 4.6.6 Merge Conflict "
1239
+ "release-handoff report is missing `### 5.6.6 Merge Conflict "
1240
1240
  "Probe` sub-section — required by the release-handoff profile "
1241
1241
  "(self-review 6, merge-conflict probe audit). When the run is "
1242
1242
  "`local only` / `skip`, record the single line `- Not run "
@@ -1260,7 +1260,7 @@ def validate_phase_boundary(
1260
1260
  if "Plan Body Verification" in content:
1261
1261
  failures.append(
1262
1262
  "implementation-planning report has `Plan Body Verification` "
1263
- "section but no `Gate result:` line — required by §4.5.9."
1263
+ "section but no `Gate result:` line — required by §5.5.9."
1264
1264
  )
1265
1265
  return
1266
1266
  gate_value = gate_match.group("value").strip().lower()
@@ -1655,7 +1655,7 @@ def _rerender_report_views_after_autofix(report_path: Path) -> str:
1655
1655
  except Exception as exc: # noqa: BLE001
1656
1656
  return f"report-views re-render failed: {exc}"
1657
1657
  if rendered is None:
1658
- return "report-views skipped (no §5 clarification rows)"
1658
+ return "report-views skipped (no §1 clarification rows)"
1659
1659
  return "report-views re-rendered"
1660
1660
 
1661
1661
 
@@ -205,9 +205,9 @@ def _check_candidates_table(
205
205
  body: str, brief_frontmatter: dict, errors: list[str]
206
206
  ) -> None:
207
207
  """Items 1–7 coordinator — parse table, check header, delegate cap and rows."""
208
- rows = _read_section_table(body, "4.9 Improvement Candidates")
208
+ rows = _read_section_table(body, "5.9 Improvement Candidates")
209
209
  if not rows:
210
- errors.append("missing or empty `## 4.9 Improvement Candidates` table")
210
+ errors.append("missing or empty `## 5.9 Improvement Candidates` table")
211
211
  return
212
212
  header, *data = rows
213
213
  expected_columns = [
@@ -216,7 +216,7 @@ def _check_candidates_table(
216
216
  ]
217
217
  if header != expected_columns:
218
218
  errors.append(
219
- f"`## 4.9 Improvement Candidates` header must be {expected_columns}, "
219
+ f"`## 5.9 Improvement Candidates` header must be {expected_columns}, "
220
220
  f"got {header}"
221
221
  )
222
222
  _check_candidates_cap(len(data), brief_frontmatter, errors)
@@ -228,18 +228,18 @@ def _check_candidates_table(
228
228
  def _check_final_verdict(
229
229
  body: str, errors: list[str]
230
230
  ) -> list[list[str]]:
231
- """Item 8 — ## 2. Final Verdict verdict token is in enum.
231
+ """Item 8 — ## 7. Final Verdict verdict token is in enum.
232
232
 
233
233
  Returns the parsed final_verdict_rows (empty list when absent).
234
234
  """
235
- final_verdict_rows = _read_section_table(body, "2. Final Verdict")
235
+ final_verdict_rows = _read_section_table(body, "7. Final Verdict")
236
236
  if not final_verdict_rows:
237
- errors.append("missing `## 2. Final Verdict` block")
237
+ errors.append("missing `## 7. Final Verdict` block")
238
238
  return []
239
239
  token_cell = final_verdict_rows[-1][0] if final_verdict_rows[-1] else ""
240
240
  if token_cell not in _VERDICT_TOKENS:
241
241
  errors.append(
242
- f"`## 2. Final Verdict` Verdict Token '{token_cell}' must be one of {_VERDICT_TOKENS}"
242
+ f"`## 7. Final Verdict` Verdict Token '{token_cell}' must be one of {_VERDICT_TOKENS}"
243
243
  )
244
244
  return final_verdict_rows
245
245
 
@@ -253,7 +253,7 @@ def _check_verdict_card(
253
253
  errors.append("missing `## Verdict Card` block")
254
254
  return
255
255
  if final_verdict_rows and verdict_card_rows[-1] != final_verdict_rows[-1]:
256
- errors.append("Verdict Card row must byte-match `## 2. Final Verdict` row")
256
+ errors.append("Verdict Card row must byte-match `## 7. Final Verdict` row")
257
257
 
258
258
 
259
259
  def validate_improvement_report(
@@ -1,12 +1,12 @@
1
1
  import { spawn } from "node:child_process";
2
- import { resolvePaths } from "./paths.mjs";
2
+ import { buildPythonpath, resolvePaths } from "./paths.mjs";
3
3
 
4
4
  export async function runPythonSnippet({ script, args = [], extraEnv = {} }) {
5
5
  const paths = await resolvePaths();
6
6
  return new Promise((resolve) => {
7
7
  const child = spawn("python3", ["-c", script, ...args], {
8
8
  stdio: ["ignore", "pipe", "pipe"],
9
- env: { ...process.env, PYTHONPATH: paths.pythonpath, ...extraEnv },
9
+ env: { ...process.env, PYTHONPATH: buildPythonpath(paths), ...extraEnv },
10
10
  });
11
11
  let stdout = "";
12
12
  let stderr = "";
@@ -22,7 +22,7 @@ export async function runPythonModule({ module, args = [], extraEnv = {}, stdio
22
22
  return new Promise((resolve) => {
23
23
  const child = spawn("python3", ["-m", module, ...args], {
24
24
  stdio: stdio === "capture" ? ["ignore", "pipe", "pipe"] : ["ignore", "inherit", "inherit"],
25
- env: { ...process.env, PYTHONPATH: paths.pythonpath, ...extraEnv },
25
+ env: { ...process.env, PYTHONPATH: buildPythonpath(paths), ...extraEnv },
26
26
  });
27
27
  let stdout = "";
28
28
  let stderr = "";
@@ -0,0 +1,27 @@
1
+ import { runPythonModule } from "./_python-helper.mjs";
2
+
3
+ const USAGE = `okstra context-cost — estimate context/read cost for a task bundle
4
+
5
+ Usage:
6
+ okstra context-cost <task-root>
7
+ okstra context-cost <task-key> --project-root <dir>
8
+ okstra context-cost <task-key> --cwd <dir>
9
+
10
+ Output: JSON with task file counts, current-run counts, lead Phase 1 read
11
+ surface, analysis-worker initial read surface, and report-writer synthesis
12
+ surface. This is read-only; it never mutates task artifacts.
13
+ `;
14
+
15
+ export async function run(args) {
16
+ if (args.includes("--help") || args.includes("-h")) {
17
+ process.stdout.write(USAGE);
18
+ return 0;
19
+ }
20
+
21
+ const result = await runPythonModule({
22
+ module: "okstra_ctl.context_cost",
23
+ args,
24
+ stdio: "inherit-stdout",
25
+ });
26
+ return result.code ?? 0;
27
+ }
package/src/install.mjs CHANGED
@@ -22,6 +22,7 @@ const BIN_ENTRYPOINTS = [
22
22
  "okstra-codex-exec.sh",
23
23
  "okstra-gemini-exec.sh",
24
24
  "okstra-trace-cleanup.sh",
25
+ "okstra-team-reconcile.sh",
25
26
  "okstra-central.sh",
26
27
  "okstra-token-usage.py",
27
28
  "okstra-error-log.py",
package/src/uninstall.mjs CHANGED
@@ -8,6 +8,7 @@ const BIN_ENTRYPOINTS = [
8
8
  "okstra-codex-exec.sh",
9
9
  "okstra-gemini-exec.sh",
10
10
  "okstra-trace-cleanup.sh",
11
+ "okstra-team-reconcile.sh",
11
12
  "okstra-central.sh",
12
13
  "okstra-token-usage.py",
13
14
  "okstra-error-log.py",