okstra 0.26.0 → 0.27.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.
Files changed (43) hide show
  1. package/README.kr.md +15 -0
  2. package/README.md +15 -0
  3. package/docs/kr/architecture.md +2 -6
  4. package/docs/kr/cli.md +40 -6
  5. package/docs/kr/performance-improvement-plan-v2.md +23 -0
  6. package/docs/kr/performance-improvement-plan.md +22 -0
  7. package/package.json +1 -1
  8. package/runtime/BUILD.json +2 -2
  9. package/runtime/bin/okstra.sh +0 -1
  10. package/runtime/prompts/profiles/_common-contract.md +25 -1
  11. package/runtime/prompts/profiles/error-analysis.md +12 -0
  12. package/runtime/prompts/profiles/implementation-planning.md +20 -0
  13. package/runtime/prompts/profiles/requirements-discovery.md +20 -0
  14. package/runtime/python/lib/okstra/cli.sh +1 -7
  15. package/runtime/python/lib/okstra/globals.sh +0 -1
  16. package/runtime/python/lib/okstra/usage.sh +1 -4
  17. package/runtime/python/okstra_ctl/render.py +3 -0
  18. package/runtime/python/okstra_ctl/run.py +0 -6
  19. package/runtime/python/okstra_ctl/run_context.py +1 -1
  20. package/runtime/python/okstra_ctl/wizard.py +25 -2
  21. package/runtime/python/okstra_token_usage/blocks.py +5 -1
  22. package/runtime/python/okstra_token_usage/claude.py +16 -1
  23. package/runtime/python/okstra_token_usage/collect.py +17 -3
  24. package/runtime/python/okstra_token_usage/pricing.py +159 -24
  25. package/runtime/skills/okstra-brief/SKILL.md +532 -65
  26. package/runtime/skills/okstra-context-loader/SKILL.md +25 -11
  27. package/runtime/skills/okstra-convergence/SKILL.md +37 -13
  28. package/runtime/skills/okstra-history/SKILL.md +68 -37
  29. package/runtime/skills/okstra-logs/SKILL.md +26 -4
  30. package/runtime/skills/okstra-report-finder/SKILL.md +49 -22
  31. package/runtime/skills/okstra-report-writer/SKILL.md +59 -64
  32. package/runtime/skills/okstra-run/SKILL.md +35 -34
  33. package/runtime/skills/okstra-schedule/SKILL.md +51 -20
  34. package/runtime/skills/okstra-setup/SKILL.md +31 -12
  35. package/runtime/skills/okstra-status/SKILL.md +20 -8
  36. package/runtime/skills/okstra-team-contract/SKILL.md +27 -15
  37. package/runtime/skills/okstra-time-summary/SKILL.md +53 -16
  38. package/runtime/templates/reports/settings.template.json +7 -4
  39. package/runtime/validators/lib/fixtures.sh +10 -2
  40. package/runtime/validators/lib/validate-assets.sh +50 -24
  41. package/runtime/validators/validate-brief.py +385 -0
  42. package/runtime/validators/validate-brief.sh +35 -0
  43. package/runtime/validators/validate-workflow.sh +7 -33
@@ -12,7 +12,9 @@ user-invocable: false
12
12
  - When the user needs to know the okstra task bundle path
13
13
  - When you need to derive all artifact paths based on `task-manifest.json`
14
14
 
15
- ## Step 1: Locating the Task Bundle
15
+ ## Step 1: Resolve the Task Bundle Path
16
+
17
+ (Resolve which task-root path to use; Step 2 opens `task-manifest.json` at that path.)
16
18
 
17
19
  ### Default Location Rules
18
20
 
@@ -28,11 +30,12 @@ user-invocable: false
28
30
  3. If the user attempts to find a task based on `task-group` + `task-id` or `task-id`, `.project-docs/okstra/discovery/task-catalog.json` is read to find candidates.
29
31
  4. If multiple candidates are found based on `task-id` alone, the situation is ambiguous, so `task-group` or the full `taskKey` is required.
30
32
  5. If the user has not provided an explicit task key/path, first read `.project-docs/okstra/discovery/latest-task.json` using the current-task convenience pointer.
31
- 6. Even if the latest-task pointer is missing or corrupted, but the task catalog exists, first check the list of prepared task bundles based on the catalog. Do not use the legacy `CLAUDE.md`, project guide, or task scan fallback.
33
+ 6. If the latest-task pointer is missing or corrupted but the task catalog exists, list candidates from the catalog. Do not use the legacy `CLAUDE.md`, project guide, or task scan fallback.
34
+ 7. If **neither** `latest-task.json` **nor** `task-catalog.json` exists, ABORT Phase 1 with `OKSTRA_CONTEXT_NOT_INITIALIZED`. Suggest the user run `/okstra-setup` and `/okstra-brief` to bootstrap the project. Do NOT crawl `.project-docs/okstra/tasks/` directly — discovery pointers are the only supported entry path.
32
35
 
33
- ## Step 2: Read task-manifest.json
36
+ ## Step 2: Open and Parse task-manifest.json
34
37
 
35
- task-manifest.json is the canonical metadata source. The following fields must be extracted from this file:
38
+ `task-manifest.json` (found at the task-root resolved in Step 1) is the canonical metadata source. Extract the following fields:
36
39
 
37
40
  | Field | Description |
38
41
  |------|------|
@@ -53,7 +56,7 @@ task-manifest.json is the canonical metadata source. The following fields must b
53
56
  | `workflow.awaitingApproval` | Approval wait marker |
54
57
  | `workflow.routingStatus` | Routing decision status |
55
58
  | `workflow.lastSafeCheckpoint` | Safe resume checkpoint metadata |
56
- | `instructionSetPath` | Instruction-set path |
59
+ | `instructionSetPath` | Path to the `instruction-set/` **directory** containing `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, `final-report-template.md` (see Step 4). Not a single-file path. |
57
60
  | `referenceExpectationsPath` | config/deployment expectation artifact path |
58
61
  | `latestRunPath` | latest run path |
59
62
  | `latestRunStatus` | latest run status |
@@ -63,7 +66,7 @@ task-manifest.json is the canonical metadata source. The following fields must b
63
66
  | `historyTimelinePath` | timeline path |
64
67
  | `resultContract` | team contract and expected artifact metadata |
65
68
  | `resultContract.requiredWorkerRoles[*].promptPath` | worker prompt history path by role |
66
- | `convergence` | convergence loop settings (enabled, maxRounds, verificationMode). `maxRounds` 기본값은 phase-aware: `requirements-discovery`는 `1`, 그 외는 `2`. 자세한 내용은 [okstra-convergence](../okstra-convergence/SKILL.md) 참고 |
69
+ | `convergence` | convergence loop settings (`enabled`, `maxRounds`, `verificationMode`). See [okstra-convergence](../okstra-convergence/SKILL.md) for the authoritative defaults — do not re-document the `maxRounds` value here. |
67
70
 
68
71
  ## Step 3: Directory Structure Rules
69
72
 
@@ -121,12 +124,21 @@ After verifying `task-manifest.json`, read the instruction set in the following
121
124
  4. `instruction-set/task-brief.md` (task brief)
122
125
  5. `instruction-set/final-report-template.md` (report template)
123
126
 
127
+ ### Brief Reporter-Confirmation Precondition (BLOCKING)
128
+
129
+ After reading `task-brief.md`, extract the frontmatter `reporter-confirmations` field (`complete | partial | pending | skipped`). This precondition is shared across every consuming phase — see `prompts/profiles/_common-contract.md` "Brief consumption" block for the authoritative handling matrix.
130
+
131
+ - `complete` or `partial` → proceed to Step 5 and hand off to `okstra-team-contract`.
132
+ - `skipped` → proceed, but flag the unmarked `intent-check:` / `conversion-block:` rows for promotion by the phase profile.
133
+ - `pending` (or field missing) → emit `REPORTER_CONFIRMATION_PENDING` and STOP. Do not invoke `okstra-team-contract` or any analyser. The operator must rerun `okstra-brief` Step 6.5 before Phase 2 can start.
134
+
124
135
  ## Step 5: Read Run Manifest and Team State
125
136
 
126
- 1. Current run manifest: The latest run manifest pointed to by the discovery pointer or task-manifest
127
- 2. Current team state: The latest team-state pointed to by the discovery pointer or task-manifest
128
- 3. Extract the worker prompt directory path and per-worker prompt history paths from the current run manifest and team-state
129
- 4. If an existing run report is available, use it solely as historical context.
137
+ 1. Identify the active run by reading `runDateTimeSegment` from the latest `runs/<task-type>/manifests/run-manifest-*.json` (mtime order). That segment is the shared run identifier across all category subdirectories (`state/`, `prompts/`, `reports/`, `status/`, `sessions/`, `worker-results/`).
138
+ 2. Resolve sibling artifacts for this run by matching the same `runDateTimeSegment`. Do NOT re-scan `<seq>` counters per category they may diverge if an earlier run only wrote some categories.
139
+ 3. Current team state: the team-state file whose `runDateTimeSegment` matches the active run manifest.
140
+ 4. Extract the worker prompt directory path and per-worker prompt history paths from the current run manifest and team-state.
141
+ 5. If an existing run report is available, use it solely as historical context.
130
142
 
131
143
  ## Output
132
144
 
@@ -137,4 +149,6 @@ Information to be obtained after executing this skill:
137
149
  - Reference list of config files/deployment manifests and task-level expected values
138
150
  - Current run status and presence of existing worker results
139
151
  - Current run prompt history contract for attempted workers
140
- - Resume command path: `runs/<task-type>/sessions/claude-resume-<task-type>-<seq>.sh`
152
+ - Candidate `teamName` for Phase 3 hand-off: `okstra-<task-key>` (with task-key slugified per Step 1's slug rule)
153
+ - Current Claude `lead.sessionId` (the in-flight Claude Code session) — required by `okstra-team-contract` when registering the lead in `team-state.json`
154
+ - Resume command path: from `task-manifest.json` → `latestResumeCommandPath` (fallback: latest `runs/<task-type>/sessions/claude-resume-*.sh` by mtime). Never reconstruct the filename — the `<seq>` counter is category-local and may diverge from `manifests/`.
@@ -6,6 +6,23 @@ user-invocable: false
6
6
 
7
7
  # OKSTRA Convergence
8
8
 
9
+ ## Index
10
+
11
+ - [Scope and Terminology (BLOCKING)](#scope-and-terminology-blocking)
12
+ - [When to Use](#when-to-use)
13
+ - [Configuration](#configuration)
14
+ - [Finding Category](#finding-category)
15
+ - [Convergence Algorithm](#convergence-algorithm)
16
+ - [Round 0: Parse worker results](#round-0-parse-worker-results)
17
+ - [Round 1-N: Re-verification Loop (queue-pruned)](#round-1-n-re-verification-loop-queue-pruned)
18
+ - [Convergence Test](#convergence-test)
19
+ - [Verification Mode](#verification-mode)
20
+ - [Re-verification Agent Dispatch](#re-verification-agent-dispatch)
21
+ - [Convergence State Artifact](#convergence-state-artifact)
22
+ - [Output](#output)
23
+ - [Convergence Disabled](#convergence-disabled)
24
+ - [Plan-body verification mode (implementation-planning only)](#plan-body-verification-mode-implementation-planning-only)
25
+
9
26
  ## Scope and Terminology (BLOCKING)
10
27
 
11
28
  This skill governs **Phase 5.5 (Convergence loop)** — a *lead operating phase* inside a single okstra run, not a task-type lifecycle phase. The 6 task-type lifecycle phases (`requirements-discovery` → `error-analysis` → `implementation-planning` → `implementation` → `final-verification` → `release-handoff`, see [okstra/SKILL.md](../../SKILL.md) "Lifecycle Phase Boundaries") are unchanged by this skill. The lead operating phases (Phase 1 Intake → Phase 7 Persist, see [okstra/SKILL.md](../../SKILL.md) "Quick Reference") describe how the lead drives a *single* task-type run.
@@ -30,6 +47,8 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
30
47
  | `maxRounds` | phase-aware: `1` for `requirements-discovery`, `2` otherwise (range 1–3) | Maximum number of re-verification rounds. Discovery's routing/missing-input outputs gain little from a second round; other phases (especially `error-analysis`) keep `2`. Lead resolves the effective value when the manifest omits the key and records it in `config.maxRounds` of the convergence state artifact. |
31
48
  | `verificationMode` | `"lightweight"` | `"lightweight"` or `"full-reanalysis"` |
32
49
 
50
+ **Auto-disable rule (BLOCKING).** Convergence requires ≥2 analyser workers to produce a meaningful consensus tally. When the active profile's `Required workers:` block (see `prompts/profiles/*.md`) resolves to fewer than 2 analyser workers — e.g. `release-handoff` (zero analyser workers, lead-only) — the lead MUST treat `convergence.enabled` as `false` for that run regardless of manifest configuration, skip Phases 5.5 and the plan-body verification round, and record `finalState: "converged"` with `totalRounds: 0` and an explanatory note in `config` (e.g. `"autoDisabled": "fewer-than-two-analysers"`). The plan-body round inherits the same rule via its `gating=false` advisory path.
51
+
33
52
  ## Finding Category
34
53
 
35
54
  | Category | Definition | Included in Report |
@@ -37,10 +56,12 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
37
56
  | `full-consensus` | All participating workers agree | Required |
38
57
  | `partial-consensus` | Majority of workers agree; dissenting opinions are recorded | Required |
39
58
  | `contested` | Final classification only. Assigned to a finding that remains in the verification queue after the **last executed round** completes (round index = `effectiveMaxRounds`). Each worker's position across all executed rounds is recorded. NEVER used as an intermediate label. | Required |
40
- | `worker-unique` | Only the discoverer confirms; others oppose or remain unverified | Required |
59
+ | `worker-unique` | Only the discoverer confirms and ALL other non-error votes are `DISAGREE`. `verification-error` votes are excluded from the tally per §"Worker failure handling in reverify"; a finding where every non-discoverer vote is `verification-error` is carried forward, never classified `worker-unique`. | Required |
41
60
 
42
61
  ## Convergence Algorithm
43
62
 
63
+ **Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the non-error votes for that finding (`verification-error` votes are excluded from both numerator and denominator). Ties — including the 1-AGREE / 1-DISAGREE case in a two-analyser roster — are NOT a majority: in intermediate rounds the finding is **carried forward**; in the final executed round the finding is classified `contested`. This rule applies identically to the plan-body verification round (§"Plan-body verification mode") where the same verdict tokens are reused.
64
+
44
65
  ### Round 0: Parse worker results
45
66
 
46
67
  Read the worker result files generated in Phase 4/5 and extract individual findings.
@@ -132,7 +153,8 @@ The lead MUST construct the per-worker reverify prompt body from `items_for_W` o
132
153
  |---|---|---|
133
154
  | `effectiveMaxRounds >= 2` | true | `"max-rounds-1"` |
134
155
  | `len(queue) > 0` after round 1 | true | `"queue-empty"` |
135
- | At least one round-1 reverify dispatch terminated as `completed` | true | `"all-reverify-non-result"` |
156
+
157
+ The third gate condition — "all reverify dispatches terminated as non-result" — is handled inline by the WHILE-loop body (see the `BREAK` on `all dispatches ... terminal non-result` and §"Worker failure handling in reverify" rule 4) which records `round2SkippedReason = "all-reverify-non-result"` and aborts before the predicate is re-evaluated. It is therefore not duplicated as a gate row here.
136
158
 
137
159
  When all conditions hold the predicate returns `true` and `round2SkippedReason` is set to `"not-skipped"`. The field is mandatory on every convergence state artifact — write `"not-skipped"` rather than omitting the key.
138
160
 
@@ -152,11 +174,7 @@ The final classifier (`FOR each finding F still in queue` block) treats `verific
152
174
 
153
175
  ### Convergence Test
154
176
 
155
- - If the verification queue is empty at the end of any round Convergence complete (`finalState: "converged"`), remaining rounds are not executed
156
- - Upon completing the **last executed round** (where round index == `effectiveMaxRounds`, OR where Round 2 was suppressed per the Round 2 gate below) → Apply final classification to remaining queue items:
157
- - Majority agreement across executed rounds → `partial-consensus`
158
- - Otherwise → `contested`
159
- - The final classification step never runs while the queue is still being re-verified — confirmed items always exit the queue first.
177
+ The exit conditions and final-classification rules are defined by the §"Convergence Algorithm" pseudocode (the `WHILE` exit, the post-loop `FOR each finding F still in queue` block, and the `finalState` mapping in §"Convergence State Artifact"). This is the single source — no separate prose copy is maintained here to prevent drift.
160
178
 
161
179
  ## Verification Mode
162
180
 
@@ -383,9 +401,11 @@ Schema rules:
383
401
  - `schemaVersion`: literal string `"1.1"` for new runs. Readers MUST accept `"1.0"` for historical artifacts and treat any missing v1.1 field as `null`.
384
402
  - `config.effectiveMaxRounds`: the integer the lead actually used after resolving the phase-aware default (`1` for `requirements-discovery`, `2` otherwise). MUST equal `config.maxRounds` when the manifest explicitly set it.
385
403
  - `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). MAY be empty when the discovering worker tagged the finding `unknown`.
404
+ - `findings[].rounds[].votes.<worker>.verdict`: enum, one of `agree | disagree | supplement | verification-error`. Lower-case tokens; map upper-case AGREE/DISAGREE/SUPPLEMENT verdicts emitted by workers to their lower-case form before persisting. `verification-error` is reserved for terminal non-result dispatches (§"Worker failure handling in reverify").
405
+ - `findings[].classification`: enum, one of `full-consensus | partial-consensus | worker-unique | contested`. No other value is permitted in v1.1.
386
406
  - `roundHistory[].inputQueueSize`: queue size at the start of this round.
387
407
  - `roundHistory[].resolvedCount`: number of findings that exited the queue this round (sum of full+partial+worker-unique classifications produced this round).
388
- - `roundHistory[].carriedForwardCount`: queue size at the END of this round (must equal `inputQueueSize - resolvedCount` when there are no in-round queue insertions; in-round insertions are forbidden).
408
+ - `roundHistory[].carriedForwardCount`: queue size at the END of this round the single definition. In-round insertions into the queue are forbidden, so this always equals `inputQueueSize - resolvedCount`. The pseudocode's per-item `carriedForwardCount += 1` accumulator is a counting convenience that lands on the same value; persist the post-round queue length, not the loop accumulator, if the two ever diverge.
389
409
  - `roundHistory[].dispatches[]`: one entry per worker that was actually dispatched in this round. Each entry is `{worker, status, durationMs}`. `status ∈ {completed, timeout, error, not-run}`. `durationMs` is integer milliseconds and is always present, even for terminal-non-result dispatches (use the elapsed time before the wrapper gave up).
390
410
  - `roundHistory[].skippedWorkers[]`: per-worker `{worker, reason}` for workers with no items to verify OR with a non-result dispatch.
391
411
  - `roundHistory[].verificationsRequested|verificationsCompleted|newConsensus|remainingInQueue|earlyExit`: legacy v1.0 aliases. New runs SHOULD populate them so existing parsers keep working: `verificationsRequested == len(dispatches)`, `verificationsCompleted == len(d for d in dispatches if d.status == "completed")`, `newConsensus == resolvedCount`, `remainingInQueue == carriedForwardCount`, `earlyExit == (round < effectiveMaxRounds AND carriedForwardCount == 0)`.
@@ -433,7 +453,7 @@ The finding queue (Phase 5.5) and the plan-item queue (this section) are **disjo
433
453
 
434
454
  - A finding-convergence reverify prompt MUST NOT contain any `P-*` item.
435
455
  - A plan-body verification prompt MUST NOT contain any `F-*` finding.
436
- - The two rounds write to **different state files**: `runs/<task-type>/state/convergence-state.json` (findings) vs. `runs/<task-type>/state/plan-body-verification.json` (plan items).
456
+ - The two rounds write to **different state files**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (findings, see §"Convergence State Artifact") vs. `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (plan items, see §"`plan-body-verification.json` schema").
437
457
  - Aggregation logic (verdict counting, classification) MUST NOT carry votes from one queue into the other.
438
458
 
439
459
  Mixing the two queues — for example, parsing a Phase 6 draft's Stepwise Execution Order step as if it were an `F-*` finding — is a contract violation. Future Claude reading this skill: if you find yourself tempted to "just reuse the finding queue for plan items, they're similar enough", stop. They are not similar enough; the verdict semantics differ (see §"Plan-body verdict semantics" below).
@@ -493,15 +513,15 @@ Plan-body verification only supports **lightweight mode** (defined in §"Verific
493
513
  4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
494
514
  - `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
495
515
  - `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
496
- - `worker-unique` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent.
516
+ - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
497
517
  - `majority-disagree` — majority of analysers `DISAGREE` on this item. This is the only classification that **blocks the Approval marker**.
498
518
  - `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
499
519
  5. Gate result resolution:
500
520
  - any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
501
521
  - all dispatches non-result → `aborted-non-result`
502
- - any `partial-consensus` / `worker-unique` present, no `majority-disagree` → `passed-with-dissent`
522
+ - any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
503
523
  - all items `full-consensus` → `passed`
504
- 6. Lead writes `runs/<task-type>/state/plan-body-verification.json` (schema below) and populates `### 4.5.9 Plan Body Verification` in the final report (template at `templates/reports/final-report.template.md`).
524
+ 6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 4.5.9 Plan Body Verification` in the final report (template at `templates/reports/final-report.template.md`).
505
525
  7. For every `majority-disagree` item, lead adds a row to `## 5. Clarification Items` with:
506
526
  - new `C-<N>` ID (numbering continues from any existing rows)
507
527
  - `Statement` summarising the disagreement and the worker breakage `<kind>`
@@ -510,7 +530,7 @@ Plan-body verification only supports **lightweight mode** (defined in §"Verific
510
530
  - the §4.5.9 verdict table's `Classification` column for that row reads `majority-disagree → C-<N>` (1:1 ID match — orphan on either side is a contract violation per `prompts/profiles/implementation-planning.md` self-review step 6).
511
531
  8. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
512
532
 
513
- ### `plan-body-verification.json` schema
533
+ ### `plan-body-verification-<task-type>-<seq>.json` schema
514
534
 
515
535
  ```json
516
536
  {
@@ -547,6 +567,10 @@ Plan-body verification only supports **lightweight mode** (defined in §"Verific
547
567
 
548
568
  `dispatches[].terminalStatus` mirrors finding convergence (`completed | timeout | error | not-run | cli-failure`).
549
569
 
570
+ `planItems[].classification` enum: `full-consensus | partial-consensus | dissent-isolated | majority-disagree | contested`. `contested` only appears when `maxRounds > 1`; at default `maxRounds=1` any otherwise-unresolved item folds into `partial-consensus` per the round protocol above.
571
+
572
+ `planItems[].votes.<worker>` is the verbatim verdict token emitted by the worker — `AGREE | DISAGREE(<a|b|c|d|e>) | SUPPLEMENT` — or `verification-error` for terminal non-result dispatches. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
573
+
550
574
  ### Plan-body reverify prompt
551
575
 
552
576
  Required prompt anchor headers are identical to finding convergence (see §"Required reverify-prompt anchor headers"). The prompt body changes from F-* listing to P-* listing:
@@ -7,9 +7,22 @@ description: Use when the user asks to list past okstra runs, check execution hi
7
7
 
8
8
  ## When to Use
9
9
 
10
- - When a user views the history of past okstra executions
11
- - When re-running or resuming a previous execution
12
- - When checking the execution status of each task
10
+ - List past okstra task executions (one row per task, with latest-run summary).
11
+ - Drill into the per-run history of a specific task.
12
+ - Build a new run from an old run's parameters (re-run), or continue an in-flight one (resume).
13
+
14
+ This skill is for **listing / re-dispatching**. For reading a single final report by task-key, use `okstra-report-finder` instead.
15
+
16
+ ## Re-run vs Resume — decide upfront
17
+
18
+ Before invoking Step 3 or Step 4, classify the user's intent. The two paths are NOT interchangeable.
19
+
20
+ | Intent | Trigger phrases | Use |
21
+ |---|---|---|
22
+ | **Re-run** — start a fresh run (new run-seq, new manifest, new report) reusing an old run's parameters | "re-run", "다시 실행", "another pass", "rerun with same brief" | Step 3 |
23
+ | **Resume** — continue an interrupted Claude session for an existing run, no new run-seq | "resume", "continue", "이어서", "session ended" | Step 4 |
24
+
25
+ If the user is ambiguous, ask. Defaulting to the wrong one either wastes a fresh run-seq or silently abandons a recoverable session.
13
26
 
14
27
  ## Step 0: Verify okstra runtime + project setup
15
28
 
@@ -38,31 +51,45 @@ use `projectRoot` to locate `.project-docs/okstra/discovery/task-catalog.json`.
38
51
  ## Step 1: Read the Task Catalog
39
52
 
40
53
  1. Read `.project-docs/okstra/discovery/task-catalog.json`.
41
- 2. Sort the `tasks` array in reverse order by `updatedAt` and display it.
42
- 3. Extract the following fields from each task:
54
+ 2. Apply filters from user input (all optional, AND-combined):
55
+ - `--task-type <type>` keep entries whose `taskType` matches.
56
+ - `--latest-run-status <status>` → keep entries whose `latestRunStatus` matches (e.g. `completed`, `contract-violated`, `error`).
57
+ - `--task-group <group>` → keep entries whose `taskGroup` matches.
58
+ 3. Sort the surviving `tasks` array by `updatedAt` descending.
59
+ 4. Page: default `--limit 20`. After printing the table, if rows were truncated, add `... <N> more (pass --limit <N> to see all)`.
60
+ 5. Extract the following fields from each task:
43
61
 
44
62
  | Field | Description |
45
63
  |------|------|
46
- | `taskKey` | Task identifier (`<project-id>:<task-group>:<task-id>`) |
47
- | `taskType` | Analysis type |
48
- | `currentStatus` | Task-level status |
49
- | `latestRunStatus` | Latest run status |
50
- | `updatedAt` | Last update time |
64
+ | `taskKey` | Task identifier — always 3 colon-separated segments: `<project-id>:<task-group>:<task-id>` (see `parse_task_key` in `okstra_project/state.py`). |
65
+ | `taskType` | `requirements-discovery` / `error-analysis` / `implementation-planning` / `implementation` / `final-verification` / `release-handoff` |
66
+ | `currentStatus` | Task lifecycle status written by the contract validator. Values: `todo` (seeded by spawn-followups), `completed`, `contract-violated`. Empty string = validator has not yet run. NOT the same as the user-managed `workStatus` (managed by `okstra-status`). |
67
+ | `latestRunStatus` | Status of the most recent run (`completed`, `contract-violated`, `error`, ...) |
68
+ | `latestRunManifestPath` | Run-manifest path of the most recent run — feed this into Step 3 to re-run from the latest parameters |
69
+ | `updatedAt` | Last update time (ISO 8601) |
51
70
  | `latestReportPath` | Latest report path |
52
- | `latestResumeCommandPath` | Resume command path |
71
+ | `latestResumeCommandPath` | Resume command path (Step 4) |
72
+ | `historyTimelinePath` | `<task-root>/history/timeline.json` (Step 2 reads from here) |
53
73
 
54
- 4. Output format:
74
+ 6. Output format:
55
75
 
56
76
  ```markdown
57
77
  ## okstra Task History — <project-id>
58
78
 
59
- | # | Task Key | Type | Status | Last Run | Report |
60
- |---|----------|------|--------|----------|--------|
61
- | 1 | proj:group:id | error-analysis | completed | 2026-04-05 22:59 | .project-docs/.../final-report-*.md |
62
- | 2 | proj:group:id2 | final-verification | prepared | 2026-04-04 15:30 | -- |
79
+ | # | Task Key | Type | currentStatus | latestRunStatus | Last Run | Report |
80
+ |---|----------|------|---------------|------------------|----------|--------|
81
+ | 1 | proj:group:id | error-analysis | completed | completed | 2026-04-05 22:59 | .project-docs/.../final-report-*.md |
82
+ | 2 | proj:group:id2 | final-verification | todo | error | 2026-04-04 15:30 | -- |
63
83
  ```
64
84
 
65
- 5. If `task-catalog.json` is missing, it responds with "There is no okstra execution history. Please run okstra.sh first."
85
+ ### Catalog absent fallback
86
+
87
+ If `.project-docs/okstra/discovery/task-catalog.json` does not exist, do NOT bail out. The catalog is a derived index — manifests on disk are the source of truth.
88
+
89
+ 1. Glob `<projectRoot>/.project-docs/okstra/tasks/*/*/task-manifest.json`.
90
+ 2. For each manifest, read `taskKey`, `taskGroup`, `taskType`, `currentStatus`, `latestRunStatus`, `updatedAt`, `latestReportPath`, `latestResumeCommandPath`, `latestRunManifestPath`, `historyTimelinePath`.
91
+ 3. Apply the same filters/sort/limit and print the same table, prefixed with: `note: task-catalog.json missing; reconstructed from task manifests on disk.`
92
+ 4. Only if the glob yields zero manifests: respond `There is no okstra execution history yet.`
66
93
 
67
94
  ## Step 2: Run History by Task
68
95
 
@@ -78,9 +105,10 @@ When a user selects a specific task or requests detailed history:
78
105
  | `runDateTimeSegment` | YYYY-MM-DD_HH-MM-SS |
79
106
  | `taskType` | `--task-type` argument value |
80
107
  | `status` | Run status |
81
- | `reportPath` | Report Path |
82
- | `resumeCommandPath` | resume Command path |
83
- | `relatedTasks` | List of related tasks |
108
+ | `runManifestPath` | This run's `run-manifest-*.json` — feed into Step 3 to re-run from this specific run's parameters |
109
+ | `reportPath` | Final report path |
110
+ | `resumeCommandPath` | Resume Claude session for this run (Step 4) |
111
+ | `relatedTasks` | List of related task-keys |
84
112
 
85
113
  4. Output format:
86
114
 
@@ -93,23 +121,26 @@ When a user selects a specific task or requests detailed history:
93
121
  | 2 | 2026-04-04 15:30 | error-analysis | error | -- |
94
122
  ```
95
123
 
96
- ## Step 3: Create a re-execution command
124
+ ## Step 3: Re-run (build a NEW run from an old run's parameters)
97
125
 
98
- To re-run a specific run:
126
+ This builds a **fresh run** — new run-seq, new manifest, new report — using the parameters captured in a previous `run-manifest-*.json`. It does NOT touch the old run's artifacts; use Step 4 if the user wants to continue an interrupted session instead.
99
127
 
100
- 1. Read the run-manifest JSON from the `runManifestPath` of that run.
101
- 2. Extract the required arguments:
128
+ 1. Pick the source run-manifest: the `runManifestPath` from a Step 2 timeline entry, or the task's `latestRunManifestPath` from Step 1.
129
+ 2. Read the run-manifest JSON and extract required arguments:
102
130
  - `projectId` → `--project-id`
103
131
  - `taskGroup` → `--task-group`
104
132
  - `taskId` → `--task-id`
105
133
  - `taskType` → `--task-type`
106
134
  - `taskBriefPath` → `--task-brief`
107
- 3. Extract the optional arguments:
135
+ 3. Extract optional arguments (include only when present in the source manifest):
108
136
  - `recommendedWorkers` → `--workers` (comma-separated)
109
- - `relatedTasks` → `--related-tasks` (if present)
110
- - model overrides → `--claude-model`, `--codex-model`, `--gemini-model` (if different from default)
111
- - for `taskType: implementation`: `teamContract.executor.provider` → `--executor <claude|codex|gemini>` (if different from `claude`)
112
- 4. Display the assembled command:
137
+ - `relatedTasks` → `--related-tasks`
138
+ - model overrides → `--claude-model`, `--codex-model`, `--gemini-model` (when different from default)
139
+ - for `taskType: implementation`: `teamContract.executor.provider` → `--executor <claude|codex|gemini>` (when different from `claude`)
140
+ 4. **`taskType: implementation` only — resolve `--base-ref`:** the base ref is NOT stored in the run-manifest; it lives in the worktree registry at `~/.okstra/worktrees/registry.json` against the registered branch. Before assembling the command:
141
+ - If a worktree for this task-key is already registered, the existing branch & base are reused — omit `--base-ref` unless the user explicitly wants a different starting point.
142
+ - If no worktree is registered (e.g. it was cleaned up), `--base-ref` is mandatory. Ask the user for the ref to branch from (e.g. `main`, a commit SHA, a tag) before running.
143
+ 5. Display the assembled command:
113
144
 
114
145
  ```bash
115
146
  okstra.sh \
@@ -121,23 +152,23 @@ okstra.sh \
121
152
  --workers <worker-list>
122
153
  ```
123
154
 
124
- 5. Once the user confirms, execute it using the Bash tool.
155
+ 6. Once the user confirms, execute it using the Bash tool.
125
156
 
126
- ## Step 4: Resume
157
+ ## Step 4: Resume (continue an interrupted run)
127
158
 
128
- To resume a paused session:
159
+ This continues an existing Claude session for a run that did not finish. It does NOT create a new run-seq — for a fresh dispatch, use Step 3.
129
160
 
130
- 1. Check `latestResumeCommandPath` in the task catalog or timeline.
131
- 2. Verify that the resume script file actually exists.
132
- 3. If it exists, display the execution command:
161
+ 1. Read `latestResumeCommandPath` from the task catalog (Step 1) — or `resumeCommandPath` from a specific timeline entry (Step 2).
162
+ 2. Verify the file exists on disk.
163
+ 3. If present:
133
164
  ```bash
134
165
  bash <resume-command-path>
135
166
  ```
136
- 4. If it does not exist, display the message: "No resume script found. Please run it again."
167
+ 4. If absent, report: `No resume script available for this run. Use Step 3 to start a fresh run instead.`
137
168
 
138
169
  ## Output Rules
139
170
 
140
171
  - Display concisely in a table format
141
172
  - Dates in `YYYY-MM-DD HH:MM` format
142
- - Display status as-is (`completed`, `prepared`, `error`, `not-run`, etc.)
173
+ - Display status fields as-is from disk (`completed`, `contract-violated`, `todo`, `error`, empty, ...). Do not normalize or remap.
143
174
  - Display `--` if no report is available
@@ -36,6 +36,9 @@ multi-MB logs; analysis-phase dispatches are typically smaller.
36
36
 
37
37
  ## Step 0: Verify okstra runtime + project setup
38
38
 
39
+ Before any other step, ensure both the okstra runtime and the current
40
+ project's okstra metadata are in place:
41
+
39
42
  ```bash
40
43
  if command -v okstra >/dev/null 2>&1; then
41
44
  OKSTRA_CMD="okstra"
@@ -46,6 +49,8 @@ $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
46
49
  echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
47
50
  exit 1
48
51
  }
52
+ eval "$($OKSTRA_CMD paths --shell)"
53
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
49
54
  OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
50
55
  echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
51
56
  echo "$OKSTRA_PROJECT_INFO" >&2
@@ -68,7 +73,7 @@ LOGS_ROOT="$PROJECT_ROOT/.project-docs/okstra/tasks"
68
73
  # columns: size_bytes | mtime_epoch | path
69
74
  find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' \
70
75
  -printf '%s\t%T@\t%p\n' 2>/dev/null \
71
- | sort -k2,2nr
76
+ | sort -k1,1nr
72
77
  ```
73
78
 
74
79
  On macOS, `find -printf` is unavailable. Fall back to `stat`:
@@ -78,7 +83,7 @@ find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' 2>/dev/null \
78
83
  | while IFS= read -r p; do
79
84
  stat -f '%z%t%m%t%N' "$p"
80
85
  done \
81
- | sort -k2,2nr
86
+ | sort -k1,1nr
82
87
  ```
83
88
 
84
89
  If the result is empty, report `No wrapper log files found under <PROJECT_ROOT>` and exit.
@@ -117,27 +122,40 @@ Total: N files, X.X MB across M tasks under <PROJECT_ROOT>
117
122
  ## Step 3: Suggested cleanup commands
118
123
 
119
124
  Emit a fenced bash block the user can copy-paste. Do NOT execute these.
125
+ Each block pairs a dry-run preview (`-print`) with the destructive
126
+ (`-delete`) command so the user can confirm the match set before
127
+ committing.
120
128
 
121
129
  ```markdown
122
130
  ## Cleanup options (manual)
123
131
 
124
132
  # 7일 이상 된 로그만 삭제
133
+ find <PROJECT_ROOT>/.project-docs/okstra/tasks \
134
+ -type f -path '*/runs/*/prompts/*.log' -mtime +7 -print # dry-run
125
135
  find <PROJECT_ROOT>/.project-docs/okstra/tasks \
126
136
  -type f -path '*/runs/*/prompts/*.log' -mtime +7 -delete
127
137
 
128
138
  # 30일 이상 된 로그만 삭제
139
+ find <PROJECT_ROOT>/.project-docs/okstra/tasks \
140
+ -type f -path '*/runs/*/prompts/*.log' -mtime +30 -print # dry-run
129
141
  find <PROJECT_ROOT>/.project-docs/okstra/tasks \
130
142
  -type f -path '*/runs/*/prompts/*.log' -mtime +30 -delete
131
143
 
132
144
  # 특정 task-group 의 로그 일괄 삭제 (예: dev-9388)
145
+ find <PROJECT_ROOT>/.project-docs/okstra/tasks/dev-9388 \
146
+ -type f -name '*.log' -print # dry-run
133
147
  find <PROJECT_ROOT>/.project-docs/okstra/tasks/dev-9388 \
134
148
  -type f -name '*.log' -delete
135
149
 
136
150
  # 특정 task-id 의 로그 일괄 삭제 (예: dev-9428)
151
+ find <PROJECT_ROOT>/.project-docs/okstra/tasks/*/dev-9428 \
152
+ -type f -name '*.log' -print # dry-run
137
153
  find <PROJECT_ROOT>/.project-docs/okstra/tasks/*/dev-9428 \
138
154
  -type f -name '*.log' -delete
139
155
 
140
156
  # 전체 일괄 삭제 (주의)
157
+ find <PROJECT_ROOT>/.project-docs/okstra/tasks \
158
+ -type f -path '*/runs/*/prompts/*.log' -print # dry-run
141
159
  find <PROJECT_ROOT>/.project-docs/okstra/tasks \
142
160
  -type f -path '*/runs/*/prompts/*.log' -delete
143
161
  ```
@@ -152,10 +170,14 @@ End the response with these short reminders:
152
170
  - Logs are truncated on each re-dispatch of the same `seq`, so deleting an
153
171
  in-flight run's log will cause the wrapper to recreate an empty file on
154
172
  the next dispatch — no data loss beyond the current trace.
173
+ - **If a dispatch is currently running, check `okstra status` first** and
174
+ avoid deleting logs for tasks in `in-progress` state — you will lose
175
+ the live trace for the active run.
155
176
  - Prompt history files (`.md`) are separate and are NOT touched by these
156
177
  commands — only `.log` sidecars.
157
- - This skill does not modify `.gitignore`. If the project commits
158
- `.project-docs/okstra/`, the user may want to add
178
+ - This skill **does not modify any external files itself**, including
179
+ `.gitignore`. If the project commits `.project-docs/okstra/`, the user
180
+ may want to add
159
181
  `.project-docs/okstra/tasks/**/runs/**/prompts/*.log` to `.gitignore`
160
182
  manually to keep large logs out of git.
161
183
 
@@ -40,54 +40,81 @@ OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
40
40
 
41
41
  task-key 형식: `<project-id>:<task-group>:<task-id>`
42
42
 
43
+ **Normalization**: task-key 매칭은 lowercase 로 수행한다. 디스크상의 group/id 세그먼트는 slugify (lowercase + non-alphanumeric runs → `-`) 된다 (`scripts/lib/okstra/interactive.sh:147`). 따라서 catalog 조회는 case-insensitive 이지만 파일 경로를 만들 때는 slugified segment 를 사용해야 한다.
44
+
43
45
  ### 방법 A: task-catalog.json (빠름)
44
46
 
45
47
  1. `.project-docs/okstra/discovery/task-catalog.json`을 읽는다.
46
- 2. `tasks` 배열에서 `taskKey`가 일치하는 항목을 찾는다.
47
- 3. `latestReportPath` 필드가 최신 보고서 경로이다.
48
+ 2. `tasks` 배열에서 `taskKey` lowercase 비교로 매칭한다.
49
+ 3. `latestReportPath` 필드는 task-type 과 무관한 "가장 최근 보고서" 이다.
48
50
 
49
51
  ### 방법 B: task-manifest.json (직접)
50
52
 
51
- task-catalog가 없거나 최신이 아닌 경우:
53
+ task-catalog 없는 경우:
54
+
55
+ 1. task-key 에서 task-group / task-id 를 추출하고 slugify 한다.
56
+ 2. `.project-docs/okstra/tasks/<group-segment>/<id-segment>/task-manifest.json` 을 읽는다.
57
+ 3. `latestReportPath` 필드 (task-type 무관, 최신).
58
+
59
+ ### 방법 C: timeline.json (특정 run 의 보고서)
52
60
 
53
- 1. task-key에서 task-group과 task-id를 추출한다.
54
- 2. `.project-docs/okstra/tasks/<task-group>/<task-id>/task-manifest.json`을 읽는다.
55
- 3. `latestReportPath` 필드가 최신 보고서 경로이다.
61
+ 특정 날짜나 run 보고서가 필요한 경우:
56
62
 
57
- ### 방법 C: timeline.json (특정 run의 보고서)
63
+ 1. `.project-docs/okstra/tasks/<group-segment>/<id-segment>/history/timeline.json` 읽는다.
64
+ 2. `runs[]` 배열에서 필터한다. 실제 필드:
65
+ - `runs[].runTimestamp` (ISO-8601 시작 시각)
66
+ - `runs[].status` (run 종료 상태)
67
+ - `runs[].taskType` (`requirements-discovery` 등 phase 이름)
68
+ - `runs[].reportPath` (해당 run 의 보고서 상대 경로)
58
69
 
59
- 특정 날짜나 run보고서가 필요한 경우:
70
+ ### 방법 D: 특정 task-type최신 보고서 (fallback)
60
71
 
61
- 1. `.project-docs/okstra/tasks/<task-group>/<task-id>/history/timeline.json`을 읽는다.
62
- 2. `runs` 배열에서 원하는 run을 찾는다 (날짜, 상태 등으로 필터).
63
- 3. `reportPath` 필드가 해당 run의 보고서 경로이다.
72
+ `latestReportPath` (방법 A/B) 는 task-type 을 가리지 않는다. 사용자가 *특정* task-type (예: `implementation-planning`) 의 최신 보고서를 요청하면:
73
+
74
+ 1. 디렉토리: `.project-docs/okstra/tasks/<group-segment>/<id-segment>/runs/<task-type-segment>/reports/`
75
+ 2. 파일명 패턴: `final-report-<task-type-segment>-<NNN>.md` (`scripts/okstra_ctl/sequence.py:31`)
76
+ 3. seq 가 가장 큰 파일이 해당 task-type 의 최신 보고서.
77
+ 4. timeline.json 의 `runs[].taskType == <task-type>` 으로 교차검증 가능.
64
78
 
65
79
  ## Step 2: Report 존재 확인
66
80
 
67
- 1. 찾은 경로에 파일이 실제로 존재하는지 확인한다.
81
+ 1. `latestReportPath`가 비어있지 않고, 해당 경로의 파일이 실제로 존재하는지 확인한다.
82
+ - 두 신호 중 하나라도 보고서 존재를 가리키면 경로를 표시한다 (tolerant).
68
83
  2. 존재하면 경로를 표시하고 읽을지 사용자에게 확인한다.
69
- 3. 존재하지 않으면:
70
- - `task-manifest.json`의 `currentStatus`를 확인한다.
71
- - status가 `completed`가 아니면: "이 task는 아직 완료되지 않았습니다 (status: `<status>`)."
72
- - 파일만 없으면: "보고서 파일이 존재하지 않습니다: `<path>`"
84
+ 3. 존재하지 않으면 `task-manifest.json`에서 다음 신호를 확인한다:
85
+ - `latestReportPath`가 비어있거나 누락된 경우, 그리고
86
+ - `currentStatus`가 `completed`가 아니고, `workStatus`가 `done`이 아니며, `workflow.routingStatus`도 완료를 가리키지 않는 경우
87
+ "이 task는 아직 완료되지 않았습니다 (currentStatus: `<currentStatus>`, workStatus: `<workStatus>`)."
88
+ - 위 신호 중 하나라도 완료를 가리키는데 파일만 없는 경우
89
+ → "보고서 파일이 존재하지 않습니다: `<path>`"
90
+
91
+ 워크플로 상태 enum은 `todo | in-progress | blocked | done` (workStatus) 와 `currentStatus`의 `completed` / `contract-violated` 등이다. `"completed"` 문자열은 `workStatus`에는 존재하지 않으므로 두 필드를 혼동하지 말 것.
73
92
 
74
93
  ## Step 3: Report 읽기 및 후속 작업 안내
75
94
 
76
95
  보고서를 읽은 후 사용자에게 가능한 후속 작업을 안내한다:
77
96
 
78
97
  1. **구현 진행**: 보고서의 "권장 다음 단계" 섹션 기반으로 코드 수정
79
- 2. **추가 검증**: 같은 task-key로 새 okstra run 실행 (`okstra-history` 스킬로 재실행 커맨드 생성)
80
- 3. **관련 task 확인**: 보고서의 관련 task 참조가 있으면 해당 task의 보고서도 조회
98
+ 2. **추가 검증**: 같은 task-key 로 새 okstra run 실행. 구체 커맨드:
99
+ ```bash
100
+ scripts/okstra.sh --task-key <task-key> --task-type <task-type>
101
+ ```
102
+ 더 풍부한 옵션 (base-ref, workers, render-only 등) 이 필요하면 `okstra-history` 스킬을 사용한다.
103
+ 3. **관련 task 확인**: 보고서의 관련 task 참조가 있으면 해당 task 의 보고서도 조회
81
104
 
82
105
  ## Output
83
106
 
84
107
  ```markdown
85
108
  ## Report for <task-key>
86
109
 
87
- - Status: `<status>`
88
- - Report path: `<relative-path>`
89
- - Run date: `<YYYY-MM-DD HH:MM>`
90
- - Task type: `<task-type>`
110
+ | Field | Value |
111
+ | ------------ | -------------------------------------------------- |
112
+ | Status | `<status>` |
113
+ | Task type | `<task-type>` |
114
+ | Run seq | `<NNN>` |
115
+ | Run date | `<runTimestamp ISO-8601>` |
116
+ | Report (rel) | `<relative-path-from-project-root>` |
117
+ | Report (abs) | `<absolute-path>` |
91
118
  ```
92
119
 
93
120
  이후 사용자 요청에 따라 보고서를 읽고 후속 작업을 진행한다.