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,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 뷰 — 미포함.