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.
- package/README.kr.md +5 -5
- package/README.md +5 -5
- package/docs/kr/architecture/storage-model.md +8 -3
- package/docs/kr/architecture.md +12 -13
- package/docs/kr/cli.md +4 -4
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +12 -0
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +6 -13
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +8 -13
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_outcome.py +271 -0
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/path_hints.py +665 -0
- package/runtime/python/okstra_ctl/render.py +4 -3
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/run_context.py +4 -2
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +37 -26
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_project/state.py +40 -2
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/commands/lifecycle/install.mjs +5 -5
- package/src/commands/lifecycle/uninstall.mjs +4 -4
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- 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 뷰 — 미포함.
|