okstra 0.105.0 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.kr.md CHANGED
@@ -161,6 +161,7 @@ Claude Code 세션 안에서 사용하는 슬래시 커맨드:
161
161
  | `/okstra-run` | 새 task 시작 (또는 기존 task 의 다음 phase 이어가기) |
162
162
  | `/okstra-memory` | `~/.okstra/memory-book` 전역 대화 메모리 저장·검색·보관 |
163
163
  | `/okstra-inspect` | 통합 read-side 스킬. sub-command: `status` (phase / 상태, workStatus 설정), `history` (과거 task / re-run / resume), `report` (final-report 조회·읽기), `time` (소요 시간 breakdown), `logs` (wrapper log sidecar 조회·정리 제안), `cost` (task bundle 컨텍스트/읽기 비용), `errors` (run 에러 로그를 리포트로 집계), `error-zip` (cross-project 에러 로그를 익명화 zip 으로 수집·클러스터 요약), `recap` (run 간 전/후 요약 + task 의 `.okstra` 산출물 위 자유 Q&A) |
164
+ | `/okstra-rollup` | task-group(또는 프로젝트 전체)의 모든 task run 결과를 모아 task별 run수/소요시간/에러와 그룹 합계를 집계하고, report 들을 묶어 task 횡단 종합 요약 작성 |
164
165
  | `/okstra-schedule` | task-group 전체에 대한 작업 계획표 생성 |
165
166
  | `/okstra-container-build` | 검증 완료된 task 의 코드를 로컬 docker compose 그룹으로 배포하고 컨테이너별 로그를 감시 (sub-command: `up` / `status` / `logs` / `stop-watcher` / `down`) |
166
167
  | `/okstra-manager` | 여러 프로젝트에 걸친 okstra task 를 manager-owned plan, assignment, 단방향 project sync snapshot, status, child launch context packet 으로 조정 |
package/README.md CHANGED
@@ -43,7 +43,7 @@ okstra/ npm package = repo root
43
43
  ├── tools/build.mjs runtime/ sync script (invoked by prepack)
44
44
  ├── runtime/ gitignored install payload copied to ~/.okstra
45
45
  ├── scripts/ python + bash runtime sources
46
- ├── skills/ public skill markdown sources (8 user-facing skills)
46
+ ├── skills/ public skill markdown sources (9 user-facing skills)
47
47
  ├── agents/ worker agent markdown sources
48
48
  ├── prompts/, schemas/, templates/, validators/
49
49
  ├── tests/, tests-e2e/
@@ -159,6 +159,7 @@ User-facing slash commands inside a Claude Code session:
159
159
  | `/okstra-run` | Start a new task (or resume the next phase of an existing one) |
160
160
  | `/okstra-memory` | Store/search/archive global conversation memory in `~/.okstra/memory-book` |
161
161
  | `/okstra-inspect` | Unified read-side. Sub-commands: `status` (phase / state, workStatus update), `history` (past runs, re-run, resume), `report` (find/read final-report), `time` (elapsed-time breakdown), `logs` (wrapper log sidecar inventory + cleanup), `cost` (task bundle context/read cost), `errors` (aggregate run error logs into a report), `error-zip` (collect cross-project error logs into an anonymized zip and summarize clusters), `recap` (run-to-run before/after summary + free-form Q&A over a task's `.okstra` artifacts) |
162
+ | `/okstra-rollup` | Roll up run results across every task in a task-group (or the whole project) — per-task runs/time/errors plus group totals — and synthesize a cross-task digest from the report files |
162
163
  | `/okstra-schedule` | Generate a work schedule for an entire task-group |
163
164
  | `/okstra-container-build` | Deploy a verified task's code as a local docker compose group and watch each container's logs (sub-commands: `up` / `status` / `logs` / `stop-watcher` / `down`) |
164
165
  | `/okstra-manager` | Coordinate cross-project okstra tasks through manager-owned plans, assignments, one-way project sync snapshots, status, and child launch context packets |
@@ -21,6 +21,7 @@
21
21
  | 현재 Claude Code 세션에서 okstra run 시작 또는 다음 phase 실행 | `okstra-run` | [`skills/okstra-run.md`](skills/okstra-run.md) |
22
22
  | 여러 프로젝트에 걸친 okstra task 묶음, 할당, sync snapshot, child launch packet 관리 | `okstra-manager` | [`skills/okstra-manager.md`](skills/okstra-manager.md) |
23
23
  | 상태, history, report, time, logs, cost, errors, error-zip, recap 확인 | `okstra-inspect` | [`skills/okstra-inspect.md`](skills/okstra-inspect.md) |
24
+ | task-group(또는 프로젝트 전체)의 여러 task run 결과를 모아 집계·종합 요약 | `okstra-rollup` | [`skills/okstra-rollup.md`](skills/okstra-rollup.md) |
24
25
  | task-group 전체의 클라이언트용 작업 일정 생성 | `okstra-schedule` | [`skills/okstra-schedule.md`](skills/okstra-schedule.md) |
25
26
  | 대화/결정/선호/요구사항을 전역 Memory Book에 저장 또는 검색 | `okstra-memory` | [`skills/okstra-memory.md`](skills/okstra-memory.md) |
26
27
  | implementation task worktree 기반 docker compose 사용자 테스트 환경 관리 | `okstra-container-build` | [`skills/okstra-container-build.md`](skills/okstra-container-build.md) |
@@ -44,7 +45,7 @@
44
45
 
45
46
  ## 공개 스킬 목록
46
47
 
47
- `src/lib/skill-catalog.mjs` 기준 공개 스킬은 다음 8개다.
48
+ `src/lib/skill-catalog.mjs` 기준 공개 스킬은 다음 9개다.
48
49
 
49
50
  - `okstra-setup`
50
51
  - `okstra-brief`
@@ -52,5 +53,6 @@
52
53
  - `okstra-manager`
53
54
  - `okstra-memory`
54
55
  - `okstra-inspect`
56
+ - `okstra-rollup`
55
57
  - `okstra-schedule`
56
58
  - `okstra-container-build`
@@ -0,0 +1,113 @@
1
+ # okstra-rollup AI Manual
2
+
3
+ ## 원천
4
+
5
+ - 스킬 원문: [`skills/okstra-rollup/SKILL.md`](../../../skills/okstra-rollup/SKILL.md)
6
+ - 집계 코어(CLI): [`scripts/okstra_ctl/rollup.py`](../../../scripts/okstra_ctl/rollup.py)
7
+ - Node 래퍼: [`src/commands/inspect/rollup.mjs`](../../../src/commands/inspect/rollup.mjs)
8
+ - 재사용하는 단일-task 집계기: [`scripts/okstra_ctl/time_report.py`](../../../scripts/okstra_ctl/time_report.py), [`scripts/okstra_ctl/error_log_core.py`](../../../scripts/okstra_ctl/error_log_core.py)
9
+ - catalog 열거 헬퍼: [`scripts/okstra_project/state.py`](../../../scripts/okstra_project/state.py) (`list_project_tasks`)
10
+ - 단위 테스트: [`tests/inspect/test_okstra_rollup.py`](../../../tests/inspect/test_okstra_rollup.py)
11
+
12
+ ## 목적
13
+
14
+ `okstra-rollup`은 **여러 task의 run 결과를 한꺼번에 모아 요약**한다. 단일 task를 보는 `okstra-inspect`와 대비되는 task 횡단(cross-task) read-side 레이어다.
15
+
16
+ - 입력 범위: 한 task-group, 또는 `--task-group`을 생략하면 프로젝트 전체 catalog.
17
+ - deterministic 집계(개수·시간 합·에러 합·상태/카테고리/phase 분포)는 `okstra rollup` CLI가 전담한다. 스킬은 그 표를 렌더하고, 각 task의 report 본문을 읽어 **task 횡단 종합 요약(digest)**을 작성한다.
18
+ - 설계 원칙: 집계 손계산은 LLM이 틀리기 쉬우므로 CLI(SSOT)로 내리고, 자연어 종합만 LLM이 맡는다. `okstra-inspect time`이 "절대 손으로 시간을 재합산하지 말 것"이라 못 박는 것과 같은 분업이다.
19
+
20
+ 이 스킬은 read-only다. task 산출물을 mutate 하지 않는다.
21
+
22
+ ## 사용 조건
23
+
24
+ 사용한다:
25
+
26
+ - 사용자가 "롤업", "task-group 요약", "그룹 단위 리포트", "여러 task 결과 모아", "전체 task 현황 요약", "run 결과 한꺼번에 정리"를 요청한다.
27
+ - 한 task가 아니라 **여러 task**를 가로질러 보고 싶을 때.
28
+
29
+ 사용하지 않는다:
30
+
31
+ - 단일 task의 report/시간/에러/recap → `okstra-inspect` (report / time / errors / recap facet).
32
+ - 미래 작업 계획표(non-done task의 클라이언트용 일정) → `okstra-schedule`. rollup은 과거 run 결과를 모으는 **회고형**이고, schedule은 앞으로의 계획을 짜는 **전망형**이다.
33
+ - 실제 phase 실행 → `okstra-run`.
34
+
35
+ ## Preflight
36
+
37
+ 각각 별도 Bash 호출. 모두 리터럴 `okstra` 토큰으로 시작해 `Bash(okstra:*)` 자동 허용을 받는다. `if`/`eval`/`$(...)`/`VAR=`/`||`/`&&`/`npx` fallback으로 감싸지 않는다.
38
+
39
+ ```bash
40
+ okstra ensure-installed --runtime claude-code
41
+ okstra check-project --json
42
+ ```
43
+
44
+ `ensure-installed` 비정상 종료 → `/okstra-setup` 안내 후 멈춘다. `check-project`의 `ok:false` → `/okstra-setup` 안내 후 멈춘다. `ok:true`면 `projectRoot`를 리터럴 문자열로 들고 다음 단계로 간다.
45
+
46
+ ## scope 해석
47
+
48
+ - 사용자가 task-group을 지목("alpha 그룹 요약") → `--task-group <group>`.
49
+ - "전체 task"/"프로젝트 전체"/범위 미지정 → `--task-group` 생략(전체 catalog).
50
+ - 진짜 모호하면 한 번만 묻는다: 한 task-group인지 프로젝트 전체인지. 특정 그룹을 임의로 추측하지 않는다.
51
+
52
+ ## CLI 호출
53
+
54
+ ```bash
55
+ okstra rollup --task-group <group> --project-root <projectRoot> --json
56
+ ```
57
+
58
+ 전체 프로젝트면 `--task-group`을 뺀다. 출력은 항상 JSON이며 **모든 시간은 raw 밀리초**다.
59
+
60
+ ## 출력 해석
61
+
62
+ 최상위:
63
+
64
+ - `taskGroup` — 범위(`null`이면 프로젝트 전체), `taskCount` — task 수.
65
+ - `tasks[]` — task 1개당: `taskKey, taskGroup, taskId, taskType, workCategory, workStatus, currentPhase, currentPhaseState, nextRecommendedPhase, latestRunStatus, updatedAt, reportPath, runCount, cpuSumMs, wallClockMs, errorCount`.
66
+ - `totals` — `runs, cpuSumMs, wallClockMs, errors`, 그리고 `byWorkStatus` / `byWorkCategory` / `byCurrentPhase` / `byTaskType` (각각 `{값: 개수}` 맵).
67
+
68
+ 수치 의미(반드시 지킬 것):
69
+
70
+ - `runCount`는 timeline의 **전체 run 수**다. `cpuSumMs`/`wallClockMs`는 Phase 7 usage에 도달한 run만 반영하므로 `runCount > 0`이어도 `0`일 수 있다.
71
+ - `cpuSumMs`는 lead + worker가 겹쳐 도는 **CPU 합**이지 wall-clock이 아니다. 라벨을 "CPU"로 쓰고, wall-clock은 사용자가 명시적으로 물을 때만 `wallClockMs`로 보여준다.
72
+ - `reportPath`는 프로젝트 상대경로이며 비어 있을 수 있다(아직 report가 없는 task).
73
+ - `taskCount`가 `0`이면 해당 범위에 okstra task가 없다고 말하고 멈춘다.
74
+
75
+ ## 렌더
76
+
77
+ 모든 `*Ms`는 `HH:MM:SS`로 변환한다(zero-pad, raw ms 절대 노출 금지 — `okstra-inspect time`과 같은 규칙). task는 `updatedAt` 내림차순 정렬.
78
+
79
+ ```markdown
80
+ ## okstra Rollup — <task-group 또는 "whole project"> (<taskCount> tasks)
81
+
82
+ | Task | Category | workStatus | Phase | Runs | CPU | Errors | Report |
83
+ |------|----------|------------|-------|------|-----|--------|--------|
84
+ | DEV-1 | bugfix | done | final-verification | 2 | 00:25:00 | 2 | ✓ |
85
+ | DEV-2 | feature | in-progress | implementation | 1 | 00:00:00 | 0 | — |
86
+
87
+ **Totals:** 3 runs · CPU 00:25:00 · 2 errors
88
+ **workStatus:** done 1 · in-progress 1 **category:** bugfix 1 · feature 1
89
+ ```
90
+
91
+ - `Report` 열: `reportPath`가 있으면 `✓`, 없으면 `—`.
92
+ - 상태/카테고리/phase 줄은 `totals`의 tally 맵을 **그대로** 옮긴다. `tasks[]` 배열을 직접 세지 않는다(집계는 CLI가 SSOT).
93
+
94
+ ## digest 작성 (요약 — 스킬의 핵심 가치)
95
+
96
+ 사용자가 "요약"/"정리"/"summarize"/"digest"를 요청한 경우(일반적인 경우):
97
+
98
+ 1. `reportPath`가 비어 있지 않고 `<projectRoot>/<reportPath>` 파일이 실제로 존재하는 task마다 report를 읽고, **무엇을 달성했고 권장 다음 단계가 무엇인지** 1~2줄로 요약한다.
99
+ 2. per-task 줄 위에, 그룹 차원 종합 2~4문장을 쓴다: 그룹 전반에서 무엇이 산출됐는지, 미결 작업이 어디 있는지(`byWorkStatus`/`byCurrentPhase` 활용), 에러 핫스팟(높은 `errorCount` task)이 있는지.
100
+ 3. 각 per-task 주장은 report 경로(`<reportPath>`)로 인용해 독자가 바로 열어볼 수 있게 한다.
101
+
102
+ report가 없는 task는 요약을 지어내지 말고 현재 phase/workStatus를 대신 말한다. report 아닌 산출물을 읽어 빈칸을 메우지 않는다(artifact-home 규칙). report가 비었거나 없으면 그렇다고 말한다.
103
+
104
+ 단일 task의 깊은 drill-down(전체 report, worker별 시간, 에러 분해, run 간 recap)이 필요하면 `/okstra-inspect`로 안내한다.
105
+
106
+ ## 금지 패턴
107
+
108
+ - 집계 수치(합계, 분포)를 `tasks[]`에서 손으로 다시 세기. `totals`가 SSOT다.
109
+ - raw ms 노출. 항상 `HH:MM:SS`.
110
+ - `cpuSumMs`를 wall-clock인 양 표기.
111
+ - report가 없는 task에 대해 요약을 지어내기. 현재 phase/workStatus로 대체한다.
112
+ - okstra 산출물 밖(non-`.okstra`) 파일을 읽어 요약을 채우기.
113
+ - 단일 task 상세를 rollup에서 처리하기. `okstra-inspect`로 보낸다.
@@ -147,9 +147,9 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
147
147
  - [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md) — main okstra lead contract. 런타임 리소스(`~/.okstra/prompts/lead/`)이며 agent skill 이 아닙니다.
148
148
  - [`skills/okstra-setup/SKILL.md`](../../skills/okstra-setup/SKILL.md) — **첫 실행 부트스트랩**. `okstra install` + `project.json` 생성.
149
149
  - [`skills/okstra-run/SKILL.md`](../../skills/okstra-run/SKILL.md) — **현재 claude 세션 안에서 okstra task 를 시작**하는 in-session 진입점. `prepare_task_bundle` 직접 호출.
150
- - 사용자 호출 가능 스킬은 `skills/okstra-setup/SKILL.md`, `skills/okstra-brief/SKILL.md`, `skills/okstra-run/SKILL.md`, `skills/okstra-manager/SKILL.md`, `skills/okstra-memory/SKILL.md`, `skills/okstra-inspect/SKILL.md`, `skills/okstra-schedule/SKILL.md`, `skills/okstra-container-build/SKILL.md` 8종뿐이며, 이것만 agent skill home 으로 복사됩니다 — brief 작성, phase 진행, cross-project manager task 조정, 전역 Memory Book 저장/검색, status/history/report/time/logs/cost/errors/recap read-side, schedule 보조, 그리고 `okstra-container-build` 는 검증 완료된 task 코드를 docker compose 그룹으로 배포하고 컨테이너별 watcher 로 감시하는 비선형(PHASE_SEQUENCE 외부) 컨테이너 배포/감시 스킬. `okstra-manager` 는 `okstra manager` CLI JSON/launch packet 을 source of truth 로 사용하며, manager-owned plan/assignment/directive/snapshot/event 는 `~/.okstra/managers/<manager-id>/` 아래에 저장합니다. `okstra-inspect` 의 read-side facet 은 `skills/okstra-inspect/SKILL.md` 의 sub-command 표가 정본인 8종입니다. `okstra-inspect logs` 는 codex/antigravity wrapper 가 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` 로 남기는 live-log sidecar 의 인벤토리·정리 안내(read-only), `okstra-inspect cost` 는 `okstra context-cost` 결과 요약, `okstra-inspect errors` 는 task 의 okstra-run 에러 로그를 타임스탬프 markdown error-report 로 모아 렌더하고 요약을 출력, `okstra-inspect recap` 은 task 의 run 간 phase 전·후 요약에 더해 `.okstra` 산출물에 대한 자유 Q&A 까지 답합니다.
150
+ - 사용자 호출 가능 스킬은 `skills/okstra-setup/SKILL.md`, `skills/okstra-brief/SKILL.md`, `skills/okstra-run/SKILL.md`, `skills/okstra-manager/SKILL.md`, `skills/okstra-memory/SKILL.md`, `skills/okstra-inspect/SKILL.md`, `skills/okstra-rollup/SKILL.md`, `skills/okstra-schedule/SKILL.md`, `skills/okstra-container-build/SKILL.md` 9종뿐이며, 이것만 agent skill home 으로 복사됩니다 — brief 작성, phase 진행, cross-project manager task 조정, 전역 Memory Book 저장/검색, status/history/report/time/logs/cost/errors/recap read-side, task-group 단위 run 결과 종합(rollup), schedule 보조, 그리고 `okstra-container-build` 는 검증 완료된 task 코드를 docker compose 그룹으로 배포하고 컨테이너별 watcher 로 감시하는 비선형(PHASE_SEQUENCE 외부) 컨테이너 배포/감시 스킬. `okstra-manager` 는 `okstra manager` CLI JSON/launch packet 을 source of truth 로 사용하며, manager-owned plan/assignment/directive/snapshot/event 는 `~/.okstra/managers/<manager-id>/` 아래에 저장합니다. `okstra-rollup` 은 단일 task 집계기(`okstra-inspect` 의 time/errors/recap)를 task-group(또는 프로젝트 전체 catalog)으로 fan-out 하는 read-side 레이어로, deterministic 집계는 `okstra rollup` CLI 가 맡고 report 본문 종합 요약만 스킬(LLM)이 작성합니다. `okstra-inspect` 의 read-side facet 은 `skills/okstra-inspect/SKILL.md` 의 sub-command 표가 정본입니다. `okstra-inspect logs` 는 codex/antigravity wrapper 가 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` 로 남기는 live-log sidecar 의 인벤토리·정리 안내(read-only), `okstra-inspect cost` 는 `okstra context-cost` 결과 요약, `okstra-inspect errors` 는 task 의 okstra-run 에러 로그를 타임스탬프 markdown error-report 로 모아 렌더하고 요약을 출력, `okstra-inspect recap` 은 task 의 run 간 phase 전·후 요약에 더해 `.okstra` 산출물에 대한 자유 Q&A 까지 답합니다.
151
151
  - 내부 운영 계약 — `context-loader` / `team-contract` / `convergence` / `report-writer` 와 lead 계약 — 은 `prompts/lead/*.md` 로, 구현/검증 워커의 언어별 coding preflight 는 `prompts/coding-preflight/*` (overview 라우터 + clean-code + languages/frameworks/architectures 3단계 선택) 로 이동했습니다. 모두 `~/.okstra/prompts/` 에 설치되는 런타임 리소스이며 skill discovery 대상이 아닙니다. generated launch prompt 가 lead 에게 절대 경로를 제공하고, 재설치 시 과거 `okstra-context-loader` / `okstra-team-contract` / `okstra-convergence` / `okstra-report-writer` / `okstra-coding-preflight` / `okstra` skill 디렉터리는 exact-name prune 됩니다.
152
- - 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다. 플러그인 매니페스트는 사용자 진입점 8개(`okstra-setup`, `okstra-brief`, `okstra-run`, `okstra-manager`, `okstra-memory`, `okstra-inspect`, `okstra-schedule`, `okstra-container-build`)만 노출한다.
152
+ - 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다. 플러그인 매니페스트는 사용자 진입점 9개(`okstra-setup`, `okstra-brief`, `okstra-run`, `okstra-manager`, `okstra-memory`, `okstra-inspect`, `okstra-rollup`, `okstra-schedule`, `okstra-container-build`)만 노출한다.
153
153
  - 설치 위치: `~/.claude/skills/<name>/SKILL.md` 또는 `~/.agents/skills/<name>/SKILL.md`.
154
154
  - 릴리스 절차: [`../../RELEASING.md`](../../RELEASING.md) — npm publish 흐름과 release-please / manual fallback.
155
155
 
@@ -301,7 +301,7 @@ Token/cost accounting:
301
301
 
302
302
  ### 4.10 `skills/`
303
303
 
304
- 8 user-facing skills (the only skills `okstra install` copies):
304
+ 9 user-facing skills (the only skills `okstra install` copies):
305
305
 
306
306
  | Skill | User-invocable | Role |
307
307
  |---|---:|---|
@@ -309,6 +309,7 @@ Token/cost accounting:
309
309
  | `okstra-run` | yes | Start/resume okstra task in current Claude Code session |
310
310
  | `okstra-memory` | yes | Store/search/archive global conversation memory under `~/.okstra/memory-book` |
311
311
  | `okstra-inspect` | yes | Unified read-side — sub-commands `status` (lifecycle + workStatus), `history` (past runs / re-run / resume), `report` (find final-report), `time` (elapsed-time breakdown), `logs` (wrapper log inventory + cleanup), `cost` (task bundle context/read cost) |
312
+ | `okstra-rollup` | yes | Cross-task roll-up — aggregate runs/time/errors across a task-group (or whole project) and synthesize a digest from the report files |
312
313
  | `okstra-schedule` | yes | Generate task-group schedule |
313
314
  | `okstra-container-build` | yes | Non-linear deploy tool — deploy a verified task's code as a docker compose group and watch each container (sub-commands `up` / `status` / `logs` / `stop-watcher` / `down`) |
314
315
  | `okstra-manager` | yes | Coordinate cross-project okstra tasks through manager-owned plans, assignments, sync snapshots, status, and child launch context packets |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.105.0",
3
+ "version": "0.106.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.105.0",
3
- "builtAt": "2026-06-27T14:17:33.391Z",
2
+ "package": "0.106.0",
3
+ "builtAt": "2026-06-27T16:35:08.703Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -0,0 +1,113 @@
1
+ """Read-side cross-task roll-up for a task-group (or the whole project).
2
+
3
+ 단일 task 집계기(time-report / error-report / recap)는 task-root 하나만 받는다.
4
+ 이 모듈은 그 위에서 catalog 를 task-group 단위로 fan-out 해 task 별 run 수·소요
5
+ 시간·에러 수·report 경로를 모으고, group 차원의 합계/분포를 deterministic 하게
6
+ roll-up 한다. report 본문 종합(자연어 요약)은 LLM(skill)에 남긴다 — 여기서는
7
+ report 경로만 노출한다. time-report 와 동일하게 raw ms 를 출력하고 HH:MM:SS
8
+ 포맷·Markdown 표 렌더는 호출자(skill)에 위임한다.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ from okstra_ctl.error_log_core import glob_error_logs, parse_records
18
+ from okstra_ctl.time_report import _load_runs, aggregate_time
19
+ from okstra_project import (
20
+ ResolverError,
21
+ list_project_tasks,
22
+ resolve_project_root,
23
+ )
24
+
25
+
26
+ def _collect_task(entry: dict, project_root: Path) -> dict:
27
+ """list_project_tasks entry → roll-up 한 줄. catalog 상태 필드 + time/error 측정.
28
+
29
+ runCount 는 timeline 의 전체 run 수(usable durationMs 없는 run 도 포함)이고
30
+ cpuSumMs/wallClockMs 는 durationMs 가 있는 run 만 반영하므로 0 일 수 있다.
31
+ """
32
+ task_root = Path(entry["_resolvedTaskRoot"])
33
+ runs = _load_runs(task_root)
34
+ time_agg = aggregate_time(runs, project_root)
35
+ records, _ = parse_records(glob_error_logs(task_root))
36
+ return {
37
+ "taskKey": entry.get("taskKey", ""),
38
+ "taskGroup": entry.get("taskGroup", ""),
39
+ "taskId": entry.get("taskId", ""),
40
+ "taskType": entry.get("taskType", ""),
41
+ "workCategory": entry.get("workCategory", "") or "unknown",
42
+ "workStatus": entry.get("workStatus", "") or "unknown",
43
+ "currentPhase": entry.get("currentPhase", ""),
44
+ "currentPhaseState": entry.get("currentPhaseState", ""),
45
+ "nextRecommendedPhase": entry.get("nextRecommendedPhase", ""),
46
+ "latestRunStatus": entry.get("latestRunStatus", ""),
47
+ "updatedAt": entry.get("updatedAt", ""),
48
+ "reportPath": entry.get("latestReportPath", ""),
49
+ "runCount": len(runs),
50
+ "cpuSumMs": time_agg["grandTotal"]["cpuSumMs"],
51
+ "wallClockMs": sum(r["wallClockMs"] for r in time_agg["perRunWallClock"]),
52
+ "errorCount": len(records),
53
+ }
54
+
55
+
56
+ def _tally(tasks: list[dict], key: str) -> dict:
57
+ """task 들의 한 필드를 값별 개수로. 빈 값은 'unknown' 으로 접는다."""
58
+ counts: dict[str, int] = {}
59
+ for task in tasks:
60
+ bucket = task.get(key) or "unknown"
61
+ counts[bucket] = counts.get(bucket, 0) + 1
62
+ return dict(sorted(counts.items()))
63
+
64
+
65
+ def build_rollup(project_root: Path, task_group: str | None) -> dict:
66
+ entries = list_project_tasks(project_root, task_group=task_group or None)
67
+ tasks = [_collect_task(e, project_root) for e in entries]
68
+ totals = {
69
+ "runs": sum(t["runCount"] for t in tasks),
70
+ "cpuSumMs": sum(t["cpuSumMs"] for t in tasks),
71
+ "wallClockMs": sum(t["wallClockMs"] for t in tasks),
72
+ "errors": sum(t["errorCount"] for t in tasks),
73
+ "byWorkStatus": _tally(tasks, "workStatus"),
74
+ "byWorkCategory": _tally(tasks, "workCategory"),
75
+ "byCurrentPhase": _tally(tasks, "currentPhase"),
76
+ "byTaskType": _tally(tasks, "taskType"),
77
+ }
78
+ return {
79
+ "ok": True,
80
+ "projectRoot": str(project_root),
81
+ "taskGroup": task_group or None,
82
+ "taskCount": len(tasks),
83
+ "tasks": tasks,
84
+ "totals": totals,
85
+ }
86
+
87
+
88
+ def main(argv: list[str] | None = None) -> int:
89
+ parser = argparse.ArgumentParser(
90
+ prog="okstra rollup",
91
+ description="Roll up run results across a task-group's tasks (raw ms).")
92
+ parser.add_argument("--task-group", default="",
93
+ help="scope to this task-group (default: whole project catalog)")
94
+ parser.add_argument("--project-root", default="")
95
+ parser.add_argument("--cwd", default=".")
96
+ parser.add_argument("--json", action="store_true", help="emit JSON (always on)")
97
+ args = parser.parse_args(argv)
98
+
99
+ try:
100
+ project_root = Path(
101
+ resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
102
+ ).resolve()
103
+ except ResolverError as exc:
104
+ print(json.dumps({"ok": False, "stage": "resolve", "reason": str(exc)}))
105
+ return 2
106
+
107
+ result = build_rollup(project_root, args.task_group)
108
+ print(json.dumps(result, ensure_ascii=False, indent=2))
109
+ return 0
110
+
111
+
112
+ if __name__ == "__main__":
113
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: okstra-rollup
3
+ description: |
4
+ Use when the user wants run results from MULTIPLE okstra tasks collected and summarized at once — a task-group digest or a whole-project roll-up, not a single task. Aggregates per-task run count, elapsed time, error count, and latest report path, plus group-level totals and status/category/phase tallies, then synthesizes a cross-task prose summary from the report files. Make sure to use this skill whenever the user mentions "okstra rollup", "롤업", "task-group 요약", "그룹 단위 리포트", "여러 task 결과 모아", "전체 task 현황 요약", "cross-task summary", "그룹 종합 리포트", "모든 task 정리", "run 결과 한꺼번에", even if they don't say "rollup". For a SINGLE task's report/time/errors/recap use okstra-inspect instead; for a forward-looking work plan over non-done tasks use okstra-schedule.
5
+ ---
6
+
7
+ # OKSTRA Rollup
8
+
9
+ Cross-task roll-up: collect every catalog task's run results (optionally scoped to one task-group) and summarize them together. The `rollup` CLI does all deterministic aggregation — counts, time sums, error tallies, status/category/phase distributions. You resolve scope, call it, render the table, and synthesize the prose digest from the report files. **Never recompute totals or re-tally by hand** — the CLI is the SSOT for the numbers.
10
+
11
+ This skill is read-only. It never mutates task artifacts.
12
+
13
+ ## Step 0: Verify okstra runtime + project setup
14
+
15
+ Run each command as a **separate Bash tool call**, each starting with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `$(...)`, `VAR=...`, `||`, `&&`, or an `npx` fallback — those defeat the permission match.
16
+
17
+ 1. `okstra ensure-installed --runtime claude-code`
18
+ Non-zero exit → tell the user: "okstra not installed — run `/okstra-setup` first." Then stop.
19
+
20
+ 2. `okstra check-project --json`
21
+ Parse `{ok, projectRoot, ...}` from stdout. `ok: false` → "this project has no okstra setup. Run `/okstra-setup` first." Then stop. `ok: true` → carry `projectRoot` as a literal string.
22
+
23
+ ## Step 1: Resolve scope
24
+
25
+ - User named a task-group (e.g. "alpha 그룹 요약") → use it as `--task-group <group>`.
26
+ - User asked for the whole project ("전체 task", "프로젝트 전체") or named no scope → omit `--task-group` (whole catalog).
27
+ - If genuinely ambiguous, ask once: one task-group, or the whole project? Do not silently guess a specific group.
28
+
29
+ ## Step 2: Fetch the roll-up
30
+
31
+ ```bash
32
+ okstra rollup --task-group <group> --project-root <projectRoot> --json
33
+ ```
34
+
35
+ Omit `--task-group` for the whole project. The JSON shape (all durations are **raw milliseconds**):
36
+
37
+ - `taskGroup` — the scope (`null` = whole project), `taskCount` — number of tasks.
38
+ - `tasks[]` — per task: `taskKey, taskGroup, taskId, taskType, workCategory, workStatus, currentPhase, currentPhaseState, nextRecommendedPhase, latestRunStatus, updatedAt, reportPath, runCount, cpuSumMs, wallClockMs, errorCount`.
39
+ - `totals` — `runs, cpuSumMs, wallClockMs, errors`, plus `byWorkStatus`, `byWorkCategory`, `byCurrentPhase`, `byTaskType` (each a `{value: count}` map).
40
+
41
+ `runCount` counts every timeline run; `cpuSumMs`/`wallClockMs` only reflect runs that reached Phase 7 usage, so they can be `0` while `runCount > 0`. `reportPath` is project-relative and may be empty (task not yet reported).
42
+
43
+ If `taskCount` is `0`, tell the user there are no okstra tasks in that scope and stop.
44
+
45
+ ## Step 3: Render the roll-up table
46
+
47
+ Convert every `*Ms` to `HH:MM:SS` (zero-pad; never show raw ms). `cpuSumMs` is CPU-sum (lead + workers overlap), not wall-clock — label it "CPU"; only surface `wallClockMs` when the user asks. Sort tasks by `updatedAt` desc.
48
+
49
+ ```markdown
50
+ ## okstra Rollup — <task-group or "whole project"> (<taskCount> tasks)
51
+
52
+ | Task | Category | workStatus | Phase | Runs | CPU | Errors | Report |
53
+ |------|----------|------------|-------|------|-----|--------|--------|
54
+ | DEV-1 | bugfix | done | final-verification | 2 | 00:25:00 | 2 | ✓ |
55
+ | DEV-2 | feature | in-progress | implementation | 1 | 00:00:00 | 0 | — |
56
+
57
+ **Totals:** 3 runs · CPU 00:25:00 · 2 errors
58
+ **workStatus:** done 1 · in-progress 1 **category:** bugfix 1 · feature 1
59
+ ```
60
+
61
+ Render `Report` as `✓` when `reportPath` is non-empty, else `—`. Build the status/category lines from the `totals` tally maps verbatim — do not count the `tasks[]` array yourself.
62
+
63
+ ## Step 4: Synthesize the digest (the summary)
64
+
65
+ This is the skill's value-add over a bare table. When the user asked to "요약"/"summarize"/"digest"/"정리" (the common case), produce a short cross-task narrative:
66
+
67
+ 1. For each task with a non-empty `reportPath` whose file exists under `<projectRoot>/<reportPath>`, read it and write a 1–2 line summary of what it accomplished and its recommended next step.
68
+ 2. Above the per-task lines, write a 2–4 sentence group-level synthesis: what was delivered across the group, where the open work sits (use `byWorkStatus`/`byCurrentPhase`), and any error hot-spots (tasks with high `errorCount`).
69
+ 3. Cite each per-task claim with the report path as `<reportPath>` so the reader can open it.
70
+
71
+ For tasks with no report, state the current phase/workStatus instead of inventing a summary — do not read non-report artifacts to fill the gap.
72
+
73
+ Keep the digest to what the reports actually say; if a report is missing or empty, say so rather than guessing. For a deeper single-task drill-down (full report, per-worker time, error breakdown, run-to-run recap), point the user to `/okstra-inspect`.
@@ -152,6 +152,13 @@ export const COMMAND_REGISTRY = [
152
152
  category: "introspection",
153
153
  summary: ["Assemble a task's run-to-run phase recap or append a recap log entry"],
154
154
  },
155
+ {
156
+ name: "rollup",
157
+ module: "./commands/inspect/rollup.mjs",
158
+ export: "run",
159
+ category: "introspection",
160
+ summary: ["Roll up run results across a task-group's tasks (raw ms + tallies)"],
161
+ },
155
162
  {
156
163
  name: "container",
157
164
  module: "./commands/inspect/container.mjs",
@@ -0,0 +1,27 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra rollup — roll up run results across a task-group's tasks
4
+
5
+ Usage:
6
+ okstra rollup [--task-group <group>] [--project-root <dir>] [--cwd <dir>]
7
+
8
+ Read-only. Prints a JSON roll-up of every catalog task (optionally scoped to one
9
+ task-group): per-task run count, elapsed time (raw ms), error count, and the
10
+ latest report path, plus group-level totals and status/category/phase tallies.
11
+ With no --task-group it covers the whole project catalog. Time is raw ms; the
12
+ caller formats HH:MM:SS and synthesizes the prose digest from the report files.
13
+ `;
14
+
15
+ export async function run(args) {
16
+ if (args.includes("--help") || args.includes("-h")) {
17
+ process.stdout.write(USAGE);
18
+ return 0;
19
+ }
20
+
21
+ const result = await runPythonModule({
22
+ module: "okstra_ctl.rollup",
23
+ args,
24
+ stdio: "inherit-stdout",
25
+ });
26
+ return result.code;
27
+ }
@@ -11,6 +11,7 @@ export const USER_SKILL_NAMES = Object.freeze([
11
11
  "okstra-manager",
12
12
  "okstra-memory",
13
13
  "okstra-inspect",
14
+ "okstra-rollup",
14
15
  "okstra-schedule",
15
16
  "okstra-container-build",
16
17
  ]);