okstra 0.102.2 → 0.102.4

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 (167) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +8 -3
  4. package/docs/kr/architecture.md +12 -13
  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/lib/okstra/interactive.sh +12 -0
  12. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  13. package/runtime/bin/okstra-codex-exec.sh +1 -2
  14. package/runtime/bin/okstra-render-final-report.py +2 -9
  15. package/runtime/bin/okstra-spawn-followups.py +4 -6
  16. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  17. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  18. package/runtime/prompts/lead/report-writer.md +1 -1
  19. package/runtime/prompts/profiles/_common-contract.md +9 -11
  20. package/runtime/python/okstra_ctl/codex_dispatch.py +6 -13
  21. package/runtime/python/okstra_ctl/consumers.py +35 -0
  22. package/runtime/python/okstra_ctl/container.py +4 -6
  23. package/runtime/python/okstra_ctl/dispatch_core.py +8 -13
  24. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  25. package/runtime/python/okstra_ctl/handoff.py +2 -1
  26. package/runtime/python/okstra_ctl/implementation_outcome.py +271 -0
  27. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  28. package/runtime/python/okstra_ctl/path_hints.py +665 -0
  29. package/runtime/python/okstra_ctl/render.py +4 -3
  30. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  31. package/runtime/python/okstra_ctl/report_views.py +7 -4
  32. package/runtime/python/okstra_ctl/run.py +3 -10
  33. package/runtime/python/okstra_ctl/run_context.py +4 -2
  34. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  35. package/runtime/python/okstra_ctl/wizard.py +37 -26
  36. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  37. package/runtime/python/okstra_project/state.py +40 -2
  38. package/runtime/python/okstra_token_usage/report.py +2 -5
  39. package/runtime/validators/validate-report-views.py +2 -1
  40. package/runtime/validators/validate-run.py +1 -11
  41. package/src/commands/lifecycle/install.mjs +5 -5
  42. package/src/commands/lifecycle/uninstall.mjs +4 -4
  43. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  44. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  45. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  46. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  47. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  48. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  49. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  50. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  51. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  52. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  53. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  54. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  55. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  56. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  57. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  58. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  59. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  60. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  61. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  62. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  63. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  64. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  65. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  66. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  67. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  68. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  69. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  70. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  71. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  72. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  73. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  76. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  77. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  78. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  79. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  80. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  81. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  82. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  83. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  84. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  85. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  86. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  87. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  88. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  89. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  90. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  91. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  92. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  93. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  94. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  95. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  96. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  97. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  98. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  99. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  100. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  101. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  102. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  103. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  104. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  105. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  106. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  107. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  108. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  109. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  110. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  111. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  112. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  113. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  114. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  115. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  116. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  117. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  118. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  119. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  120. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  121. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  122. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  123. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  124. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  125. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  126. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  127. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  128. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  129. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  130. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  131. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  132. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  133. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  134. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  135. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  136. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  137. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  138. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  139. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  140. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  141. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  142. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  143. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  144. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  145. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  146. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  147. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  148. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  149. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  150. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  151. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  152. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  153. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  154. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  155. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  156. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  157. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  158. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  159. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  160. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  161. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  162. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  163. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  164. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  165. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  166. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  167. package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
@@ -1,724 +0,0 @@
1
- # okstra-run 에러 집계 리포트 구현 Plan
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-run 이 적재한 `errors-<type>-<seq>.jsonl` 을 task 단위로 집계해 timestamp 누적 `.md` 리포트로 렌더하는 신규 CLI `okstra error-report` 와, 그것을 호출하는 okstra-inspect `errors` facet 을 추가한다.
6
-
7
- **Architecture:** Python 렌더러(`okstra_ctl/error_report.py`)가 결정론적으로 errors.jsonl 들을 glob·집계·렌더하고 stdout 으로 요약 JSON 을 낸다. Node CLI 래퍼(`src/error-report.mjs`)가 이를 `okstra error-report` 로 노출하고, inspect `errors` facet 이 호출 결과를 표시한다. task-key→task-root 해석은 `context_cost.py` 에서 공유 헬퍼(`task_target.py`)로 추출해 두 소비자가 공유한다.
8
-
9
- **Tech Stack:** Python 3 (argparse, json, pathlib, datetime), Node ES modules, pytest.
10
-
11
- 설계 근거: [`docs/superpowers/specs/2026-06-17-okstra-error-report-design.md`](../specs/2026-06-17-okstra-error-report-design.md)
12
-
13
- ---
14
-
15
- ## File Structure
16
-
17
- - Create: `scripts/okstra_ctl/task_target.py` — task-key → (task_root, project_root) 해석 공유 헬퍼. `context_cost.py` 의 private 헬퍼를 추출한 SSOT.
18
- - Modify: `scripts/okstra_ctl/context_cost.py` — 추출한 헬퍼를 `task_target` 에서 import (중복 제거).
19
- - Create: `scripts/okstra_ctl/error_report.py` — glob·parse·aggregate·render·main.
20
- - Create: `tests/test_okstra_error_report.py` — 집계/파싱/렌더/main 단위 테스트.
21
- - Create: `src/error-report.mjs` — Node CLI 래퍼 (`context-cost.mjs` 복제).
22
- - Modify: `src/cli-registry.mjs` — `error-report` 명령 등록.
23
- - Modify: `skills/okstra-inspect/SKILL.md` — `errors` facet 섹션 + dispatch 행 + 정체성 문구.
24
- - Modify: `CHANGES.md` — 사용자 영향 항목.
25
-
26
- ---
27
-
28
- ## Task 1: task-key 해석 공유 헬퍼 추출
29
-
30
- 기존 `context_cost.py` 가 두 번째 소비자(`error_report.py`)를 갖게 되므로, task-key 해석 로직을 공유 모듈로 추출한다(single-reference-point). 동작은 바뀌지 않으므로 기존 cost 테스트가 회귀 가드 역할을 한다.
31
-
32
- **Files:**
33
- - Create: `scripts/okstra_ctl/task_target.py`
34
- - Modify: `scripts/okstra_ctl/context_cost.py:16`, `:92-123`
35
-
36
- - [ ] **Step 1: 공유 헬퍼 파일 작성**
37
-
38
- Create `scripts/okstra_ctl/task_target.py` (코드는 `context_cost.py:92-123` 에서 그대로 이동, 이름에서 선행 `_` 제거):
39
-
40
- ```python
41
- """task-key → (task_root, project_root) 해석 공유 헬퍼.
42
-
43
- read-side CLI(context-cost, error-report)가 동일한 입력 계약을 공유하기 위한
44
- SSOT. target 은 task-root 경로 / task-key 둘 다 받는다.
45
- """
46
- from __future__ import annotations
47
-
48
- from pathlib import Path
49
-
50
- from okstra_project import ResolverError, find_task_root, resolve_project_root
51
-
52
-
53
- def resolve_task_root(target: str, project_root: str, cwd: str) -> tuple[Path, Path]:
54
- target_path = Path(target).expanduser()
55
- if target_path.exists():
56
- task_root = target_path.resolve()
57
- return task_root, infer_project_root(task_root)
58
-
59
- try:
60
- resolved_project = Path(
61
- resolve_project_root(explicit_root=project_root, cwd=cwd)
62
- ).resolve()
63
- except ResolverError as exc:
64
- raise SystemExit(f"project root resolution failed: {exc}") from exc
65
-
66
- task_root = find_task_root(resolved_project, target)
67
- if task_root is None:
68
- raise SystemExit(f"no task root for task key: {target}")
69
- return task_root.resolve(), resolved_project
70
-
71
-
72
- def infer_project_root(task_root: Path) -> Path:
73
- parts = task_root.parts
74
- if ".okstra" not in parts:
75
- return task_root
76
- idx = parts.index(".okstra")
77
- return Path(*parts[:idx]).resolve()
78
-
79
-
80
- def project_rel(path: Path, project_root: Path) -> str:
81
- try:
82
- return str(path.resolve().relative_to(project_root.resolve()))
83
- except ValueError:
84
- return str(path)
85
- ```
86
-
87
- - [ ] **Step 2: context_cost.py 가 공유 헬퍼를 import 하도록 변경**
88
-
89
- In `scripts/okstra_ctl/context_cost.py`, line 16 의 import 아래에 추가하고, `_resolve_task_root`/`_infer_project_root`/`_project_rel` 정의(line 92-123)를 삭제:
90
-
91
- ```python
92
- from okstra_project import ResolverError, find_task_root, resolve_project_root
93
- from okstra_ctl.task_target import resolve_task_root, infer_project_root, project_rel
94
- ```
95
-
96
- 그런 다음 파일 전체에서 호출부를 치환:
97
- - `_resolve_task_root(` → `resolve_task_root(`
98
- - `_infer_project_root(` → `infer_project_root(`
99
- - `_project_rel(` → `project_rel(`
100
-
101
- 치환 명령:
102
-
103
- ```bash
104
- cd /Volumes/Workspaces/workspace/projects/Okstra
105
- sed -i '' 's/_resolve_task_root(/resolve_task_root(/g; s/_infer_project_root(/infer_project_root(/g; s/_project_rel(/project_rel(/g' scripts/okstra_ctl/context_cost.py
106
- ```
107
-
108
- 이후 `context_cost.py` 안에 남은 `def resolve_task_root`/`def infer_project_root`/`def project_rel` 정의(이전 `_` 버전)를 수동으로 삭제한다. `okstra_project import ...` 줄 다음에 더 이상 `ResolverError`/`find_task_root`/`resolve_project_root` 를 직접 쓰지 않으면 그 import 도 제거한다(grep 으로 확인 후).
109
-
110
- - [ ] **Step 3: cost 회귀 테스트 통과 확인**
111
-
112
- Run: `cd /Volumes/Workspaces/workspace/projects/Okstra && python3 -m pytest tests/ -k "context_cost or cost" -q`
113
- Expected: PASS (전부). 만약 import 오류면 Step 2 의 남은 정의/직접 사용처를 점검.
114
-
115
- - [ ] **Step 4: Commit**
116
-
117
- ```bash
118
- cd /Volumes/Workspaces/workspace/projects/Okstra
119
- git add scripts/okstra_ctl/task_target.py scripts/okstra_ctl/context_cost.py
120
- git commit -m "refactor(ctl): task-key 해석 헬퍼를 task_target 공유 모듈로 추출"
121
- ```
122
-
123
- ---
124
-
125
- ## Task 2: error_report.py 집계 코어 (glob + parse + aggregate)
126
-
127
- errors.jsonl 을 찾아 파싱하고 집계하는 순수 함수들. project_root 없이 task_root 만으로 단위 테스트 가능하게 설계한다.
128
-
129
- **Files:**
130
- - Create: `scripts/okstra_ctl/error_report.py`
131
- - Test: `tests/test_okstra_error_report.py`
132
-
133
- - [ ] **Step 1: 실패 테스트 작성 (glob + parse + aggregate)**
134
-
135
- Create `tests/test_okstra_error_report.py`:
136
-
137
- ```python
138
- import json
139
- from pathlib import Path
140
-
141
- from okstra_ctl import error_report
142
-
143
-
144
- def _write_log(task_root: Path, run_seg: str, seq: str, records: list[dict]) -> None:
145
- log_dir = task_root / "runs" / run_seg / "logs"
146
- log_dir.mkdir(parents=True, exist_ok=True)
147
- path = log_dir / f"errors-{run_seg.split('/')[0]}-{seq}.jsonl"
148
- with path.open("w", encoding="utf-8") as fh:
149
- for rec in records:
150
- fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
151
-
152
-
153
- def _rec(**over) -> dict:
154
- base = {
155
- "ts": "2026-06-17T01:00:00+00:00",
156
- "taskKey": "p:g:DEV-1",
157
- "phase": "implementation",
158
- "agent": "claude-worker",
159
- "agentRole": "worker",
160
- "source": "worker-reported",
161
- "errorType": "cli-failure",
162
- "command": "pytest",
163
- "exitCode": 1,
164
- "message": "boom",
165
- "stderrExcerpt": "trace...",
166
- }
167
- base.update(over)
168
- return base
169
-
170
-
171
- def test_glob_finds_flat_and_staged(tmp_path):
172
- _write_log(tmp_path, "implementation", "001", [_rec()])
173
- _write_log(tmp_path, "implementation/stage-2", "003", [_rec(phase="phase-2")])
174
- paths = error_report._glob_error_logs(tmp_path)
175
- assert len(paths) == 2
176
-
177
-
178
- def test_parse_skips_blank_and_broken_lines(tmp_path):
179
- log_dir = tmp_path / "runs" / "implementation" / "logs"
180
- log_dir.mkdir(parents=True)
181
- (log_dir / "errors-implementation-001.jsonl").write_text(
182
- json.dumps(_rec()) + "\n\n" + "{not json\n" + "[1,2]\n",
183
- encoding="utf-8",
184
- )
185
- records, skipped = error_report._parse_records(
186
- [log_dir / "errors-implementation-001.jsonl"]
187
- )
188
- assert len(records) == 1
189
- assert skipped == 2 # broken json line + non-dict array
190
-
191
-
192
- def test_aggregate_counts(tmp_path):
193
- records = [
194
- _rec(errorType="cli-failure", source="worker-reported",
195
- phase="implementation", agent="claude-worker"),
196
- _rec(errorType="tool-failure", source="lead-observed",
197
- phase="implementation", agent="claude-lead"),
198
- _rec(errorType="cli-failure", source="worker-reported",
199
- phase="error-analysis", agent="codex-worker"),
200
- ]
201
- agg = error_report._aggregate(records)
202
- assert agg["errorCount"] == 3
203
- assert agg["byErrorType"]["cli-failure"] == 2
204
- assert agg["bySource"]["lead-observed"] == 1
205
- assert {row["phase"]: row["count"] for row in agg["byPhase"]} == {
206
- "implementation": 2, "error-analysis": 1,
207
- }
208
- assert {row["agent"]: row["count"] for row in agg["byAgent"]}["codex-worker"] == 1
209
- ```
210
-
211
- - [ ] **Step 2: 실패 확인**
212
-
213
- Run: `cd /Volumes/Workspaces/workspace/projects/Okstra && python3 -m pytest tests/test_okstra_error_report.py -q`
214
- Expected: FAIL — `ModuleNotFoundError: No module named 'okstra_ctl.error_report'` (또는 AttributeError).
215
-
216
- - [ ] **Step 3: 집계 코어 구현**
217
-
218
- Create `scripts/okstra_ctl/error_report.py`:
219
-
220
- ```python
221
- """Read-side aggregator for okstra-run error logs.
222
-
223
- task 의 모든 run errors-<type>-<seq>.jsonl 을 모아 phase / worker / errorType
224
- 별로 집계하고 timestamp 누적 .md 리포트로 렌더한다. task 산출물을 mutate 하지
225
- 않는다.
226
- """
227
- from __future__ import annotations
228
-
229
- import argparse
230
- import datetime as dt
231
- import json
232
- import sys
233
- from pathlib import Path
234
-
235
- from okstra_ctl.task_target import resolve_task_root, project_rel
236
-
237
-
238
- def _glob_error_logs(task_root: Path) -> list[Path]:
239
- runs = task_root / "runs"
240
- if not runs.exists():
241
- return []
242
- flat = runs.glob("*/logs/errors-*.jsonl")
243
- staged = runs.glob("*/stage-*/logs/errors-*.jsonl")
244
- return sorted(set(flat) | set(staged))
245
-
246
-
247
- def _parse_records(paths: list[Path]) -> tuple[list[dict], int]:
248
- records: list[dict] = []
249
- skipped = 0
250
- for path in paths:
251
- for line in path.read_text(encoding="utf-8").splitlines():
252
- line = line.strip()
253
- if not line:
254
- continue
255
- try:
256
- rec = json.loads(line)
257
- except Exception:
258
- skipped += 1
259
- continue
260
- if isinstance(rec, dict):
261
- rec["_sourceLog"] = str(path)
262
- records.append(rec)
263
- else:
264
- skipped += 1
265
- return records, skipped
266
-
267
-
268
- def _tally(records: list[dict], key: str) -> dict:
269
- out: dict[str, int] = {}
270
- for rec in records:
271
- out[str(rec.get(key, ""))] = out.get(str(rec.get(key, "")), 0) + 1
272
- return out
273
-
274
-
275
- def _rows(counts: dict, label: str) -> list[dict]:
276
- return [
277
- {label: name, "count": n}
278
- for name, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
279
- ]
280
-
281
-
282
- def _aggregate(records: list[dict]) -> dict:
283
- return {
284
- "errorCount": len(records),
285
- "runCount": len({rec.get("_sourceLog", "") for rec in records}),
286
- "byErrorType": _tally(records, "errorType"),
287
- "bySource": _tally(records, "source"),
288
- "byPhase": _rows(_tally(records, "phase"), "phase"),
289
- "byAgent": _rows(_tally(records, "agent"), "agent"),
290
- }
291
- ```
292
-
293
- - [ ] **Step 4: 통과 확인**
294
-
295
- Run: `cd /Volumes/Workspaces/workspace/projects/Okstra && python3 -m pytest tests/test_okstra_error_report.py -q`
296
- Expected: PASS (3 tests).
297
-
298
- - [ ] **Step 5: Commit**
299
-
300
- ```bash
301
- cd /Volumes/Workspaces/workspace/projects/Okstra
302
- git add scripts/okstra_ctl/error_report.py tests/test_okstra_error_report.py
303
- git commit -m "feat(ctl): error_report 집계 코어(glob/parse/aggregate)"
304
- ```
305
-
306
- ---
307
-
308
- ## Task 3: 렌더(.md) + main(stdout JSON)
309
-
310
- 집계 결과를 `.md` 로 렌더하고, main 이 파일을 쓰고 stdout JSON 요약을 낸다.
311
-
312
- **Files:**
313
- - Modify: `scripts/okstra_ctl/error_report.py`
314
- - Test: `tests/test_okstra_error_report.py`
315
-
316
- - [ ] **Step 1: 실패 테스트 추가**
317
-
318
- Append to `tests/test_okstra_error_report.py`:
319
-
320
- ```python
321
- def test_render_markdown_has_sections(tmp_path):
322
- records = [_rec(stderrExcerpt="line1\nline2")]
323
- md = error_report.render_markdown(
324
- task_key="p:g:DEV-1",
325
- records=records,
326
- agg=error_report._aggregate(records),
327
- parse_skipped=0,
328
- generated_at="2026-06-17T02:00:00+00:00",
329
- )
330
- assert "# okstra 에러 리포트" in md
331
- assert "## 요약" in md
332
- assert "## phase 별 집계" in md
333
- assert "## worker(agent) 별 집계" in md
334
- assert "## 개별 에러 이벤트" in md
335
-
336
-
337
- def test_main_writes_md_and_prints_json(tmp_path, capsys):
338
- task_root = tmp_path / ".okstra" / "tasks" / "g" / "DEV-1"
339
- _write_log(task_root, "implementation", "001", [_rec()])
340
- (task_root / "task-manifest.json").write_text(
341
- json.dumps({"taskKey": "p:g:DEV-1"}), encoding="utf-8"
342
- )
343
- rc = error_report.main([str(task_root)])
344
- assert rc == 0
345
- out = json.loads(capsys.readouterr().out)
346
- assert out["totals"]["errorCount"] == 1
347
- report_abs = tmp_path / out["reportPath"]
348
- assert report_abs.is_file()
349
- assert "## 개별 에러 이벤트" in report_abs.read_text(encoding="utf-8")
350
-
351
-
352
- def test_main_no_logs_reports_zero(tmp_path, capsys):
353
- task_root = tmp_path / ".okstra" / "tasks" / "g" / "DEV-2"
354
- task_root.mkdir(parents=True)
355
- (task_root / "task-manifest.json").write_text(
356
- json.dumps({"taskKey": "p:g:DEV-2"}), encoding="utf-8"
357
- )
358
- rc = error_report.main([str(task_root)])
359
- assert rc == 0
360
- out = json.loads(capsys.readouterr().out)
361
- assert out["totals"]["errorCount"] == 0
362
- assert out["reportPath"] == "" # glob 0개 → .md 미생성
363
- ```
364
-
365
- - [ ] **Step 2: 실패 확인**
366
-
367
- Run: `cd /Volumes/Workspaces/workspace/projects/Okstra && python3 -m pytest tests/test_okstra_error_report.py -q`
368
- Expected: FAIL — `AttributeError: module 'okstra_ctl.error_report' has no attribute 'render_markdown'`.
369
-
370
- - [ ] **Step 3: 렌더 + main 구현 (error_report.py 끝에 추가)**
371
-
372
- ```python
373
- def _md_table(header: list[str], rows: list[list[str]]) -> str:
374
- line = "| " + " | ".join(header) + " |"
375
- sep = "| " + " | ".join("---" for _ in header) + " |"
376
- body = ["| " + " | ".join(cells) + " |" for cells in rows]
377
- return "\n".join([line, sep, *body]) if rows else line + "\n" + sep + "\n| _없음_ |"
378
-
379
-
380
- def render_markdown(*, task_key, records, agg, parse_skipped, generated_at) -> str:
381
- by_type = ", ".join(f"{k}: {v}" for k, v in sorted(agg["byErrorType"].items())) or "_없음_"
382
- by_src = ", ".join(f"{k}: {v}" for k, v in sorted(agg["bySource"].items())) or "_없음_"
383
- parts = [
384
- f"# okstra 에러 리포트 — {task_key}",
385
- "",
386
- "## 요약",
387
- "",
388
- f"- 총 에러: {agg['errorCount']}건",
389
- f"- 집계된 로그(run) 수: {agg['runCount']}",
390
- f"- errorType 별: {by_type}",
391
- f"- source 별: {by_src}",
392
- f"- 파싱 건너뛴 줄: {parse_skipped}",
393
- f"- 생성 시각: {generated_at}",
394
- "",
395
- "## phase 별 집계",
396
- "",
397
- _md_table(["Phase", "Count"],
398
- [[r["phase"] or "(없음)", str(r["count"])] for r in agg["byPhase"]]),
399
- "",
400
- "## worker(agent) 별 집계",
401
- "",
402
- _md_table(["Agent", "Count"],
403
- [[r["agent"] or "(없음)", str(r["count"])] for r in agg["byAgent"]]),
404
- "",
405
- "## 개별 에러 이벤트",
406
- "",
407
- _md_table(
408
- ["ts", "phase", "agent", "errorType", "command", "exitCode", "message"],
409
- [[
410
- str(r.get("ts", "")), str(r.get("phase", "")), str(r.get("agent", "")),
411
- str(r.get("errorType", "")), f"`{r.get('command', '')}`",
412
- str(r.get("exitCode", "")),
413
- str(r.get("message", "")).replace("|", "\\|").replace("\n", " "),
414
- ] for r in sorted(records, key=lambda x: str(x.get("ts", "")))],
415
- ),
416
- "",
417
- ]
418
- return "\n".join(parts)
419
-
420
-
421
- def _timestamp_segment(now: dt.datetime) -> str:
422
- return now.strftime("%Y-%m-%d_%H-%M-%S")
423
-
424
-
425
- def build_and_write(task_root: Path, project_root: Path, now: dt.datetime) -> dict:
426
- manifest_path = task_root / "task-manifest.json"
427
- task_key = ""
428
- if manifest_path.is_file():
429
- try:
430
- task_key = json.loads(manifest_path.read_text(encoding="utf-8")).get("taskKey", "")
431
- except Exception:
432
- task_key = ""
433
- paths = _glob_error_logs(task_root)
434
- records, skipped = _parse_records(paths)
435
- agg = _aggregate(records)
436
- report_rel = ""
437
- if paths:
438
- md = render_markdown(
439
- task_key=task_key, records=records, agg=agg,
440
- parse_skipped=skipped, generated_at=now.isoformat(),
441
- )
442
- out_dir = task_root / "error-reports"
443
- out_dir.mkdir(parents=True, exist_ok=True)
444
- out_path = out_dir / f"error-report-{_timestamp_segment(now)}.md"
445
- out_path.write_text(md, encoding="utf-8")
446
- report_rel = project_rel(out_path, project_root)
447
- return {
448
- "taskKey": task_key,
449
- "reportPath": report_rel,
450
- "parseSkipped": skipped,
451
- "totals": {
452
- "errorCount": agg["errorCount"],
453
- "runCount": agg["runCount"],
454
- "byErrorType": agg["byErrorType"],
455
- "bySource": agg["bySource"],
456
- },
457
- "byPhase": agg["byPhase"],
458
- "byAgent": agg["byAgent"],
459
- }
460
-
461
-
462
- def main(argv: list[str] | None = None) -> int:
463
- parser = argparse.ArgumentParser(
464
- description="Aggregate okstra-run error logs into a task-level report."
465
- )
466
- parser.add_argument("target", help="task root path or task-key")
467
- parser.add_argument("--project-root", default="", help="project root for task-key lookup")
468
- parser.add_argument("--cwd", default=".", help="cwd for project root resolution")
469
- args = parser.parse_args(argv)
470
-
471
- task_root, project_root = resolve_task_root(args.target, args.project_root, args.cwd)
472
- result = build_and_write(task_root, project_root, dt.datetime.now(dt.timezone.utc))
473
- print(json.dumps(result, ensure_ascii=False, indent=2))
474
- return 0
475
-
476
-
477
- if __name__ == "__main__":
478
- raise SystemExit(main(sys.argv[1:]))
479
- ```
480
-
481
- - [ ] **Step 4: 통과 확인**
482
-
483
- Run: `cd /Volumes/Workspaces/workspace/projects/Okstra && python3 -m pytest tests/test_okstra_error_report.py -q`
484
- Expected: PASS (6 tests).
485
-
486
- - [ ] **Step 5: Commit**
487
-
488
- ```bash
489
- cd /Volumes/Workspaces/workspace/projects/Okstra
490
- git add scripts/okstra_ctl/error_report.py tests/test_okstra_error_report.py
491
- git commit -m "feat(ctl): error_report 렌더(.md) + main(stdout JSON)"
492
- ```
493
-
494
- ---
495
-
496
- ## Task 4: Node CLI 래퍼 + 레지스트리 등록
497
-
498
- `okstra error-report` 를 노출한다. `context-cost.mjs` 를 복제한다.
499
-
500
- **Files:**
501
- - Create: `src/error-report.mjs`
502
- - Modify: `src/cli-registry.mjs:100-105` (context-cost 항목 바로 뒤)
503
-
504
- - [ ] **Step 1: Node 래퍼 작성**
505
-
506
- Create `src/error-report.mjs`:
507
-
508
- ```javascript
509
- import { runPythonModule } from "./_python-helper.mjs";
510
-
511
- const USAGE = `okstra error-report — aggregate okstra-run error logs into a task report
512
-
513
- Usage:
514
- okstra error-report <task-root>
515
- okstra error-report <task-key> --project-root <dir>
516
- okstra error-report <task-key> --cwd <dir>
517
-
518
- Writes a timestamped markdown report under <task-root>/error-reports/ and
519
- prints a JSON summary (counts by errorType / source / phase / agent) to stdout.
520
- This is read-only against task state; it never mutates task artifacts.
521
- `;
522
-
523
- export async function run(args) {
524
- if (args.includes("--help") || args.includes("-h")) {
525
- process.stdout.write(USAGE);
526
- return 0;
527
- }
528
-
529
- const result = await runPythonModule({
530
- module: "okstra_ctl.error_report",
531
- args,
532
- stdio: "inherit-stdout",
533
- });
534
- return result.code ?? 0;
535
- }
536
- ```
537
-
538
- - [ ] **Step 2: 레지스트리 등록**
539
-
540
- In `src/cli-registry.mjs`, context-cost 항목(line 100-105) 바로 뒤에 추가:
541
-
542
- ```javascript
543
- {
544
- name: "error-report",
545
- module: "./error-report.mjs",
546
- export: "run",
547
- category: "introspection",
548
- summary: ["Aggregate okstra-run error logs into a task-level report"],
549
- },
550
- ```
551
-
552
- - [ ] **Step 3: 빌드 + smoke 테스트**
553
-
554
- ```bash
555
- cd /Volumes/Workspaces/workspace/projects/Okstra
556
- npm run build
557
- node bin/okstra error-report --help
558
- ```
559
- Expected: USAGE 출력, exit 0.
560
-
561
- - [ ] **Step 4: Commit**
562
-
563
- ```bash
564
- cd /Volumes/Workspaces/workspace/projects/Okstra
565
- git add src/error-report.mjs src/cli-registry.mjs
566
- git commit -m "feat(cli): okstra error-report 명령 등록"
567
- ```
568
-
569
- ---
570
-
571
- ## Task 5: inspect `errors` facet + 정체성 문구
572
-
573
- okstra-inspect SKILL 에 facet 을 추가한다.
574
-
575
- **Files:**
576
- - Modify: `skills/okstra-inspect/SKILL.md` (frontmatter description, line 9 정체성, line 11-18 dispatch 표, 신규 `## errors` 섹션)
577
-
578
- - [ ] **Step 1: 정체성 문구 + dispatch 표 갱신**
579
-
580
- `skills/okstra-inspect/SKILL.md:9` 의 정체성 문장을 다음으로 교체:
581
-
582
- ```
583
- Single read-side entry point for okstra runtime inspection plus the one status mutation that belongs here (`workStatus`) and read-derived artifact rendering (`errors` report). Sub-commands:
584
- ```
585
-
586
- `SKILL.md:11-18` dispatch 표의 `cost` 행 다음에 추가:
587
-
588
- ```
589
- | `errors` | Aggregate okstra-run error logs for a task into a timestamped markdown report; print a summary. |
590
- ```
591
-
592
- frontmatter `description`(line 4) 의 trigger 목록 끝에 추가: `"okstra errors", "error report", "에러 리포트", "에러 보고", "에러 모아줘", "실패 로그 정리"`.
593
-
594
- - [ ] **Step 2: `## errors` facet 섹션 추가 (`## logs` 섹션 뒤, `## Output Rules (shared)` 앞)**
595
-
596
- ```markdown
597
- ---
598
-
599
- ## errors
600
-
601
- Trigger phrases: "okstra errors", "error report", "에러 리포트", "에러 보고", "에러 모아줘", "실패 로그 정리".
602
-
603
- Aggregate a task's okstra-run error logs (`runs/*/logs/errors-*.jsonl`, lead-observed + worker-reported) into a timestamped markdown report and summarize it. This sub-command renders a **read-derived artifact** (a `.md` file) but never mutates task state (`task-manifest.json`, catalog, timeline).
604
-
605
- ### errors.1 — Resolve target
606
-
607
- Accepted target forms (same as `cost`):
608
-
609
- 1. Full task-key: `<project-id>:<task-group>:<task-id>`.
610
- 2. Task id only — read `.okstra/discovery/task-catalog.json`, match `taskId` case-insensitively (multiple → list and ask; none → report not found).
611
- 3. Task root path.
612
-
613
- ### errors.2 — Run the renderer
614
-
615
- Use the CLI output as the source of truth:
616
-
617
- ```bash
618
- okstra error-report <resolved-target> --project-root <projectRoot>
619
- ```
620
-
621
- For a task-root path, run `okstra error-report <path>` directly. Do not parse the jsonl files by hand unless the CLI fails and the user explicitly asks for a manual fallback.
622
-
623
- ### errors.3 — Summarize output
624
-
625
- Parse the stdout JSON and report:
626
-
627
- | Field | Source |
628
- |---|---|
629
- | Report file | `reportPath` (project-root-relative `.md`) |
630
- | Total errors | `totals.errorCount` |
631
- | Logs (runs) | `totals.runCount` |
632
- | By errorType | `totals.byErrorType` (tool-failure / cli-failure / contract-violation) |
633
- | By source | `totals.bySource` (lead-observed / worker-reported) |
634
- | By phase | `byPhase[]` |
635
- | By agent | `byAgent[]` |
636
- | Parse-skipped lines | `parseSkipped` |
637
-
638
- - If `reportPath` is empty AND `totals.errorCount == 0`: report `이 task 에는 기록된 에러 로그가 없습니다.` and do not claim a file was written.
639
- - Otherwise show the `.md` path and offer to read it.
640
- - If `parseSkipped > 0`, surface it (do not silently hide malformed lines).
641
-
642
- ### errors — Output template
643
-
644
- ```markdown
645
- ## okstra Error Report — <task-key>
646
-
647
- - Report: `<reportPath-or-->`
648
- - Total errors: <N> across <runCount> log(s)
649
- - By type: <tool-failure: a, cli-failure: b, ...>
650
- - By source: <lead-observed: x, worker-reported: y>
651
-
652
- | Phase | Count |
653
- |---|---:|
654
- | implementation | 3 |
655
-
656
- | Agent | Count |
657
- |---|---:|
658
- | codex-worker | 2 |
659
-
660
- <If parseSkipped > 0: "⚠ 파싱 건너뛴 줄: <N>">
661
- ```
662
- ```
663
-
664
- - [ ] **Step 3: 빌드 + 동기화 확인**
665
-
666
- ```bash
667
- cd /Volumes/Workspaces/workspace/projects/Okstra
668
- npm run build
669
- grep -c "## errors" runtime/skills/okstra-inspect/SKILL.md
670
- ```
671
- Expected: `1` (runtime 에 동기화됨).
672
-
673
- - [ ] **Step 4: Commit**
674
-
675
- ```bash
676
- cd /Volumes/Workspaces/workspace/projects/Okstra
677
- git add skills/okstra-inspect/SKILL.md
678
- git commit -m "feat(inspect): errors facet 추가 (error-report 호출 + 요약)"
679
- ```
680
-
681
- ---
682
-
683
- ## Task 6: CHANGES.md + 전체 회귀
684
-
685
- **Files:**
686
- - Modify: `CHANGES.md`
687
-
688
- - [ ] **Step 1: CHANGES.md 항목 추가**
689
-
690
- `CHANGES.md` 의 `## 2026-06-17` 섹션 안, 기존 첫 `###` 항목 앞에 추가:
691
-
692
- ```markdown
693
- ### feat(inspect): okstra-run 에러를 task 단위로 집계하는 error-report + errors facet
694
-
695
- - **배경**: okstra-run 은 실행 중 에러를 `runs/<task-type>/logs/errors-<type>-<seq>.jsonl` 에 구조화해 적재하지만(lead-observed + worker-reported), 그것을 읽어 사람에게 보고하는 기능이 없어 `latestRunStatus == error` 라는 run 단위 거친 상태만 확인 가능했다.
696
- - **해결**: task 의 모든 run errors.jsonl 을 phase/worker/errorType 별로 집계해 `<task_root>/error-reports/error-report-<timestamp>.md` 로 렌더하는 신규 CLI `okstra error-report` 와, 그것을 호출·요약하는 okstra-inspect `errors` facet 을 추가했다. task-key 해석은 `context_cost` 와 공유 헬퍼(`task_target`)로 통일했다.
697
- - 사용자 영향: `/okstra-inspect errors <task-key>` 또는 "에러 리포트" 로 특정 task 의 실행 에러를 집계한 markdown 리포트를 생성·요약받을 수 있다. 리포트는 timestamp 누적이라 이력이 보존된다.
698
- ```
699
-
700
- - [ ] **Step 2: 전체 테스트 + 빌드**
701
-
702
- ```bash
703
- cd /Volumes/Workspaces/workspace/projects/Okstra
704
- python3 -m pytest tests/ -q && npm run build && node bin/okstra --version
705
- ```
706
- Expected: 전체 PASS, 빌드 done, 버전 출력.
707
-
708
- - [ ] **Step 3: Commit**
709
-
710
- ```bash
711
- cd /Volumes/Workspaces/workspace/projects/Okstra
712
- git add CHANGES.md
713
- git commit -m "docs(changes): error-report + errors facet 사용자 영향 기록"
714
- ```
715
-
716
- ---
717
-
718
- ## Self-Review 결과 (작성자 점검)
719
-
720
- - **Spec coverage:** §3 아키텍처 4컴포넌트 → Task 1(task_target 추출은 §3 단일 참조점)·2·3(error_report)·4(mjs+registry)·5(SKILL). §4 CLI 인터페이스 → Task 3 main + Task 4 USAGE. §5 산출물 위치/구조 → Task 3 `build_and_write`(error-reports/ + timestamp) + `render_markdown`(5섹션). §6 glob(flat+staged) → Task 2 `_glob_error_logs`. §7 write 경계 문구 → Task 5 Step 1. §8 엣지케이스(0개/0건/깨진 줄) → Task 3 `test_main_no_logs`/Task 2 `test_parse_skips`. §9 테스트 → Task 2·3. 모든 spec 항목에 대응 task 존재.
721
- - **Placeholder scan:** 모든 코드 step 에 실제 코드 포함, "TBD"/"적절히 처리" 없음.
722
- - **Type consistency:** `resolve_task_root`/`project_rel`(Task 1) ↔ Task 3 import 일치. `_aggregate` 반환 키(`errorCount/runCount/byErrorType/bySource/byPhase/byAgent`) ↔ `render_markdown`/`build_and_write`/테스트 전부 동일. stdout JSON 키(`totals.errorCount` 등) ↔ SKILL errors.3 표 일치.
723
-
724
- **범위 밖(YAGNI):** 단일 run 모드, task-group 집계, 자동 생성 훅, HTML 뷰 — 미포함.