okstra 0.97.1 → 0.98.1
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 +4 -3
- package/README.md +4 -3
- package/docs/kr/architecture.md +2 -2
- package/docs/kr/cli.md +2 -0
- package/docs/kr/container.md +122 -0
- package/docs/project-structure-overview.md +12 -2
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +714 -0
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +125 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -18
- package/runtime/agents/workers/claude-worker.md +10 -33
- package/runtime/agents/workers/codex-worker.md +2 -18
- package/runtime/agents/workers/report-writer-worker.md +2 -10
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +5 -2
- package/runtime/bin/okstra-antigravity-exec.sh +6 -1
- package/runtime/bin/okstra-claude-exec.sh +6 -1
- package/runtime/bin/okstra-codex-exec.sh +6 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +1 -0
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_coverage-critic.md +17 -0
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-self-check.md +2 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/_stage-discipline.md +5 -8
- package/runtime/prompts/profiles/error-analysis.md +5 -5
- package/runtime/prompts/profiles/final-verification.md +4 -4
- package/runtime/prompts/profiles/implementation-planning.md +7 -7
- package/runtime/prompts/profiles/implementation.md +5 -5
- package/runtime/prompts/profiles/improvement-discovery.md +6 -6
- package/runtime/prompts/profiles/release-handoff.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +4 -4
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/__init__.py +4 -1
- package/runtime/python/okstra_ctl/container.py +970 -0
- package/runtime/python/okstra_ctl/container_registry.py +93 -0
- package/runtime/python/okstra_ctl/error_zip.py +4 -4
- package/runtime/python/okstra_ctl/handoff.py +7 -1
- package/runtime/python/okstra_ctl/ids.py +40 -1
- package/runtime/python/okstra_ctl/listing.py +3 -5
- package/runtime/python/okstra_ctl/log_report.py +102 -0
- package/runtime/python/okstra_ctl/pane_reclaim.py +5 -4
- package/runtime/python/okstra_ctl/paths.py +40 -0
- package/runtime/python/okstra_ctl/plan_run_root.py +78 -0
- package/runtime/python/okstra_ctl/reconcile.py +4 -2
- package/runtime/python/okstra_ctl/resolve_task_key.py +54 -0
- package/runtime/python/okstra_ctl/run.py +48 -30
- package/runtime/python/okstra_ctl/stage_integrate.py +8 -2
- package/runtime/python/okstra_ctl/stage_targets.py +79 -0
- package/runtime/python/okstra_ctl/time_report.py +200 -0
- package/runtime/python/okstra_ctl/tmux.py +67 -0
- package/runtime/python/okstra_ctl/wizard.py +35 -20
- package/runtime/python/okstra_project/__init__.py +2 -0
- package/runtime/python/okstra_project/state.py +50 -2
- package/runtime/skills/okstra-brief/SKILL.md +17 -7
- package/runtime/skills/okstra-container/SKILL.md +169 -0
- package/runtime/skills/okstra-inspect/SKILL.md +64 -178
- package/runtime/skills/okstra-memory/SKILL.md +8 -6
- package/runtime/skills/okstra-run/SKILL.md +4 -4
- package/runtime/skills/okstra-schedule/SKILL.md +9 -16
- package/runtime/skills/okstra-setup/SKILL.md +10 -9
- package/runtime/templates/reports/brief.template.md +1 -1
- package/runtime/templates/reports/final-report.template.md +1 -1
- package/runtime/templates/reports/settings.template.json +3 -1
- package/runtime/validators/validate-implementation-plan-stages.py +11 -3
- package/src/cli-registry.mjs +28 -0
- package/src/commands/inspect/container.mjs +27 -0
- package/src/commands/inspect/log-report.mjs +26 -0
- package/src/commands/inspect/resolve-task-key.mjs +26 -0
- package/src/commands/inspect/task-list.mjs +26 -15
- package/src/commands/inspect/time-report.mjs +26 -0
- package/src/commands/lifecycle/check-project.mjs +7 -1
- package/src/commands/lifecycle/doctor.mjs +16 -0
- package/src/lib/skill-catalog.mjs +1 -0
- package/runtime/agents/TODO.md +0 -226
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-inspect
|
|
3
3
|
description: |
|
|
4
|
-
Use for any read-side okstra inspection or status mutation.
|
|
4
|
+
Use for any read-side okstra inspection or status mutation. Trigger words include "okstra status", "task status", "current phase", "next phase", "okstra status set", "okstra mark", "<task-id> done|in-progress|진행중|완료", "okstra history", "past runs", "resume", "re-run", "list tasks", "find report", "show report for", "read the okstra report", "continue from report", "작업 시간", "소요 시간", "time summary", "okstra logs", "로그 현황", "log cleanup", "okstra context-cost", "context cost", "컨텍스트 비용", "okstra errors", "error report", "에러 리포트", "okstra error-zip", "에러 zip", "에러 환류", "cross-project 에러", "okstra recap", "recap", "<task-id> 요약", "실패 로그 정리".
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA Inspect
|
|
@@ -72,7 +72,7 @@ Read `.okstra/discovery/task-catalog.json`. The catalog is the authoritative sou
|
|
|
72
72
|
|
|
73
73
|
Sort by `updatedAt` desc, then `taskKey`.
|
|
74
74
|
|
|
75
|
-
The overview table is intentionally narrow so it renders cleanly in a terminal. Only six columns are shown; for any task that needs a closer look (phase state, routing, approval gate, last run status, resume path, etc.) tell the user to run
|
|
75
|
+
The overview table is intentionally narrow so it renders cleanly in a terminal. Only six columns are shown; for any task that needs a closer look (phase state, routing, approval gate, last run status, resume path, etc.) tell the user to run `/okstra-inspect status <task-key>` for the detail view in `status.2`.
|
|
76
76
|
|
|
77
77
|
If `awaitingApproval` is true OR `routingStatus == "pending"`, append a `*` to the `Next` cell and explain the marker once below the table.
|
|
78
78
|
|
|
@@ -84,7 +84,7 @@ If `awaitingApproval` is true OR `routingStatus == "pending"`, append a `*` to t
|
|
|
84
84
|
| 1 | proj:group:id | bugfix | error-analysis | in-progress | implementation-planning |
|
|
85
85
|
| 2 | proj:group:id2 | feature | requirements-discovery | done | pending-routing-decision* |
|
|
86
86
|
|
|
87
|
-
`*` = awaiting user approval or pending routing decision. Run
|
|
87
|
+
`*` = awaiting user approval or pending routing decision. Run `/okstra-inspect status <task-key>` for details.
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
### status.2 — Specific task status
|
|
@@ -150,7 +150,7 @@ Recognize requests to change a task's `workStatus` and update the corresponding
|
|
|
150
150
|
|
|
151
151
|
Natural language: "DEV-6827 done으로 바꿔줘", "PROD-1623을 blocked로 표시", "DEV-9047 진행중", "Mark DEV-6827 as done".
|
|
152
152
|
|
|
153
|
-
Explicit
|
|
153
|
+
Explicit phrasing (recognized user utterances — these are **not** okstra CLI subcommands; `status.4` edits the manifest directly):
|
|
154
154
|
- `okstra status set <task-id> <status>`
|
|
155
155
|
- `okstra status set <task-group> <task-id> <status>` (disambiguation)
|
|
156
156
|
- `okstra mark <task-id> <status>`
|
|
@@ -161,9 +161,11 @@ Accepted `<status>` values: `todo`, `in-progress`, `blocked`, `done`.
|
|
|
161
161
|
**Procedure:**
|
|
162
162
|
|
|
163
163
|
1. **Validate status value.** If not in the enum, output an error and the allowed values list. Do not modify any file.
|
|
164
|
-
2. **
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
2. **Resolve the task** with the shared resolver (pass `--task-group` when the user gave one — it scopes the match case-insensitively):
|
|
165
|
+
```bash
|
|
166
|
+
okstra resolve-task-key <task-id> [--task-group <task-group>] --project-root <projectRoot> --json
|
|
167
|
+
```
|
|
168
|
+
Branch on `matches[]`: single → proceed. Multiple → list candidates and ask the user to retry with the explicit form (`okstra status set <task-group> <TASK-ID> <status>`). None → `<TASK-ID>를 찾을 수 없습니다.` and stop.
|
|
167
169
|
3. **Open the matching `task-manifest.json`** at `.okstra/tasks/<task-group-segment>/<task-id-segment>/task-manifest.json`. Assemble the path from the catalog entry's `taskGroupPathSegment` and `taskIdPathSegment` fields (filesystem-safe segments emitted by the renderer) — NOT from the raw user-supplied strings, which may differ in case or contain characters normalized when the manifest was created. Prefer the `taskManifestPath` field directly when present.
|
|
168
170
|
4. **Update fields at the manifest root:**
|
|
169
171
|
- `workStatus` ← new status value
|
|
@@ -265,7 +267,7 @@ Builds a fresh run — new run-seq, new manifest, new report — using parameter
|
|
|
265
267
|
4. **`taskType: implementation` only — resolve `--base-ref`:** base ref is NOT in the run-manifest; it lives in the worktree registry at `~/.okstra/worktrees/registry.json`. If a worktree is already registered, existing branch & base are reused — omit `--base-ref` unless the user explicitly wants a different starting point. If no worktree is registered (cleaned up), `--base-ref` is mandatory — ask the user before running.
|
|
266
268
|
5. Display the assembled command:
|
|
267
269
|
```bash
|
|
268
|
-
okstra.sh \
|
|
270
|
+
~/.okstra/bin/okstra.sh \
|
|
269
271
|
--project-id <project-id> \
|
|
270
272
|
--task-group <task-group> \
|
|
271
273
|
--task-id <task-id> \
|
|
@@ -294,7 +296,7 @@ Trigger phrases: "find report", "show report for", "read the okstra report", "co
|
|
|
294
296
|
|
|
295
297
|
task-key format: `<project-id>:<task-group>:<task-id>`.
|
|
296
298
|
|
|
297
|
-
**Normalization:** task-key matching is lowercase. Disk segments are slugified (lowercase + non-alphanumeric runs → `-`) per `scripts/
|
|
299
|
+
**Normalization:** task-key matching is lowercase. Disk segments are slugified (lowercase + non-alphanumeric runs → `-`) per `scripts/okstra_ctl/ids.py:88` (`slugify_task_segment`, the SSOT; `interactive.sh` consumes it via import). Catalog lookup is case-insensitive; file path assembly uses slugified segments.
|
|
298
300
|
|
|
299
301
|
Lookup methods (in priority order):
|
|
300
302
|
|
|
@@ -321,11 +323,7 @@ D. **Specific task-type (fallback):** `latestReportPath` is task-type-agnostic.
|
|
|
321
323
|
After reading the report, surface follow-up options:
|
|
322
324
|
|
|
323
325
|
1. **구현 진행:** based on the report's "권장 다음 단계" section.
|
|
324
|
-
2. **추가 검증:** new okstra run with the same task-key
|
|
325
|
-
```bash
|
|
326
|
-
scripts/okstra.sh --task-key <task-key> --task-type <task-type>
|
|
327
|
-
```
|
|
328
|
-
For richer options (base-ref, workers, render-only), use the `history` sub-command.
|
|
326
|
+
2. **추가 검증:** to launch a new okstra run with the same task-key, assemble the command through the `history` sub-command — it offers the full option set (base-ref, workers, render-only) and renders a host-correct invocation.
|
|
329
327
|
3. **관련 task 확인:** if the report references related task-keys, fetch their reports too.
|
|
330
328
|
|
|
331
329
|
### report — Output template
|
|
@@ -349,100 +347,34 @@ After reading the report, surface follow-up options:
|
|
|
349
347
|
|
|
350
348
|
Trigger phrases: "작업 시간", "소요 시간", "time summary", "duration", "elapsed", "얼마나 걸렸", "시간 분석".
|
|
351
349
|
|
|
352
|
-
Aggregate elapsed work time for a
|
|
353
|
-
|
|
354
|
-
**Data sources** (both collected by okstra):
|
|
355
|
-
|
|
356
|
-
1. `.okstra/tasks/<task-group>/<task-id>/history/timeline.json` — `runs` array with `runTimestamp`, `taskType`, `status`, `teamStatePath`. `teamStatePath` is project-root-relative (written by `render.py` via `_rel(project_root, ...)`).
|
|
357
|
-
2. Each run's `.../runs/<task-type>/state/team-state-<suffix>.json` — populated by `scripts/okstra-token-usage.py` at Phase 7. Contains `leadUsage.{startedAt, endedAt, durationMs}` and `workers[].{workerId, agent, usage.{startedAt, endedAt, durationMs}}`.
|
|
358
|
-
|
|
359
|
-
If a run never reached Phase 7, its `team-state` lacks `durationMs`. Mark such runs as `unavailable` rather than guessing.
|
|
360
|
-
|
|
361
|
-
### time.1 — Resolve task-id → timeline path
|
|
362
|
-
|
|
363
|
-
1. If the user gave a full `task-key`, use it directly.
|
|
364
|
-
2. Otherwise read `.okstra/discovery/task-catalog.json` and find the entry whose `taskId` matches.
|
|
365
|
-
3. Multiple matches → list candidates (`taskKey`, `taskType`, `updatedAt`) and ask the user to pick.
|
|
366
|
-
4. Read `historyTimelinePath` from the chosen entry.
|
|
367
|
-
|
|
368
|
-
If `task-catalog.json` is missing: `No okstra history found. Run /okstra-run first.`
|
|
369
|
-
|
|
370
|
-
### time.2 — Walk runs and collect durations
|
|
371
|
-
|
|
372
|
-
For each entry in `timeline.json`'s `runs` array:
|
|
350
|
+
Aggregate elapsed work time for a task, grouped by **task type** and broken down by **worker** (lead + each worker). The `time-report` CLI reads both data sources (`history/timeline.json` runs + each run's `team-state-*.json` usage blocks) and does **all** aggregation — cross-run sums, CPU-sum vs wall-clock, timestamp parsing, unavailable detection. You only resolve the task-key, call it, and render. **Never recompute durations by hand.**
|
|
373
351
|
|
|
374
|
-
1
|
|
375
|
-
2. Extract `taskType` from the timeline entry (authoritative), `leadUsage.durationMs` and `leadUsage.{startedAt,endedAt}`, and for each `worker` in `workers[]`: `workerId`, `agent`, `usage.durationMs`. Read defensively — `usage` (and `leadUsage`) may be:
|
|
376
|
-
- a normal `usage_block` with `durationMs >= 0`,
|
|
377
|
-
- a `na_block` with `{"source": "unavailable", "durationMs": 0, "note": ...}` when Phase 7 collection failed,
|
|
378
|
-
- missing entirely (older team-state files), or `None`.
|
|
379
|
-
Normalize via `(block or {}).get("durationMs", 0) or 0`, and treat `source == "unavailable"` as zero contribution.
|
|
380
|
-
3. If the team-state file is missing, or every `durationMs` for the run is `0`/absent, record the run under `unavailable` with its `runTimestamp` and `taskType`.
|
|
352
|
+
### time.1 — Resolve the task-key
|
|
381
353
|
|
|
382
|
-
|
|
354
|
+
Same standard as `cost.1`: full task-key → use directly; bare task-id → `okstra resolve-task-key <task-id> --project-root <projectRoot> --json` and branch on `matches[]` (0 = not found, 1 = use, N = 3-option picker). If no okstra history exists, say `No okstra history found. Run /okstra-run first.`
|
|
383
355
|
|
|
384
|
-
|
|
356
|
+
### time.2 — Fetch aggregated data
|
|
385
357
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
| `Runs` | count of runs of that task type that contributed any duration |
|
|
389
|
-
| `CPU sum` | sum of (lead + all workers) across those runs |
|
|
390
|
-
| `Lead` | sum of `leadUsage.durationMs` |
|
|
391
|
-
| `Workers` | sum of all `workers[].usage.durationMs` |
|
|
392
|
-
|
|
393
|
-
Add a final `Grand total` row.
|
|
394
|
-
|
|
395
|
-
**Note on `CPU sum` vs wall-clock:** workers run as children of the lead session, so the lead's `durationMs` window OVERLAPS its workers' windows. `CPU sum = Lead + Workers` is an additive CPU-style sum, not the wall-clock elapsed time the user actually waited.
|
|
396
|
-
|
|
397
|
-
Worked example for one run with three concurrent workers:
|
|
398
|
-
|
|
399
|
-
```
|
|
400
|
-
lead [================================] durationMs = 1800000 (30:00)
|
|
401
|
-
claude [============] durationMs = 720000 (12:00)
|
|
402
|
-
codex [==============] durationMs = 840000 (14:00)
|
|
403
|
-
antigravity [========] durationMs = 480000 (08:00)
|
|
358
|
+
```bash
|
|
359
|
+
okstra time-report <task-key> --project-root <projectRoot> --json
|
|
404
360
|
```
|
|
405
361
|
|
|
406
|
-
|
|
407
|
-
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
For each task type, list one row per `workerId` actually present, plus `lead`. Aggregate `durationMs` across all runs of that task type.
|
|
414
|
-
|
|
415
|
-
| Worker | Runs | Total | Avg/run |
|
|
416
|
-
|--------|------|-------|---------|
|
|
417
|
-
|
|
418
|
-
- `Runs` denominator = number of runs of this task type in which this worker recorded a **nonzero** `durationMs`. A run where the worker's block was `na_block`, missing, or `0` does NOT count.
|
|
419
|
-
- If `Runs == 0` for a worker, **omit the row entirely** rather than dividing by zero.
|
|
420
|
-
- `Avg/run = Total / Runs` (integer ms, then format to `HH:MM:SS`).
|
|
421
|
-
|
|
422
|
-
Use the `workerId` from team-state. Valid enum: `lead, claude, codex, antigravity, report-writer`.
|
|
362
|
+
Returns (all durations are **raw milliseconds**):
|
|
363
|
+
- `byTaskType[]` — `{taskType, runs, leadMs, workersMs, cpuSumMs}`, plus `grandTotal`
|
|
364
|
+
- `perWorker` — `{<taskType>: [{workerId, agents[], runs, totalMs, avgMs}]}`; only workers with a nonzero run appear, and `agents[]` lists agent labels that differ from `workerId`
|
|
365
|
+
- `perRunWallClock[]` — `{runTimestamp, taskType, wallClockMs}` (max `endedAt` − min `startedAt` per run)
|
|
366
|
+
- `phaseTimelines[]` — `{runTimestamp, taskType, phases:[{phase, firstAt, wallMsToNext}]}`
|
|
367
|
+
- `unavailable[]` — `{runTimestamp, taskType, reason}` for runs with no Phase-7 durations (never summed into totals)
|
|
423
368
|
|
|
424
|
-
|
|
425
|
-
- If every run of this task type used `agent == workerId` for this row, display the bare `workerId`.
|
|
426
|
-
- If `agent` differs (e.g. a `claude` worker slot ran with `agent == "sonnet-eval"`), display `workerId (agent)`. Multiple distinct agents across runs → comma-join: `claude (sonnet-eval, opus-eval)`.
|
|
369
|
+
### time.3 — Render
|
|
427
370
|
|
|
428
|
-
|
|
371
|
+
Convert every `*Ms` to `HH:MM:SS` (zero-pad; never show raw ms). Task types in `byTaskType` are already in chronological (first-appearance) order.
|
|
429
372
|
|
|
430
|
-
**
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
-
|
|
435
|
-
- `wallMsToNext` is `null` on the last phase (the run-end signal is not a marker) — render `--`.
|
|
436
|
-
- `phases: []` means the lead emitted no markers (run predates the contract, or markers were skipped) — say "phase timeline 측정 불가(마커 없음)" rather than estimating from other fields.
|
|
437
|
-
- Older team-state files have no `phaseTimeline` key at all — omit the section silently.
|
|
438
|
-
|
|
439
|
-
**Timestamp parsing:** for `startedAt` / `endedAt`, normalize ISO-8601: replace trailing `Z` with `+00:00`, accept explicit offsets as-is, parse via `datetime.fromisoformat(s.replace("Z", "+00:00"))`. Strings without an offset are assumed UTC. Mixed-form comparisons must be done as `datetime` objects, never raw strings.
|
|
440
|
-
|
|
441
|
-
### time.4 — Format output
|
|
442
|
-
|
|
443
|
-
- Convert `durationMs` to `HH:MM:SS` (zero-pad). Example: `7384000ms` → `02:03:04`.
|
|
444
|
-
- Sort task types by order of first appearance in the timeline (chronological, not alphabetical).
|
|
445
|
-
- If any runs were `unavailable`, append a final note listing them with reason.
|
|
373
|
+
- **By task type** — `| Task type | Runs | CPU sum | Lead | Workers |` from `byTaskType`, plus a `grandTotal` row. `CPU sum` (= Lead + Workers) overlaps because workers run inside the lead's window — it is *not* wall-clock. Surface wall-clock only when the user explicitly asks, from `perRunWallClock`.
|
|
374
|
+
- **Per worker** (per task type) — `| Worker | Runs | Total | Avg/run |` from `perWorker`. Render the worker as bare `workerId` when `agents` is empty, else `workerId (agent1, agent2)`.
|
|
375
|
+
- **Phase breakdown** — only when the user asks ("단계별"/"phase별"/"어느 단계가 오래"): one table per run from `phaseTimelines`: `| Phase | Start | Wall to next |` using `firstAt`/`wallMsToNext` (`null` → `--`). A run with empty `phases` → "phase timeline 측정 불가(마커 없음)".
|
|
376
|
+
- If `unavailable[]` is non-empty, append a trailing note listing each run with its reason. Never fold them into totals.
|
|
377
|
+
- Show the resolved `<task-key>` in the heading.
|
|
446
378
|
|
|
447
379
|
```markdown
|
|
448
380
|
## Time summary — <task-key>
|
|
@@ -452,41 +384,17 @@ team-state written by collector ≥ v0.73 carries a `phaseTimeline` block — th
|
|
|
452
384
|
| Task type | Runs | CPU sum | Lead | Workers |
|
|
453
385
|
|------------------------|------|-----------|----------|----------|
|
|
454
386
|
| requirements-discovery | 2 | 00:33:12 | 00:12:08 | 00:21:04 |
|
|
455
|
-
| error-analysis | 1 | 00:18:45 | 00:08:11 | 00:10:34 |
|
|
456
|
-
| implementation | 3 | 02:11:09 | 00:45:30 | 01:25:39 |
|
|
457
387
|
| **Grand total** | 6 | **03:03:06** | 01:05:49 | 01:57:17 |
|
|
458
388
|
|
|
459
|
-
`CPU sum` adds the lead window to each worker window even though they overlap; it is not a wall-clock total.
|
|
460
|
-
|
|
461
389
|
### Per worker — requirements-discovery
|
|
462
390
|
|
|
463
|
-
| Worker
|
|
464
|
-
|
|
465
|
-
| lead
|
|
466
|
-
| claude
|
|
467
|
-
| codex | 2 | 00:07:40 | 00:03:50 |
|
|
468
|
-
| antigravity | 2 | 00:03:12 | 00:01:36 |
|
|
469
|
-
| report-writer | 2 | 00:01:00 | 00:00:30 |
|
|
470
|
-
|
|
471
|
-
> Unavailable: 1 run (implementation / 2026-04-30_03-03-48) — team-state has no durationMs (Phase 7 not reached)
|
|
472
|
-
|
|
473
|
-
### Phase breakdown — requirements-discovery (run 002)
|
|
474
|
-
|
|
475
|
-
| Phase | Start (UTC) | Wall to next |
|
|
476
|
-
|------------------------|------------------|--------------|
|
|
477
|
-
| phase-1-intake | 2026-06-11 09:00 | 00:04:00 |
|
|
478
|
-
| phase-2-prompts | 2026-06-11 09:04 | 00:02:10 |
|
|
479
|
-
| phase-4-dispatch | 2026-06-11 09:06 | 00:18:30 |
|
|
480
|
-
| phase-5.5-convergence | 2026-06-11 09:25 | 00:21:00 |
|
|
481
|
-
| phase-6-synthesis | 2026-06-11 09:46 | 00:15:00 |
|
|
482
|
-
| phase-7-persist | 2026-06-11 10:01 | -- |
|
|
483
|
-
```
|
|
391
|
+
| Worker | Runs | Total | Avg/run |
|
|
392
|
+
|----------------------|------|----------|----------|
|
|
393
|
+
| lead | 2 | 00:12:08 | 00:06:04 |
|
|
394
|
+
| claude (sonnet-eval) | 2 | 00:09:12 | 00:04:36 |
|
|
484
395
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
- Never invent or estimate `durationMs`. Missing → `--`.
|
|
488
|
-
- Never sum across `unavailable` runs into the totals — those are reported only in the trailing note.
|
|
489
|
-
- Show the resolved `<task-key>` in the heading so the user can confirm disambiguation.
|
|
396
|
+
> Unavailable: 1 run (implementation / 2026-04-30_03-03-48) — Phase 7 not reached
|
|
397
|
+
```
|
|
490
398
|
|
|
491
399
|
---
|
|
492
400
|
|
|
@@ -512,13 +420,16 @@ If the user gives a full task-key, run:
|
|
|
512
420
|
okstra context-cost <task-key> --project-root <projectRoot>
|
|
513
421
|
```
|
|
514
422
|
|
|
515
|
-
If the user gives only a task id:
|
|
423
|
+
If the user gives only a task id, resolve it to a task-key with the shared resolver (SSOT — `scripts/okstra_ctl/resolve_task_key.py` / `okstra_project.resolve_task_id`):
|
|
516
424
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
425
|
+
```bash
|
|
426
|
+
okstra resolve-task-key <task-id> --project-root <projectRoot> --json
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Branch on the stdout JSON `matches[]` — this **standard 0/1/N rule** is referenced by `errors`/`recap` below:
|
|
430
|
+
- **0개** → report the task cannot be found. Do not guess.
|
|
431
|
+
- **1개** → use that entry's `taskKey`.
|
|
432
|
+
- **N개** → list the candidate `taskKey`s (with `updatedAt`) and ask via a 3-option picker (1~2 추천 + `직접 입력`), then use the chosen `taskKey`.
|
|
522
433
|
|
|
523
434
|
If the user asks generally ("컨텍스트 비용 보여줘") and does not name a task:
|
|
524
435
|
|
|
@@ -536,24 +447,14 @@ okstra context-cost <resolved-target> --project-root <projectRoot>
|
|
|
536
447
|
|
|
537
448
|
Do not re-count files manually unless the CLI fails and the user explicitly asks for manual fallback.
|
|
538
449
|
|
|
539
|
-
### cost.3 —
|
|
450
|
+
### cost.3 — Render
|
|
540
451
|
|
|
541
|
-
|
|
452
|
+
Render the CLI JSON into the cost.4 template. Each row's Files / Size / ~Tokens come from the matching output block — `totals` (Task bundle, Current run, Legacy timestamp), `instructionSet`, `leadPhase1`, `analysisWorker`, `reportWriter`, `skillAssets`; their exact field names are in the CLI output (do not re-list them here). Format bytes as raw + rounded KB/MB.
|
|
542
453
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
| Legacy timestamp artifacts | `totals.legacyTimestampFileCount` |
|
|
548
|
-
| Instruction set | `instructionSet.fileCount`, `instructionSet.bytes`, `instructionSet.analysisPacketBytes`, `instructionSet.legacyTaskPacketBytes` |
|
|
549
|
-
| Lead Phase 1 | `leadPhase1.mode`, `leadPhase1.fileCount`, `leadPhase1.bytes`, `leadPhase1.estimatedTokens` |
|
|
550
|
-
| Analysis worker | `analysisWorker.mode`, `analysisWorker.fileCount`, `analysisWorker.bytesPerWorker`, `analysisWorker.estimatedTokensPerWorker`, `analysisWorker.legacyFullContractBytesPerWorker`, `analysisWorker.estimatedPacketModeBytesPerWorker`, `analysisWorker.estimatedReductionPercent` |
|
|
551
|
-
| Report writer | `reportWriter.fileCount`, `reportWriter.bytes`, `reportWriter.estimatedTokens` |
|
|
552
|
-
| Skill assets (hot path) | `skillAssets.fileCount`, `skillAssets.bytes`, `skillAssets.estimatedTokens`, top entries of `skillAssets.files[]` |
|
|
553
|
-
|
|
554
|
-
Format bytes as both raw bytes and rounded KB/MB where useful. Use `analysisWorker.estimatedReductionPercent` for the worker-input reduction. Do not recompute it from `bytesPerWorker` when `analysisWorker.mode == "analysis-packet-primary"` because `bytesPerWorker` is already the packet-primary cost.
|
|
555
|
-
|
|
556
|
-
`estimatedTokens*` fields are a static heuristic (~4 ASCII chars/token, non-ASCII ≈ 1 token/char) for ranking instruction surfaces — actual billable cost is the token-usage collector's domain; never present these as billing numbers. `skillAssets` measures the per-run hot-path instruction assets loaded OUTSIDE the task bundle (lifecycle skill bodies + worker agent specs, installed copy preferred) — the prompt-diet target list, sorted by size descending.
|
|
454
|
+
Field-specific notes (these affect interpretation, not just display):
|
|
455
|
+
- Analysis worker uses per-worker fields (`bytesPerWorker`, `estimatedTokensPerWorker`). Use `analysisWorker.estimatedReductionPercent` directly for the worker-input reduction — do **not** recompute it from `bytesPerWorker` when `mode == "analysis-packet-primary"` (that value is already the packet-primary cost).
|
|
456
|
+
- `estimatedTokens*` are a static ranking heuristic (~4 ASCII chars/token, non-ASCII ≈ 1 token/char), never billing numbers — actual cost is the token-usage collector's domain.
|
|
457
|
+
- `skillAssets` = the per-run hot-path instruction assets loaded OUTSIDE the task bundle (skill bodies + agent specs), sorted by size — the prompt-diet target.
|
|
557
458
|
|
|
558
459
|
### cost.4 — Output template
|
|
559
460
|
|
|
@@ -611,41 +512,26 @@ The log is truncated at each dispatch (`: > "$log_path"`) — only the latest ru
|
|
|
611
512
|
|
|
612
513
|
### logs.1 — Inventory
|
|
613
514
|
|
|
614
|
-
Construct the logs root by appending `/.okstra/tasks` to the literal `projectRoot` value parsed in Step 0; paste as a literal absolute path in place of `<LOGS_ROOT>` below (no shell variables, no `$(...)`):
|
|
615
|
-
|
|
616
515
|
```bash
|
|
617
|
-
|
|
618
|
-
-printf '%s\t%T@\t%p\n' 2>/dev/null \
|
|
619
|
-
| sort -k1,1nr
|
|
516
|
+
okstra log-report --project-root <projectRoot> --json
|
|
620
517
|
```
|
|
621
518
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
519
|
+
Scans `<projectRoot>/.okstra/tasks/**/runs/*/prompts/*.log` and parses task/phase/worker/seq from each path. Returns (sizes are **raw bytes**, mtimes **epoch seconds**):
|
|
520
|
+
- `topLargest[]` — `{path, sizeBytes, mtimeEpoch, taskKey, taskGroup, taskId, phase, worker, seq}`, size desc (widen with `--top <N>`)
|
|
521
|
+
- `perTask[]` — `{taskKey, fileCount, totalBytes, oldestEpoch, newestEpoch}`, total-size desc
|
|
522
|
+
- `totals` — `{fileCount, totalBytes, taskCount}`
|
|
625
523
|
|
|
626
|
-
|
|
627
|
-
find <LOGS_ROOT> -type f -path '*/runs/*/prompts/*.log' -exec stat -f '%z%t%m%t%N' {} + 2>/dev/null | sort -k1,1nr
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
If empty, report `No wrapper log files found under <projectRoot>` and exit.
|
|
524
|
+
If `totals.fileCount` is 0, report `No wrapper log files found under <projectRoot>` and stop.
|
|
631
525
|
|
|
632
526
|
### logs.2 — Summary tables
|
|
633
527
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
| # | Task | Phase | Worker | Seq | Size | Age | Path |
|
|
637
|
-
|---|------|-------|--------|-----|------|-----|------|
|
|
638
|
-
|
|
639
|
-
Parse fields from the path: task-group / task-id from the `tasks/<task-group>/<task-id>/` segment; phase from `runs/<phase>/`; worker from filename prefix before `-worker-prompt-` (the wrapper names files `<worker>-worker-prompt-<phase>-<seq>.log`); seq from filename suffix (last 3-digit segment). Format sizes as KB/MB. Format age as `Nd` (days) or `Nh` (hours) from `mtime` relative to "now".
|
|
640
|
-
|
|
641
|
-
**Table B — Per-task totals:**
|
|
528
|
+
Render from the CLI output (format bytes → KB/MB; epoch → `Nd`/`Nh` relative to now):
|
|
642
529
|
|
|
643
|
-
| Task
|
|
644
|
-
|----------|-------|-----------:|--------|--------|
|
|
530
|
+
**Table A — Top largest logs** (from `topLargest`): `| # | Task | Phase | Worker | Seq | Size | Age | Path |`.
|
|
645
531
|
|
|
646
|
-
|
|
532
|
+
**Table B — Per-task totals** (from `perTask`): `| Task Key | Files | Total Size | Oldest | Newest |`.
|
|
647
533
|
|
|
648
|
-
**Footer:** `Total:
|
|
534
|
+
**Footer:** `Total: <fileCount> files, <totalBytes→MB> across <taskCount> tasks under <PROJECT_ROOT>`.
|
|
649
535
|
|
|
650
536
|
### logs.3 — Suggested cleanup commands
|
|
651
537
|
|
|
@@ -707,7 +593,7 @@ Aggregate a task's okstra-run error logs (`runs/*/logs/errors-*.jsonl`, lead-obs
|
|
|
707
593
|
Accepted target forms (same as `cost`):
|
|
708
594
|
|
|
709
595
|
1. Full task-key: `<project-id>:<task-group>:<task-id>`.
|
|
710
|
-
2. Task id only —
|
|
596
|
+
2. Task id only — resolve via `okstra resolve-task-key <task-id> --project-root <projectRoot> --json` and branch on `matches[]` using the standard 0/1/N rule from `cost.1` (0 = not found, 1 = use, N = picker).
|
|
711
597
|
3. Task root path.
|
|
712
598
|
|
|
713
599
|
### errors.2 — Run the renderer
|
|
@@ -810,7 +696,7 @@ task-id 하나에 쌓인 `.okstra` 산출물 위에서 (a) 처리 전/후 요약
|
|
|
810
696
|
|
|
811
697
|
### recap.1 — Resolve target
|
|
812
698
|
|
|
813
|
-
`cost` / `errors` 와 동일한 3형식: ① full task-key, ② task-id 만(→
|
|
699
|
+
`cost` / `errors` 와 동일한 3형식: ① full task-key, ② task-id 만(→ `okstra resolve-task-key <task-id> --project-root <projectRoot> --json`; `cost.1` 의 표준 0/1/N 분기), ③ task-root path.
|
|
814
700
|
|
|
815
701
|
### recap.2 — Assemble the before/after summary
|
|
816
702
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-memory
|
|
3
|
-
description: Use when the user wants to preserve, remember, store, recall, search, or archive AI/human conversation notes in okstra's global Memory Book. Trigger words include "okstra 에 정리해서 보관해", "memory-book", "기억해둬", "대화 저장", "정리해서 저장", "remember this", "store this conversation", "save this decision".
|
|
3
|
+
description: Use when the user wants to preserve, remember, store, recall, search, or archive AI/human conversation notes in okstra's global Memory Book. Trigger words include "okstra 에 정리해서 보관해", "memory-book", "기억해둬", "대화 저장", "정리해서 저장", "remember this", "store this conversation", "save this decision", "memory-book 검색", "저장한 메모 찾아줘", "recall a saved decision".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# okstra-memory
|
|
@@ -58,15 +58,18 @@ searching so the rest of the skill can scope to it.
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
2. Present a 3-option picker (most-used existing group, next existing group,
|
|
61
|
-
then always `직접 입력`).
|
|
62
|
-
first option.
|
|
61
|
+
then always `직접 입력`). For a personal note, recommend `private` as the
|
|
62
|
+
first option. If `groups` is empty or the user makes no selection, the
|
|
63
|
+
default group is `global` (the CLI default in `memory.mjs`).
|
|
63
64
|
3. Carry the chosen group name into every `add` (`--project-group <name>`) and
|
|
64
65
|
into scoped `search`/`list` (`--project-group <name>`) below. Omit the flag
|
|
65
66
|
only when the user explicitly wants a cross-group search.
|
|
66
67
|
|
|
67
68
|
## Store current conversation
|
|
68
69
|
|
|
69
|
-
1. Extract only durable memory from the conversation
|
|
70
|
+
1. Extract only durable memory from the conversation and tag each with a
|
|
71
|
+
`--type`. The authoritative `--type` value list is the `okstra memory --help`
|
|
72
|
+
output read in Step 0; the categories below mirror it:
|
|
70
73
|
- decision
|
|
71
74
|
- preference
|
|
72
75
|
- requirement
|
|
@@ -89,8 +92,7 @@ okstra memory add --content "<summary markdown>" --title "<short title>" --type
|
|
|
89
92
|
```
|
|
90
93
|
|
|
91
94
|
Use repeated `--tag` / `--project` flags when needed. Omit `--project` when no
|
|
92
|
-
project is clearly related. `--project-group` is the group chosen in Step 1
|
|
93
|
-
(default `global` if the user truly wants it ungrouped).
|
|
95
|
+
project is clearly related. `--project-group` is the group chosen in Step 1.
|
|
94
96
|
|
|
95
97
|
## Search / read / archive
|
|
96
98
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-run
|
|
3
|
-
description: Use when the user wants to start an okstra task (cross-verification run) directly from the current Claude Code session — without spawning a new claude process. Equivalent in effect to `okstra.sh --task-type ...` but driven through interactive prompts. Trigger words include "okstra run", "okstra start", "start okstra", "begin okstra task", "run okstra in this session", "okstra here".
|
|
3
|
+
description: Use when the user wants to start an okstra task (cross-verification run) directly from the current Claude Code session — without spawning a new claude process. Equivalent in effect to `okstra.sh --task-type ...` but driven through interactive prompts. Trigger words include "okstra run", "okstra start", "start okstra", "begin okstra task", "run okstra in this session", "okstra here", "이어서 진행", "다음 단계 실행", "이 세션에서 okstra 시작".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# OKSTRA Run (in-session)
|
|
@@ -39,7 +39,7 @@ Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit i
|
|
|
39
39
|
"current": { "step": "approved_plan", "kind": "text", "label": "..." } }
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
On `ok: false`, re-prompt with the same `current.step` using the error message. The wizard never advances on validation failure; the user retries the same step.
|
|
42
|
+
On `ok: false`, re-prompt with the same `current.step` using the error message. The wizard never advances on validation failure; the user retries the same step. **`current` may be `null`** when the current step itself cannot render (e.g. the approved plan's Stage Map is corrupt) — that case is terminal: show `error` and stop, the user must fix the plan file before retrying. Never re-prompt off a `null` `current`.
|
|
43
43
|
|
|
44
44
|
The wizard tells you *which UI to use* via `kind` (and the optional `multi` flag on `pick`):
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ The wizard tells you *which UI to use* via `kind` (and the optional `multi` flag
|
|
|
52
52
|
|
|
53
53
|
The final `confirm` step is a normal `pick` step with three options — `Proceed` / `Edit` / `중단`(abort) — and is rendered the same way (no special handling). `Edit` rewinds to any earlier step (including `base-ref`); `중단` terminally cancels the wizard. The branch/worktree decision the run will actually use (for `implementation`, the **stage worktree** — not the task-key directory) is folded into the Step 4 confirmation summary block as a `worktree` line, so there is no separate branch-confirm prompt.
|
|
54
54
|
|
|
55
|
-
Never invent additional questions. Never reorder. **Never drop, hide, or merge a `pick` / `pick_group` option** — render every `options[]` entry as its own selectable `AskUserQuestion` choice, including entries that carry a `(default)` / `(recommended)` suffix. Do NOT collapse a multi-option pick into a "recommended + 직접 입력 / Other" shortlist: the wizard's `options[]` array IS the complete, authoritative choice set. Example:
|
|
55
|
+
Never invent additional questions. Never reorder. **Never drop, hide, or merge a `pick` / `pick_group` option** — render every `options[]` entry as its own selectable `AskUserQuestion` choice, including entries that carry a `(default)` / `(recommended)` suffix. Do NOT collapse a multi-option pick into a "recommended + 직접 입력 / Other" shortlist: the wizard's `options[]` array IS the complete, authoritative choice set. Example: if a pick's `options[]` carries N entries, render all N as selectable choices — never abbreviate a multi-option step down to one recommended value. The run-prompt recommendation rule (1–2 추천 + 직접 입력) applies ONLY to prompts this skill authors itself (e.g. the conformance-waiver picker), never to wizard-provided `options[]`. Never use `AskUserQuestion` for `text` prompts — the wizard explicitly chose `text` to avoid the picker-Other re-render lag.
|
|
56
56
|
|
|
57
57
|
## Step 1: Verify okstra runtime + project setup
|
|
58
58
|
|
|
@@ -116,7 +116,7 @@ Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How
|
|
|
116
116
|
**Escaping rule**: if the literal answer contains `"`, escape each occurrence as `\"` inside the double-quoted argument. Empty values must still be `--answer ""` — the flag itself is mandatory, even when the value is empty.
|
|
117
117
|
3. **Handle result**:
|
|
118
118
|
- `ok: true` → echo `result.echo` to the user on one short line, then loop with `result.next`.
|
|
119
|
-
- `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step).
|
|
119
|
+
- `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step). If `result.current` is `null`, do not loop — the current step cannot render (e.g. corrupt Stage Map); surface the error and stop so the user fixes the plan.
|
|
120
120
|
|
|
121
121
|
That is the entire interactive flow. The wizard handles:
|
|
122
122
|
|
|
@@ -52,7 +52,7 @@ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this ski
|
|
|
52
52
|
|
|
53
53
|
## Process Procedure
|
|
54
54
|
|
|
55
|
-
### Step 0: Verify model (
|
|
55
|
+
### Step 0: Verify model (권장 게이트)
|
|
56
56
|
|
|
57
57
|
This skill performs cross-task synthesis (multi-task classification, dependency reasoning, phase placement, Gantt/timeline assembly) which benefits substantially from Opus-class reasoning. The frontmatter `model: opus` field above instructs supporting Claude Code harness versions to switch automatically; if the harness ignores it, this gate catches the case explicitly.
|
|
58
58
|
|
|
@@ -64,7 +64,8 @@ This skill performs cross-task synthesis (multi-task classification, dependency
|
|
|
64
64
|
/model opus 로 전환 후 다시 호출하시거나, 'sonnet으로 진행' 이라고 명시하시면 그대로 실행합니다.
|
|
65
65
|
```
|
|
66
66
|
4. If the user explicitly insists on the lower model ("sonnet으로 진행", "그대로 진행", "force", or similar): proceed to Step 1, but prepend a single-line warning at the top of the generated schedule file: `> ⚠️ Generated with <model> (not Opus). Cross-task synthesis quality may be reduced.`
|
|
67
|
-
|
|
67
|
+
|
|
68
|
+
This is a recommendation gate, not a blocking mechanism — the override path in step 4 always lets the run proceed.
|
|
68
69
|
|
|
69
70
|
### Step 1: Resolve task-group and collect tasks
|
|
70
71
|
|
|
@@ -76,7 +77,7 @@ This skill performs cross-task synthesis (multi-task classification, dependency
|
|
|
76
77
|
|
|
77
78
|
### Step 2: Filter by workStatus
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
`workStatus` is also surfaced directly inside each catalog entry, but Step 1 still re-reads each `task-manifest.json` because the manifest is authoritative — no extra fetch is needed beyond that.
|
|
80
81
|
|
|
81
82
|
For inference rules when `workStatus` is missing or empty, **defer to the inference table in `skills/okstra-inspect/SKILL.md` (`status.4` → "Default value convention")** instead of duplicating it here. Apply that table to derive a working value, then filter:
|
|
82
83
|
|
|
@@ -131,7 +132,7 @@ Claude lead synthesises the schedule directly using collected data. Proceed to S
|
|
|
131
132
|
|
|
132
133
|
Classify each task into one of three phases based on report data.
|
|
133
134
|
|
|
134
|
-
**`workCategory` accepted values**
|
|
135
|
+
**`workCategory` accepted values** — the category keys come from `scripts/okstra_ctl/worktree.py::_WORK_CATEGORY_PREFIX` (code source), plus the `unknown` fallback emitted by `render.py`. The phase mapping below is layered on top: when a category is added to `worktree.py`, add a row here too — otherwise the new category falls through to `unknown` → Phase 2.
|
|
135
136
|
|
|
136
137
|
| workCategory | Default phase |
|
|
137
138
|
|--------------|---------------|
|
|
@@ -329,7 +330,7 @@ If a directive source is found and contains a `## Directive` section that affect
|
|
|
329
330
|
|
|
330
331
|
A directive file that exists but contains no `## Directive` heading is treated as "no directive" — fall through to the heuristic, no warning required.
|
|
331
332
|
|
|
332
|
-
`## Gantt Chart` is the only `##` section that MAY be added beyond the
|
|
333
|
+
`## Gantt Chart` is the only `##` section that MAY be added beyond the 10 mandatory ones. Any other extra `##` heading is forbidden.
|
|
333
334
|
|
|
334
335
|
#### MUST — emit a skip-reason note when omitting Gantt Chart (rare case)
|
|
335
336
|
|
|
@@ -416,7 +417,7 @@ Required shapes:
|
|
|
416
417
|
| Pattern | Why forbidden |
|
|
417
418
|
|---------|---------------|
|
|
418
419
|
| Translating any `##` heading to Korean (`전체 개요`, `요약`, `위험 완화 전략`, `즉시 권장 액션`, etc.) or any other language | Heading literals are stable identifiers used by the validator and downstream tooling |
|
|
419
|
-
| Adding sections not in the contract (`## Effort-to-Day 매핑`, `## Référentiel …`, etc. — anything beyond the
|
|
420
|
+
| Adding sections not in the contract (`## Effort-to-Day 매핑`, `## Référentiel …`, etc. — anything beyond the 10 mandatory + the 1 optional `## Gantt Chart`) | Sizing tables belong inside `### Effort Sizing 기준` (Executive Summary). All other graphic detail belongs inside per-phase prose |
|
|
420
421
|
| Translating the optional graphic section (`## Gantt 다이어그램`, `## Diagramme de Gantt`) | Use `## Gantt Chart` literally — translation is forbidden |
|
|
421
422
|
| Adding `## Cumulative Timeline` (or any translated form: `## 누적 일정표`, `## Calendrier Cumulé`) | The Cumulative Timeline section has been removed from the contract — never emit it |
|
|
422
423
|
| Using calendar dates / weekdays / "today + N" labels in the Gantt axis (e.g. `2026-05-04`, `Mon May 4`, `오늘 + 3일`) | The axis MUST be relative day-counts (`Day 1`, `J1`, …) — the schedule describes effort, not a wall-clock plan |
|
|
@@ -444,7 +445,7 @@ After writing the file and before printing the completion message in Step 7, you
|
|
|
444
445
|
python3 ~/.okstra/lib/validators/validate-schedule.py <output-path>
|
|
445
446
|
```
|
|
446
447
|
3. If the validator exits non-zero, **fix the file and re-validate**. Do not print the completion message until the validator passes.
|
|
447
|
-
4. If `~/.okstra/lib/validators/validate-schedule.py` is not present, fall back to a manual checklist: confirm each of the
|
|
448
|
+
4. If `~/.okstra/lib/validators/validate-schedule.py` is not present, fall back to a manual checklist: confirm each of the 10 mandatory headings from `### MUST — exact ordered heading list` (above) appears in the file with that exact spelling, the title ends with `— Work Schedule`, and the metadata `> Generated:` block is present.
|
|
448
449
|
|
|
449
450
|
## Schedule Document Template
|
|
450
451
|
|
|
@@ -454,15 +455,7 @@ The two rules below complement the template — they encode COMPUTATION that the
|
|
|
454
455
|
|
|
455
456
|
### Effort-to-Day mapping (At a Glance total)
|
|
456
457
|
|
|
457
|
-
|
|
458
|
-
|------|----------|
|
|
459
|
-
| S | 0.5 - 1 |
|
|
460
|
-
| M | 2 - 3 |
|
|
461
|
-
| L | 3 - 5 |
|
|
462
|
-
| XL | 5 - 10 |
|
|
463
|
-
| XXL | 10 - |
|
|
464
|
-
|
|
465
|
-
Sum the lower bounds across all in-scope tasks for the lower total, and the upper bounds for the upper total. The same table is rendered as descriptive reference under `### Effort Sizing 기준`; this copy is the source-of-truth for the At a Glance computation.
|
|
458
|
+
Day-range values per size are defined once in the template's `### Effort Sizing 기준` table (`~/.okstra/templates/reports/schedule.template.md`) — that table is the SSOT. To compute the At a Glance total, read its `Day(s)` column and sum the lower bounds across all in-scope tasks for the lower total, the upper bounds for the upper total.
|
|
466
459
|
|
|
467
460
|
### `[NEEDS-OKSTRA-RUN]` / `[PARSE-ERROR: <section>]` placement
|
|
468
461
|
|
|
@@ -74,6 +74,9 @@ Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, pro
|
|
|
74
74
|
okstra check-project --cwd /abs/path/from/user --json
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
- `ok: false`, `stage: "project_json_missing"` → proceed to Step 4 (this is the normal create path).
|
|
78
|
+
- `ok: false`, any other `stage` (`python`, `parse`, `project_json_invalid`) → show the JSON `reason` field to the user verbatim and follow the recovery it names (typically `okstra doctor` to diagnose, then `okstra ensure-installed` or re-running the Step 1 install). `reason` is the SSOT — do not hand-enumerate stage causes.
|
|
79
|
+
|
|
77
80
|
## Step 4: Inspect or create `project.json`
|
|
78
81
|
|
|
79
82
|
Use the `Read` tool on the literal absolute path `<projectRoot>/.okstra/project.json` (substitute the literal `projectRoot` value parsed in Step 3). If `Read` errors with "file does not exist", treat that as the "create" branch below; otherwise the file exists and you can inspect its contents inline.
|
|
@@ -163,14 +166,12 @@ commands whose language is not represented in this run's diff.
|
|
|
163
166
|
|
|
164
167
|
**`cmd` deny-list (mutation guard):** the verifier rejects any `cmd`
|
|
165
168
|
containing tokens that imply mutation — declare commands in their
|
|
166
|
-
check-only form only.
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- `npm install` (use `npm ci`), `cargo update`, `pip install -U`,
|
|
173
|
-
- `pnpm add`, `bun add`, `cargo add`.
|
|
169
|
+
check-only form only. Representative denied tokens: `--fix` / `--write`
|
|
170
|
+
(formatters), `--updateSnapshot`, `npm install` (use `npm ci`),
|
|
171
|
+
`pip install -U`, `cargo add`. The authoritative deny list is
|
|
172
|
+
`scripts/okstra_ctl/qa_commands.py::find_denied_tokens`
|
|
173
|
+
(`_DENIED_LITERAL_TOKENS` / `_DENIED_SUBSTRINGS` plus the dynamic
|
|
174
|
+
`npm install` and `INSTA_UPDATE=` checks) — do not re-enumerate it here.
|
|
174
175
|
|
|
175
176
|
Encountering a denied token aborts the verifier with status
|
|
176
177
|
`contract-violated`; re-declare the command in check-only form to
|
|
@@ -338,6 +339,6 @@ Inform the user with a short summary:
|
|
|
338
339
|
| `okstra ensure-installed` keeps reinstalling | `~/.okstra/version` write fails (permissions) | Check `~/.okstra` ownership and writability. |
|
|
339
340
|
| `error: --project-id is required (no existing project.json, not a TTY)` | `okstra setup --yes` invoked without `--project-id`, or with empty answer to Step 4 prompt | Re-ask Step 4 and pass a non-empty id via `--project-id`. |
|
|
340
341
|
| `projectId mismatch` / `projectId 불일치` | `project.json` already exists with a different id | Decide which id is canonical; manually delete `<PROJECT_ROOT>/.okstra/project.json` to re-register, or re-run with the existing id. |
|
|
341
|
-
| `EACCES` writing under `.okstra/` | directory owned by another user (e.g. created by a previous root-shell run) | `chown -R "$USER" <PROJECT_ROOT>/.
|
|
342
|
+
| `EACCES` writing under `.okstra/` | directory owned by another user (e.g. created by a previous root-shell run) | `chown -R "$USER" <PROJECT_ROOT>/.okstra` or delete and let setup recreate. |
|
|
342
343
|
| `warning: failed to provision .claude/settings.local.json symlink` | a non-symlink `.claude/settings.local.json` already exists and the backup-and-replace step failed | Inspect `<PROJECT_ROOT>/.claude/settings.local.json{,.bak.*}`; manually merge project-specific rules, then re-run setup. |
|
|
343
344
|
| `npx okstra@latest install` succeeds but `doctor` shows FAIL | runtime/{python,bin,skills} sync not yet performed (pre-release package) | Use dev install: clone the repo and run `node bin/okstra install --link <repo>`. |
|
|
@@ -23,7 +23,7 @@ candidate-cap: 8 # codebase-scan only: 1..12, de
|
|
|
23
23
|
> Source type: <file | linear | jira | github | notion | url | user-input>
|
|
24
24
|
> Tracker key (if any): <LIN-1234 | PROJ-42 | gh-repo-123 | notion-abcdef12>
|
|
25
25
|
> Parent brief (child briefs only): <relative path>
|
|
26
|
-
> Recommended next phase: <requirements-discovery | error-analysis> ← from Step 6
|
|
26
|
+
> Recommended next phase: <requirements-discovery | error-analysis | improvement-discovery> ← from Step 6
|
|
27
27
|
> Handoff contract: see `prompts/profiles/_common-contract.md` § "Brief handoff contract"
|
|
28
28
|
|
|
29
29
|
## Source Material
|
|
@@ -573,7 +573,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
|
|
|
573
573
|
{% if header.taskType == 'improvement-discovery' %}
|
|
574
574
|
## 5.9 Improvement Candidates
|
|
575
575
|
|
|
576
|
-
> Lens whitelist and column schema enforced by `validators/
|
|
576
|
+
> Lens whitelist and column schema enforced by `validators/validate_improvement_report.py`. Row count is bounded by the brief's `candidate-cap` (default 8, absolute max 12).
|
|
577
577
|
|
|
578
578
|
| Cand ID | Lens | Title | Scope | Severity | Effort | Consensus | Source workers | Recommended next-phase | Evidence |
|
|
579
579
|
|---------|------|-------|-------|----------|--------|-----------|----------------|------------------------|----------|
|