okstra 0.102.2 → 0.102.3

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 (161) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +7 -11
  5. package/docs/kr/cli.md +4 -4
  6. package/docs/kr/container.md +1 -1
  7. package/docs/kr/performance-improvement-plan-v2.md +2 -2
  8. package/docs/project-structure-overview.md +2 -2
  9. package/package.json +7 -2
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  12. package/runtime/bin/okstra-codex-exec.sh +1 -2
  13. package/runtime/bin/okstra-render-final-report.py +2 -9
  14. package/runtime/bin/okstra-spawn-followups.py +4 -6
  15. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/report-writer.md +1 -1
  18. package/runtime/prompts/profiles/_common-contract.md +9 -11
  19. package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
  20. package/runtime/python/okstra_ctl/consumers.py +35 -0
  21. package/runtime/python/okstra_ctl/container.py +4 -6
  22. package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
  23. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  24. package/runtime/python/okstra_ctl/handoff.py +2 -1
  25. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  26. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  27. package/runtime/python/okstra_ctl/report_views.py +7 -4
  28. package/runtime/python/okstra_ctl/run.py +3 -10
  29. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  30. package/runtime/python/okstra_ctl/wizard.py +24 -25
  31. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  32. package/runtime/python/okstra_token_usage/report.py +2 -5
  33. package/runtime/validators/validate-report-views.py +2 -1
  34. package/runtime/validators/validate-run.py +1 -11
  35. package/src/commands/lifecycle/install.mjs +5 -5
  36. package/src/commands/lifecycle/uninstall.mjs +4 -4
  37. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  38. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  39. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  40. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  41. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  42. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  43. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  44. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  45. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  46. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  47. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  48. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  49. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  50. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  52. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  53. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  54. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  55. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  56. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  57. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  58. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  59. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  60. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  61. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  62. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  63. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  68. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  76. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  77. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  78. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  79. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  81. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  82. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  83. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  84. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  85. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  86. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  87. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  88. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  89. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  93. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  95. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  96. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  97. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  98. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  99. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  100. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  101. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  102. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  103. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  104. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  105. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  106. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  107. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  108. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  109. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  110. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  111. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  112. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  113. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  114. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  115. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  116. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  117. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  119. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  120. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  121. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  122. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  123. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  124. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  125. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  126. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  127. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  128. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  129. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  130. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  131. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  132. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  133. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  134. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  135. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  136. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  137. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  138. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  139. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  140. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  141. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  142. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  143. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  144. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  145. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  146. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  147. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  148. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  149. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  150. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  151. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  152. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  153. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  154. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  155. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  156. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  157. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  158. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  159. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  160. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  161. package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
@@ -1,647 +0,0 @@
1
- # okstra-inspect `recap` facet 구현 계획
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** okstra-inspect 에 task-id 단위 전/후 요약 + 자유 Q&A 를 제공하는 8번째 facet `recap` 을 추가한다.
6
-
7
- **Architecture:** 결정론적 부분(timeline run 간 phase 전이 계산 = `recap assemble`, recap-log append = `recap record`)은 python `okstra_ctl/recap.py` 에 두고 단위 테스트로 검증한다. 요약 서술과 자유 Q&A 는 skill-markdown(okstra-inspect)이 그 산출물 위에서 수행한다. 경로는 SSOT 인 `compute_run_paths` 에만 추가한다.
8
-
9
- **Tech Stack:** Python 3 (argparse, 4-space indent, snake_case) · Node ES modules (`src/*.mjs`, 2-space indent) · skill markdown · pytest.
10
-
11
- ## Global Constraints
12
-
13
- - `runtime/` 는 빌드 산출물 — 절대 손으로 수정하지 않는다. 소스는 `scripts/`, `skills/`, `src/` 등에서만 고치고 `npm run build` 로 동기화한다.
14
- - 모든 okstra 산출물은 `<PROJECT_ROOT>/.okstra/` 안에만 쓴다. recap 은 `<task-root>/recap/recap-log.jsonl` 만 append 하고 다른 task 산출물(`task-manifest.json`, catalog, timeline)은 mutate 하지 않는다.
15
- - read-side python 모듈은 task-key/ task-root 입력 계약을 `okstra_ctl.task_target.resolve_task_root(target, project_root, cwd)` 로 공유한다 (context-cost / error-report 와 동일).
16
- - 사용자-노출 문서·주석 산문은 한국어, 코드 식별자/경로/CLI 인자는 영어.
17
- - 새 함수는 유효 코드 50줄 이하로 유지한다.
18
- - 타임스탬프는 `datetime.datetime.now(datetime.timezone.utc).isoformat()` (error_report 와 동일 패턴).
19
-
20
- ---
21
-
22
- ### Task 1: `compute_run_paths` 에 recap 경로 추가
23
-
24
- **Files:**
25
- - Modify: `scripts/okstra_ctl/paths.py` (task-root 레벨 경로 블록 `paths.py:122-130`, `abs_paths` 딕셔너리 `paths.py:206-302`, `rel_pairs` 리스트 `paths.py:303-350`)
26
- - Test: `tests/test_okstra_ctl_paths.py`
27
-
28
- **Interfaces:**
29
- - Consumes: 기존 `compute_run_paths(...)` 시그니처와 `task_root` 지역변수.
30
- - Produces: 컨텍스트 딕셔너리에 새 키 `RECAP_DIR`, `RECAP_LOG_PATH`(절대), `RECAP_DIR_RELATIVE_PATH`, `RECAP_LOG_RELATIVE_PATH`(project-root 상대). 경로 규약: `<task-root>/recap/` 디렉터리와 `<task-root>/recap/recap-log.jsonl` 파일.
31
-
32
- - [ ] **Step 1: Write the failing test**
33
-
34
- `tests/test_okstra_ctl_paths.py` 끝에 추가:
35
-
36
- ```python
37
- def test_recap_paths_present():
38
- ctx = compute_run_paths(**_common())
39
- assert ctx["RECAP_DIR"].endswith("/.okstra/tasks/g/t/recap")
40
- assert ctx["RECAP_LOG_PATH"].endswith("/.okstra/tasks/g/t/recap/recap-log.jsonl")
41
- assert ctx["RECAP_DIR_RELATIVE_PATH"] == ".okstra/tasks/g/t/recap"
42
- assert ctx["RECAP_LOG_RELATIVE_PATH"] == ".okstra/tasks/g/t/recap/recap-log.jsonl"
43
- ```
44
-
45
- - [ ] **Step 2: Run test to verify it fails**
46
-
47
- Run: `python3 -m pytest tests/test_okstra_ctl_paths.py::test_recap_paths_present -v`
48
- Expected: FAIL with `KeyError: 'RECAP_DIR'`.
49
-
50
- - [ ] **Step 3: Add the path locals**
51
-
52
- `scripts/okstra_ctl/paths.py` 의 `timeline_file = history_dir / "timeline.json"` (`paths.py:130`) 바로 다음 줄에 추가:
53
-
54
- ```python
55
- recap_dir = task_root / "recap"
56
- recap_log = recap_dir / "recap-log.jsonl"
57
- ```
58
-
59
- - [ ] **Step 4: Register absolute keys**
60
-
61
- `abs_paths` 딕셔너리에서 `"TIMELINE_PATH": str(timeline_file),` (`paths.py:249`) 다음 줄에 추가:
62
-
63
- ```python
64
- "RECAP_DIR": str(recap_dir),
65
- "RECAP_LOG_PATH": str(recap_log),
66
- ```
67
-
68
- - [ ] **Step 5: Register relative keys**
69
-
70
- `rel_pairs` 리스트에서 `("TIMELINE_RELATIVE_PATH", timeline_file),` (`paths.py:314`) 다음 줄에 추가:
71
-
72
- ```python
73
- ("RECAP_DIR_RELATIVE_PATH", recap_dir),
74
- ("RECAP_LOG_RELATIVE_PATH", recap_log),
75
- ```
76
-
77
- - [ ] **Step 6: Run test to verify it passes**
78
-
79
- Run: `python3 -m pytest tests/test_okstra_ctl_paths.py -v`
80
- Expected: PASS (신규 + 기존 테스트 모두).
81
-
82
- - [ ] **Step 7: Commit**
83
-
84
- ```bash
85
- git add scripts/okstra_ctl/paths.py tests/test_okstra_ctl_paths.py
86
- git commit -m "feat(paths): compute_run_paths 에 recap 디렉터리/로그 경로 추가"
87
- ```
88
-
89
- ---
90
-
91
- ### Task 2: `recap assemble` — timeline run 간 phase 전이 조립
92
-
93
- **Files:**
94
- - Create: `scripts/okstra_ctl/recap.py`
95
- - Test: `tests/test_okstra_recap.py`
96
-
97
- **Interfaces:**
98
- - Consumes: `okstra_ctl.task_target.resolve_task_root`, `project_rel`. task-root 의 `history/timeline.json`(`{"runs": [ {runTimestamp, taskType, status, reportPath, workflowSnapshot:{currentPhase, lastCompletedPhase, nextRecommendedPhase, phaseStates}} ... ]}`).
99
- - Produces: `assemble_recap(task_root: Path, project_root: Path) -> dict` 반환:
100
- ```
101
- {
102
- "taskKey": str,
103
- "runCount": int,
104
- "transitions": [ {"index": int, "runTimestamp": str, "taskType": str,
105
- "status": str, "fromPhase": str, "toPhase": str,
106
- "lastCompletedPhase": str, "nextRecommendedPhase": str,
107
- "reportPath": str} ... ],
108
- "latestPhaseStates": dict
109
- }
110
- ```
111
- `fromPhase` = 직전 run 의 `currentPhase`(첫 run 은 `""`), `toPhase` = 해당 run 의 `currentPhase`.
112
-
113
- - [ ] **Step 1: Write the failing test**
114
-
115
- `tests/test_okstra_recap.py` 생성:
116
-
117
- ```python
118
- import json
119
- import sys
120
- from pathlib import Path
121
-
122
- LIB_DIR = Path(__file__).resolve().parents[1] / "scripts"
123
- if str(LIB_DIR) not in sys.path:
124
- sys.path.insert(0, str(LIB_DIR))
125
-
126
- from okstra_ctl import recap
127
-
128
-
129
- def _run(ts, current, last="", nxt="", task_type="implementation",
130
- status="done", report=""):
131
- return {
132
- "runTimestamp": ts, "taskType": task_type, "status": status,
133
- "reportPath": report,
134
- "workflowSnapshot": {
135
- "currentPhase": current, "lastCompletedPhase": last,
136
- "nextRecommendedPhase": nxt, "phaseStates": {current: "done"},
137
- },
138
- }
139
-
140
-
141
- def _write_timeline(task_root: Path, runs: list[dict]) -> None:
142
- hist = task_root / "history"
143
- hist.mkdir(parents=True, exist_ok=True)
144
- (hist / "timeline.json").write_text(
145
- json.dumps({"runs": runs}, ensure_ascii=False), encoding="utf-8")
146
- (task_root / "task-manifest.json").write_text(
147
- json.dumps({"taskKey": "p:g:DEV-1"}, ensure_ascii=False), encoding="utf-8")
148
-
149
-
150
- def test_assemble_computes_adjacent_transitions(tmp_path):
151
- _write_timeline(tmp_path, [
152
- _run("2026-06-01T00:00:00+00:00", "requirements-discovery",
153
- nxt="error-analysis"),
154
- _run("2026-06-02T00:00:00+00:00", "error-analysis",
155
- last="requirements-discovery", nxt="implementation-planning"),
156
- ])
157
- result = recap.assemble_recap(tmp_path, tmp_path)
158
- assert result["taskKey"] == "p:g:DEV-1"
159
- assert result["runCount"] == 2
160
- assert result["transitions"][0]["fromPhase"] == ""
161
- assert result["transitions"][0]["toPhase"] == "requirements-discovery"
162
- assert result["transitions"][1]["fromPhase"] == "requirements-discovery"
163
- assert result["transitions"][1]["toPhase"] == "error-analysis"
164
- assert result["latestPhaseStates"] == {"error-analysis": "done"}
165
-
166
-
167
- def test_assemble_empty_timeline(tmp_path):
168
- (tmp_path / "history").mkdir(parents=True)
169
- (tmp_path / "history" / "timeline.json").write_text(
170
- json.dumps({"runs": []}), encoding="utf-8")
171
- (tmp_path / "task-manifest.json").write_text(
172
- json.dumps({"taskKey": "p:g:DEV-1"}), encoding="utf-8")
173
- result = recap.assemble_recap(tmp_path, tmp_path)
174
- assert result["runCount"] == 0
175
- assert result["transitions"] == []
176
- assert result["latestPhaseStates"] == {}
177
- ```
178
-
179
- - [ ] **Step 2: Run test to verify it fails**
180
-
181
- Run: `python3 -m pytest tests/test_okstra_recap.py -v`
182
- Expected: FAIL with `ModuleNotFoundError: No module named 'okstra_ctl.recap'`.
183
-
184
- - [ ] **Step 3: Write the module with assemble**
185
-
186
- `scripts/okstra_ctl/recap.py` 생성:
187
-
188
- ```python
189
- """Read-side recap for a task: assemble run-to-run phase transitions and
190
- append a recap Q&A log. Mutates only <task-root>/recap/recap-log.jsonl;
191
- never touches task-manifest / catalog / timeline.
192
- """
193
- from __future__ import annotations
194
-
195
- import argparse
196
- import datetime as dt
197
- import json
198
- import sys
199
- from pathlib import Path
200
-
201
- from okstra_ctl.task_target import resolve_task_root, project_rel
202
-
203
-
204
- def _load_timeline(task_root: Path) -> list[dict]:
205
- path = task_root / "history" / "timeline.json"
206
- if not path.is_file():
207
- return []
208
- try:
209
- data = json.loads(path.read_text(encoding="utf-8"))
210
- except Exception:
211
- return []
212
- runs = data.get("runs", [])
213
- return runs if isinstance(runs, list) else []
214
-
215
-
216
- def _task_key(task_root: Path) -> str:
217
- path = task_root / "task-manifest.json"
218
- if not path.is_file():
219
- return ""
220
- try:
221
- return json.loads(path.read_text(encoding="utf-8")).get("taskKey", "")
222
- except Exception:
223
- return ""
224
-
225
-
226
- def assemble_recap(task_root: Path, project_root: Path) -> dict:
227
- runs = _load_timeline(task_root)
228
- transitions = []
229
- prev_phase = ""
230
- latest_states: dict = {}
231
- for idx, run in enumerate(runs):
232
- snap = run.get("workflowSnapshot", {}) if isinstance(run, dict) else {}
233
- cur = snap.get("currentPhase", "")
234
- transitions.append({
235
- "index": idx,
236
- "runTimestamp": run.get("runTimestamp", ""),
237
- "taskType": run.get("taskType", ""),
238
- "status": run.get("status", ""),
239
- "fromPhase": prev_phase,
240
- "toPhase": cur,
241
- "lastCompletedPhase": snap.get("lastCompletedPhase", ""),
242
- "nextRecommendedPhase": snap.get("nextRecommendedPhase", ""),
243
- "reportPath": run.get("reportPath", ""),
244
- })
245
- prev_phase = cur
246
- latest_states = snap.get("phaseStates", {}) or latest_states
247
- return {
248
- "taskKey": _task_key(task_root),
249
- "runCount": len(runs),
250
- "transitions": transitions,
251
- "latestPhaseStates": latest_states,
252
- }
253
- ```
254
-
255
- - [ ] **Step 4: Run test to verify it passes**
256
-
257
- Run: `python3 -m pytest tests/test_okstra_recap.py -v`
258
- Expected: PASS (2 tests).
259
-
260
- - [ ] **Step 5: Commit**
261
-
262
- ```bash
263
- git add scripts/okstra_ctl/recap.py tests/test_okstra_recap.py
264
- git commit -m "feat(recap): timeline run 간 phase 전이 조립(assemble_recap)"
265
- ```
266
-
267
- ---
268
-
269
- ### Task 3: `recap record` — recap-log.jsonl append + CLI 진입점
270
-
271
- **Files:**
272
- - Modify: `scripts/okstra_ctl/recap.py`
273
- - Test: `tests/test_okstra_recap.py`
274
-
275
- **Interfaces:**
276
- - Consumes: Task 2 의 `assemble_recap`, `resolve_task_root`, `project_rel`.
277
- - Produces:
278
- - `append_recap_entry(task_root, project_root, *, kind, mode, question, answer_summary, citations, now) -> dict` — `<task-root>/recap/recap-log.jsonl` 에 한 줄 append, `{"logPath": <project-rel>, "entry": <written dict>}` 반환. entry 스키마: `{ts, kind, mode, question, answerSummary, citations}`.
279
- - `main(argv)` — `okstra recap` 의 두 서브커맨드(`assemble`, `record`)를 argparse 로 디스패치하고 결과 JSON 을 stdout 에 출력.
280
-
281
- - [ ] **Step 1: Write the failing test**
282
-
283
- `tests/test_okstra_recap.py` 에 추가:
284
-
285
- ```python
286
- def test_append_recap_entry_appends_one_line(tmp_path):
287
- (tmp_path / "task-manifest.json").write_text(
288
- json.dumps({"taskKey": "p:g:DEV-1"}), encoding="utf-8")
289
- now = dt.datetime(2026, 6, 19, tzinfo=dt.timezone.utc)
290
- first = recap.append_recap_entry(
291
- tmp_path, tmp_path, kind="qa", mode="artifact",
292
- question="무엇을 했나?", answer_summary="phase 2 까지 진행",
293
- citations=["history/timeline.json:1"], now=now)
294
- second = recap.append_recap_entry(
295
- tmp_path, tmp_path, kind="summary", mode="code",
296
- question="", answer_summary="요약", citations=[], now=now)
297
- log = tmp_path / "recap" / "recap-log.jsonl"
298
- lines = [l for l in log.read_text(encoding="utf-8").splitlines() if l.strip()]
299
- assert len(lines) == 2
300
- rec0 = json.loads(lines[0])
301
- assert rec0["kind"] == "qa"
302
- assert rec0["mode"] == "artifact"
303
- assert rec0["question"] == "무엇을 했나?"
304
- assert rec0["answerSummary"] == "phase 2 까지 진행"
305
- assert rec0["citations"] == ["history/timeline.json:1"]
306
- assert rec0["ts"] == "2026-06-19T00:00:00+00:00"
307
- assert first["logPath"].endswith("recap/recap-log.jsonl")
308
- assert second["entry"]["kind"] == "summary"
309
-
310
-
311
- def test_main_assemble_prints_json(tmp_path, capsys):
312
- _write_timeline(tmp_path, [
313
- _run("2026-06-01T00:00:00+00:00", "implementation"),
314
- ])
315
- rc = recap.main(["assemble", str(tmp_path)])
316
- out = json.loads(capsys.readouterr().out)
317
- assert rc == 0
318
- assert out["runCount"] == 1
319
-
320
-
321
- def test_main_record_appends_and_prints(tmp_path, capsys):
322
- (tmp_path / "task-manifest.json").write_text(
323
- json.dumps({"taskKey": "p:g:DEV-1"}), encoding="utf-8")
324
- rc = recap.main([
325
- "record", str(tmp_path), "--kind", "qa", "--mode", "artifact",
326
- "--question", "q?", "--answer", "a.",
327
- "--citation", "x:1", "--citation", "y:2",
328
- ])
329
- out = json.loads(capsys.readouterr().out)
330
- assert rc == 0
331
- assert out["entry"]["citations"] == ["x:1", "y:2"]
332
- log = tmp_path / "recap" / "recap-log.jsonl"
333
- assert len([l for l in log.read_text().splitlines() if l.strip()]) == 1
334
- ```
335
-
336
- - [ ] **Step 2: Run test to verify it fails**
337
-
338
- Run: `python3 -m pytest tests/test_okstra_recap.py -v`
339
- Expected: FAIL with `AttributeError: module 'okstra_ctl.recap' has no attribute 'append_recap_entry'`.
340
-
341
- - [ ] **Step 3: Add append + main**
342
-
343
- `scripts/okstra_ctl/recap.py` 끝(`assemble_recap` 다음)에 추가:
344
-
345
- ```python
346
- def append_recap_entry(task_root, project_root, *, kind, mode, question,
347
- answer_summary, citations, now) -> dict:
348
- recap_dir = task_root / "recap"
349
- recap_dir.mkdir(parents=True, exist_ok=True)
350
- log_path = recap_dir / "recap-log.jsonl"
351
- entry = {
352
- "ts": now.isoformat(),
353
- "kind": kind,
354
- "mode": mode,
355
- "question": question,
356
- "answerSummary": answer_summary,
357
- "citations": list(citations),
358
- }
359
- with log_path.open("a", encoding="utf-8") as fh:
360
- fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
361
- return {"logPath": project_rel(log_path, project_root), "entry": entry}
362
-
363
-
364
- def main(argv: list[str] | None = None) -> int:
365
- parser = argparse.ArgumentParser(prog="okstra recap")
366
- parser.add_argument("--project-root", default="")
367
- parser.add_argument("--cwd", default=".")
368
- sub = parser.add_subparsers(dest="command", required=True)
369
-
370
- p_asm = sub.add_parser("assemble", help="assemble run-to-run phase transitions")
371
- p_asm.add_argument("target", help="task root path or task-key")
372
-
373
- p_rec = sub.add_parser("record", help="append a recap Q&A/summary log entry")
374
- p_rec.add_argument("target", help="task root path or task-key")
375
- p_rec.add_argument("--kind", choices=["summary", "qa"], required=True)
376
- p_rec.add_argument("--mode", choices=["artifact", "code"], required=True)
377
- p_rec.add_argument("--question", default="")
378
- p_rec.add_argument("--answer", required=True)
379
- p_rec.add_argument("--citation", action="append", default=[])
380
-
381
- args = parser.parse_args(argv)
382
- task_root, project_root = resolve_task_root(
383
- args.target, args.project_root, args.cwd)
384
-
385
- if args.command == "assemble":
386
- result = assemble_recap(task_root, project_root)
387
- else:
388
- result = append_recap_entry(
389
- task_root, project_root, kind=args.kind, mode=args.mode,
390
- question=args.question, answer_summary=args.answer,
391
- citations=args.citation,
392
- now=dt.datetime.now(dt.timezone.utc))
393
- print(json.dumps(result, ensure_ascii=False, indent=2))
394
- return 0
395
-
396
-
397
- if __name__ == "__main__":
398
- raise SystemExit(main(sys.argv[1:]))
399
- ```
400
-
401
- - [ ] **Step 4: Run test to verify it passes**
402
-
403
- Run: `python3 -m pytest tests/test_okstra_recap.py -v`
404
- Expected: PASS (5 tests).
405
-
406
- - [ ] **Step 5: Commit**
407
-
408
- ```bash
409
- git add scripts/okstra_ctl/recap.py tests/test_okstra_recap.py
410
- git commit -m "feat(recap): recap-log.jsonl append + assemble/record CLI 디스패치"
411
- ```
412
-
413
- ---
414
-
415
- ### Task 4: Node CLI 래퍼 `okstra recap`
416
-
417
- **Files:**
418
- - Create: `src/recap.mjs`
419
- - Modify: `src/cli-registry.mjs` (introspection 카테고리, `error-report` 항목 `cli-registry.mjs:106-112` 바로 다음)
420
-
421
- **Interfaces:**
422
- - Consumes: `./_python-helper.mjs` 의 `runPythonModule({module, args, stdio})` (error-report.mjs 와 동일).
423
- - Produces: `okstra recap <assemble|record> <target> ...` 가 `okstra_ctl.recap` 파이썬 모듈로 인자를 그대로 전달하고 stdout 을 그대로 흘려보낸다.
424
-
425
- - [ ] **Step 1: Create the wrapper**
426
-
427
- `src/recap.mjs` 생성:
428
-
429
- ```javascript
430
- import { runPythonModule } from "./_python-helper.mjs";
431
-
432
- const USAGE = `okstra recap — assemble a task's run-to-run phase recap, or append a recap log entry
433
-
434
- Usage:
435
- okstra recap assemble <task-root|task-key> [--project-root <dir>] [--cwd <dir>]
436
- okstra recap record <task-root|task-key> --kind <summary|qa> --mode <artifact|code> \\
437
- [--question <text>] --answer <text> [--citation <path:line> ...]
438
-
439
- \`assemble\` is read-only and prints a JSON summary of phase transitions across runs.
440
- \`record\` appends one line to <task-root>/recap/recap-log.jsonl; it never mutates
441
- other task artifacts.
442
- `;
443
-
444
- export async function run(args) {
445
- if (args.includes("--help") || args.includes("-h")) {
446
- process.stdout.write(USAGE);
447
- return 0;
448
- }
449
-
450
- const result = await runPythonModule({
451
- module: "okstra_ctl.recap",
452
- args,
453
- stdio: "inherit-stdout",
454
- });
455
- return result.code ?? 0;
456
- }
457
- ```
458
-
459
- - [ ] **Step 2: Register in cli-registry**
460
-
461
- `src/cli-registry.mjs` 의 `error-report` 객체(`cli-registry.mjs:106-112`) 닫는 `},` 다음에 추가:
462
-
463
- ```javascript
464
- {
465
- name: "recap",
466
- module: "./recap.mjs",
467
- export: "run",
468
- category: "introspection",
469
- summary: ["Assemble a task's run-to-run phase recap or append a recap log entry"],
470
- },
471
- ```
472
-
473
- - [ ] **Step 3: Smoke-test the CLI wiring**
474
-
475
- Run: `node bin/okstra recap --help`
476
- Expected: USAGE 텍스트 출력, exit 0.
477
-
478
- - [ ] **Step 4: Smoke-test the python bridge**
479
-
480
- Run: `node bin/okstra recap assemble /nonexistent-task-root`
481
- Expected: `no task root for task key:` 류의 메시지로 비정상 종료(파이썬 브리지가 연결됐다는 증거). 경로/스택 추적 없이 SystemExit 메시지면 정상.
482
-
483
- - [ ] **Step 5: Commit**
484
-
485
- ```bash
486
- git add src/recap.mjs src/cli-registry.mjs
487
- git commit -m "feat(cli): okstra recap Node 래퍼 + cli-registry 등록"
488
- ```
489
-
490
- ---
491
-
492
- ### Task 5: okstra-inspect 에 `recap` facet 추가
493
-
494
- **Files:**
495
- - Modify: `skills/okstra-inspect/SKILL.md` (frontmatter description `SKILL.md:3-4`, facet 테이블 `SKILL.md:11-19`, facet 본문 — `errors` 섹션 `SKILL.md:695` 다음 끝에 추가)
496
-
497
- **Interfaces:**
498
- - Consumes: Task 4 의 `okstra recap assemble` / `okstra recap record` CLI. Step 0 의 공유 부트스트랩(`okstra ensure-installed`, `okstra check-project --json`)과 target 해석 규약(task-key / task-id catalog 매칭 / task-root path — `cost`·`errors` 와 동일).
499
- - Produces: 사용자에게 노출되는 8번째 sub-command `recap`.
500
-
501
- - [ ] **Step 1: Extend the frontmatter description**
502
-
503
- `SKILL.md:3-4` 의 `description` 에서 `dispatches by sub-command to seven facets — status, history, report, time, logs, cost, errors.` 를 `dispatches by sub-command to eight facets — status, history, report, time, logs, cost, errors, recap.` 로 바꾸고, 트리거 문자열 목록 끝(`"실패 로그 정리".` 직전)에 다음을 추가:
504
-
505
- ```
506
- "okstra recap", "recap", "작업 요약", "이 task 요약", "전후 요약", "이 작업 설명해줘", "task 질문",
507
- ```
508
-
509
- - [ ] **Step 2: Add the facet table row**
510
-
511
- `SKILL.md:19` 의 `errors` 행 바로 다음에 추가:
512
-
513
- ```
514
- | `recap` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl`. |
515
- ```
516
-
517
- 그리고 `SKILL.md:9` 문장의 "Sub-commands:" 앞에 있는 facet 나열 설명이 7개를 가정하면 8개로 맞춘다(해당 문장에 숫자가 박혀 있지 않으면 변경 불필요 — 확인 후 진행).
518
-
519
- - [ ] **Step 3: Add the `recap` facet body**
520
-
521
- `errors — Output template` 블록(`SKILL.md:757` 부근, 파일 끝) 다음에 추가:
522
-
523
- ````markdown
524
- ---
525
-
526
- ## recap
527
-
528
- Trigger phrases: "okstra recap", "recap", "작업 요약", "이 task 요약", "전후 요약", "이 작업 설명해줘", "task 질문".
529
-
530
- task-id 하나에 쌓인 `.okstra` 산출물 위에서 (a) 처리 전/후 요약을 내고, (b) 그 작업에 대한 자유 질문에 답한다. 기본은 `.okstra/` 서브트리만 읽는다(artifact 모드). 사용자가 명시적으로 코드 변경까지 봐 달라고 요청할 때만 code 모드로 확장한다. 이 sub-command 는 `recap/recap-log.jsonl` 만 append 하고 `task-manifest.json` / catalog / timeline 은 절대 mutate 하지 않는다.
531
-
532
- ### recap.1 — Resolve target
533
-
534
- `cost` / `errors` 와 동일한 3형식: ① full task-key, ② task-id 만(→ `.okstra/discovery/task-catalog.json` 의 `taskId` 대소문자 무시 매칭; 복수면 나열·질문, 없으면 not-found), ③ task-root path.
535
-
536
- ### recap.2 — Assemble the before/after summary
537
-
538
- CLI 출력을 source of truth 로 쓴다:
539
-
540
- ```bash
541
- okstra recap assemble <resolved-target> --project-root <projectRoot>
542
- ```
543
-
544
- stdout JSON(`{taskKey, runCount, transitions[], latestPhaseStates}`)을 파싱해 run 단위 전/후 전이를 서술한다. 각 `transitions[]` 항목의 `fromPhase → toPhase`(직전 run currentPhase → 이번 run currentPhase), `status`, `lastCompletedPhase`, `nextRecommendedPhase`, `reportPath` 를 시간순으로 보여준다. `runCount == 0` 이면 "이 task 에는 기록된 run 이 없습니다." 라고만 답하고 파일을 읽었다고 주장하지 않는다.
545
-
546
- `reportPath` 가 있는 run 은 사용자가 더 깊은 요약을 원할 때만 해당 final-report 를 읽어 보강한다(자동으로 모두 읽지 않는다).
547
-
548
- ### recap.3 — Free-form Q&A loop
549
-
550
- 요약 출력 뒤 사용자의 임의 질문을 받는다.
551
-
552
- - **artifact 모드(기본)**: `.okstra/` 산출물(timeline, task-manifest, 해당 run 의 final-report)만 근거로 답한다. 모든 사실 주장은 `.okstra` 파일 `path:line` 인용을 동반한다.
553
- - **code 모드(opt-in)**: 사용자가 "코드 변경까지 / diff 까지 봐줘" 류로 **명시적으로 요청**할 때만, 해당 task 의 worktree/브랜치에서 `git diff` · `git log` 를 추가로 읽어 코드 레벨로 답한다. recap 진입만으로는 절대 code 모드로 넘어가지 않는다.
554
-
555
- ### recap.4 — Persist each turn
556
-
557
- 요약 1건과 각 Q&A 답변을 로그에 남긴다(append-only):
558
-
559
- ```bash
560
- okstra recap record <resolved-target> --project-root <projectRoot> \
561
- --kind <summary|qa> --mode <artifact|code> \
562
- --question "<질문 또는 생략>" --answer "<한두 줄 요약>" \
563
- --citation "<path:line>" --citation "<path:line>"
564
- ```
565
-
566
- `--answer` 에는 전체 답변 전사가 아니라 한두 줄 요약을 넣는다. 인용이 여러 개면 `--citation` 을 반복한다. 기록 실패(비정상 종료)는 조용히 넘기지 말고 사용자에게 알린다.
567
-
568
- ### recap — Output template
569
-
570
- ```markdown
571
- ## okstra Recap — <task-key>
572
-
573
- 전/후 요약 (runs: <N>):
574
-
575
- | # | when | task-type | from → to | status | next |
576
- |---|---|---|---|---|---|
577
- | 1 | <ts> | requirements-discovery | (시작) → requirements-discovery | done | error-analysis |
578
-
579
- <이후 자유 질문을 받습니다. 코드 변경까지 보려면 "diff 까지 봐줘" 라고 요청하세요.>
580
- ```
581
- ````
582
-
583
- - [ ] **Step 4: Verify the facet count is consistent**
584
-
585
- Run: `grep -n "seven facets\|eight facets\|seven\b" skills/okstra-inspect/SKILL.md`
586
- Expected: `seven` 잔존 없음, `eight facets` 만 존재. 잔존하면 그 자리를 8개 표현으로 고친다.
587
-
588
- - [ ] **Step 5: Commit**
589
-
590
- ```bash
591
- git add skills/okstra-inspect/SKILL.md
592
- git commit -m "feat(inspect): recap facet — task-id 전/후 요약 + 자유 Q&A"
593
- ```
594
-
595
- ---
596
-
597
- ### Task 6: 아키텍처 문서 갱신 + 빌드 동기화 + 전체 검증
598
-
599
- **Files:**
600
- - Modify: `docs/kr/architecture.md` (task-root 트리, `architecture.md:443-478` 부근)
601
- - (빌드) `npm run build` → `runtime/` 동기화
602
-
603
- **Interfaces:**
604
- - Consumes: Task 1–5 의 결과. 새 정보 없음(문서·빌드만).
605
-
606
- - [ ] **Step 1: Document the recap directory**
607
-
608
- `docs/kr/architecture.md` 의 task-root 트리(`history/`·`timeline.json` 가 그려진 블록, `architecture.md:443-478`)에 `recap/` 노드를 추가하고, 한 줄 설명을 단다:
609
-
610
- ```
611
- └── recap/
612
- └── recap-log.jsonl # recap facet 의 전/후 요약·Q&A append-only 로그 (다른 산출물 불변)
613
- ```
614
-
615
- - [ ] **Step 2: Sync runtime**
616
-
617
- Run: `npm run build`
618
- Expected: 에러 없이 완료. `recap.py` / `recap.mjs` / 갱신된 `SKILL.md` 가 `runtime/` 에 복사됨.
619
-
620
- - [ ] **Step 3: Run the full python suite**
621
-
622
- Run: `python3 -m pytest tests/ -q`
623
- Expected: 전부 PASS (신규 `tests/test_okstra_recap.py` 포함, 회귀 없음).
624
-
625
- - [ ] **Step 4: CLI smoke**
626
-
627
- Run: `node bin/okstra recap --help`
628
- Expected: USAGE 출력, exit 0.
629
-
630
- Run: `node bin/okstra --help`
631
- Expected: introspection 카테고리에 `recap` 이 나열됨.
632
-
633
- - [ ] **Step 5: Commit**
634
-
635
- ```bash
636
- git add docs/kr/architecture.md runtime/
637
- git commit -m "docs(architecture): task-root 트리에 recap/ 추가 + runtime 동기화"
638
- ```
639
-
640
- ---
641
-
642
- ## 검증 (계획 전체)
643
-
644
- - 결정론적 로직(`compute_run_paths` recap 경로 / `assemble_recap` 전이 계산 / `append_recap_entry` append)은 모두 단위 테스트로 커버(Task 1·2·3).
645
- - facet 동작(요약 서술·Q&A·2-모드)은 skill-markdown 주도라 자동 단위 테스트 대상이 아님 → Task 4·6 의 CLI 스모크 + 수동 `okstra recap assemble <task-id>` 로 확인.
646
- - "요약 품질"은 휴리스틱이라 자동 검증 범위 밖(spec §5 와 동일 입장).
647
- - 단일 참조점: 경로는 `compute_run_paths` 한 곳, target 해석은 `task_target.resolve_task_root` 한 곳, Node↔python 브리지는 `runPythonModule` 한 곳으로만 흐른다.