okstra 0.25.1 → 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.
- package/README.kr.md +16 -0
- package/README.md +16 -0
- package/docs/kr/architecture.md +3 -7
- package/docs/kr/cli.md +47 -4
- package/docs/kr/performance-improvement-plan-v2.md +23 -0
- package/docs/kr/performance-improvement-plan.md +22 -0
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +254 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +30 -2
- package/runtime/bin/okstra.sh +1 -1
- package/runtime/prompts/profiles/_common-contract.md +30 -1
- package/runtime/prompts/profiles/error-analysis.md +12 -0
- package/runtime/prompts/profiles/implementation-planning.md +23 -0
- package/runtime/prompts/profiles/requirements-discovery.md +20 -0
- package/runtime/python/lib/okstra/cli.sh +8 -7
- package/runtime/python/lib/okstra/globals.sh +3 -1
- package/runtime/python/lib/okstra/usage.sh +8 -4
- package/runtime/python/okstra_ctl/render.py +35 -0
- package/runtime/python/okstra_ctl/run.py +27 -6
- package/runtime/python/okstra_ctl/run_context.py +1 -1
- package/runtime/python/okstra_ctl/wizard.py +259 -10
- package/runtime/python/okstra_token_usage/blocks.py +5 -1
- package/runtime/python/okstra_token_usage/claude.py +16 -1
- package/runtime/python/okstra_token_usage/collect.py +17 -3
- package/runtime/python/okstra_token_usage/pricing.py +159 -24
- package/runtime/skills/okstra-brief/SKILL.md +532 -65
- package/runtime/skills/okstra-context-loader/SKILL.md +25 -11
- package/runtime/skills/okstra-convergence/SKILL.md +235 -8
- package/runtime/skills/okstra-history/SKILL.md +68 -37
- package/runtime/skills/okstra-logs/SKILL.md +26 -4
- package/runtime/skills/okstra-report-finder/SKILL.md +49 -22
- package/runtime/skills/okstra-report-writer/SKILL.md +59 -64
- package/runtime/skills/okstra-run/SKILL.md +53 -39
- package/runtime/skills/okstra-schedule/SKILL.md +51 -20
- package/runtime/skills/okstra-setup/SKILL.md +31 -12
- package/runtime/skills/okstra-status/SKILL.md +20 -8
- package/runtime/skills/okstra-team-contract/SKILL.md +27 -15
- package/runtime/skills/okstra-time-summary/SKILL.md +53 -16
- package/runtime/templates/reports/final-report.template.md +34 -0
- package/runtime/templates/reports/settings.template.json +7 -4
- package/runtime/validators/lib/fixtures.sh +10 -2
- package/runtime/validators/lib/validate-assets.sh +50 -24
- package/runtime/validators/validate-brief.py +385 -0
- package/runtime/validators/validate-brief.sh +35 -0
- package/runtime/validators/validate-run.py +71 -0
- package/runtime/validators/validate-workflow.sh +7 -33
- package/src/wizard.mjs +21 -5
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import sys
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from pathlib import Path
|
|
@@ -527,6 +528,35 @@ PLANNING_REQUIRED_SECTIONS = (
|
|
|
527
528
|
"Validation Checklist",
|
|
528
529
|
"Rollback",
|
|
529
530
|
"User Approval Request",
|
|
531
|
+
"Plan Body Verification",
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
PLAN_VERIFY_GATE_VALUES = (
|
|
535
|
+
"passed",
|
|
536
|
+
"passed-with-dissent",
|
|
537
|
+
"blocked-by-disagreement",
|
|
538
|
+
"aborted-non-result",
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# `Gate result:` line in §4.5.9 of the final report — captures the value
|
|
542
|
+
# token that follows. Tolerates arbitrary markdown formatting between the
|
|
543
|
+
# label and the value (backticks for inline code, double-asterisks for
|
|
544
|
+
# bold, colons, hyphens, whitespace). The captured value is then
|
|
545
|
+
# validated against `PLAN_VERIFY_GATE_VALUES` below so typo'd or unknown
|
|
546
|
+
# values surface as their own failure rather than silently no-matching.
|
|
547
|
+
_GATE_RESULT_RE = re.compile(
|
|
548
|
+
r"Gate result[^A-Za-z\n]+(?P<value>[a-z][a-z\-]+)",
|
|
549
|
+
re.IGNORECASE,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Approval marker line — the bullet that toggles to grant approval. Both
|
|
553
|
+
# unchecked (`- [ ] Approved`) and checked (`- [x] Approved`) forms count
|
|
554
|
+
# as "checkbox present" for this gate. Line-anchored to avoid false
|
|
555
|
+
# positives from inline-code prose examples elsewhere in the template
|
|
556
|
+
# (mirrors `APPROVED_PLAN_PATTERN` in scripts/okstra_ctl/run.py:79).
|
|
557
|
+
_APPROVAL_CHECKBOX_RE = re.compile(
|
|
558
|
+
r"^[ \t]*(?:[-*+][ \t]+)?`?\[[ xX]\][ \t]*Approved`?[ \t]*$",
|
|
559
|
+
re.MULTILINE,
|
|
530
560
|
)
|
|
531
561
|
|
|
532
562
|
|
|
@@ -541,6 +571,13 @@ def validate_phase_boundary(
|
|
|
541
571
|
required deliverable sections; absence indicates a planning run that
|
|
542
572
|
skipped its core outputs (or an implementation run that ran under the
|
|
543
573
|
wrong task type).
|
|
574
|
+
|
|
575
|
+
Additionally enforces the Plan Body Verification gate (§4.5.9):
|
|
576
|
+
- gate ∈ {passed, passed-with-dissent} → top-level Approval checkbox
|
|
577
|
+
MUST be present.
|
|
578
|
+
- gate ∈ {blocked-by-disagreement, aborted-non-result} → checkbox
|
|
579
|
+
MUST be absent (lead converted findings into Clarification rows
|
|
580
|
+
instead of opening the gate).
|
|
544
581
|
"""
|
|
545
582
|
if task_type != "implementation-planning":
|
|
546
583
|
return
|
|
@@ -554,6 +591,40 @@ def validate_phase_boundary(
|
|
|
554
591
|
f"`{needle}`"
|
|
555
592
|
)
|
|
556
593
|
|
|
594
|
+
gate_match = _GATE_RESULT_RE.search(content)
|
|
595
|
+
if gate_match is None:
|
|
596
|
+
# The `Plan Body Verification` heading check above already covers
|
|
597
|
+
# the wholly-missing case; a heading present without a `Gate result`
|
|
598
|
+
# line is its own contract violation.
|
|
599
|
+
if "Plan Body Verification" in content:
|
|
600
|
+
failures.append(
|
|
601
|
+
"implementation-planning report has `Plan Body Verification` "
|
|
602
|
+
"section but no `Gate result:` line — required by §4.5.9."
|
|
603
|
+
)
|
|
604
|
+
return
|
|
605
|
+
gate_value = gate_match.group("value").strip().lower()
|
|
606
|
+
if gate_value not in PLAN_VERIFY_GATE_VALUES:
|
|
607
|
+
failures.append(
|
|
608
|
+
"implementation-planning report `Gate result` value "
|
|
609
|
+
f"`{gate_value}` is not one of "
|
|
610
|
+
f"{', '.join(PLAN_VERIFY_GATE_VALUES)}."
|
|
611
|
+
)
|
|
612
|
+
return
|
|
613
|
+
checkbox_present = _APPROVAL_CHECKBOX_RE.search(content) is not None
|
|
614
|
+
if gate_value in ("passed", "passed-with-dissent") and not checkbox_present:
|
|
615
|
+
failures.append(
|
|
616
|
+
"implementation-planning report Gate result is "
|
|
617
|
+
f"`{gate_value}` but the top-level `User Approval Request` "
|
|
618
|
+
"checkbox line (`- [ ] Approved` / `- [x] Approved`) is missing."
|
|
619
|
+
)
|
|
620
|
+
if gate_value in ("blocked-by-disagreement", "aborted-non-result") and checkbox_present:
|
|
621
|
+
failures.append(
|
|
622
|
+
"implementation-planning report Gate result is "
|
|
623
|
+
f"`{gate_value}` but a top-level `User Approval Request` "
|
|
624
|
+
"checkbox line is present — gate must NOT render the checkbox "
|
|
625
|
+
"for this gate result."
|
|
626
|
+
)
|
|
627
|
+
|
|
557
628
|
|
|
558
629
|
def _refresh_task_catalog(project_root: Path, task_manifest: dict) -> tuple[bool, str]:
|
|
559
630
|
"""Regenerate `discovery/task-catalog.json` so it stops trailing the
|
|
@@ -18,7 +18,6 @@ OKSTRA_SCRIPT="$WORKSPACE_ROOT/scripts/okstra.sh"
|
|
|
18
18
|
RUN_VALIDATOR_SCRIPT="$WORKSPACE_ROOT/validators/validate-run.py"
|
|
19
19
|
SOURCE_ASSET_ROOT="$WORKSPACE_ROOT/agents"
|
|
20
20
|
TASK_TYPE="final-verification"
|
|
21
|
-
MARKER="MANUAL-VALIDATION-MARKER"
|
|
22
21
|
PRIMARY_TASK_GROUP="validation"
|
|
23
22
|
PRIMARY_TASK_ID="asset-refresh-and-reference-expectations"
|
|
24
23
|
PRIMARY_BRIEF_FILENAME="validation-brief-primary.md"
|
|
@@ -56,7 +55,6 @@ PRIMARY_BRIEF_PATH="$PROJECT_ROOT/$PRIMARY_BRIEF_FILENAME"
|
|
|
56
55
|
SECONDARY_BRIEF_PATH="$PROJECT_ROOT/$SECONDARY_BRIEF_FILENAME"
|
|
57
56
|
DISCOVERY_FILE="$PROJECT_ROOT/$LATEST_TASK_RELATIVE_PATH"
|
|
58
57
|
CATALOG_FILE="$PROJECT_ROOT/$TASK_CATALOG_RELATIVE_PATH"
|
|
59
|
-
MARKER_FILE="$PROJECT_ROOT/.claude/agents/codex-worker.md"
|
|
60
58
|
PRIMARY_TASK_KEY="$(task_key "$PRIMARY_TASK_GROUP" "$PRIMARY_TASK_ID")"
|
|
61
59
|
SECONDARY_TASK_KEY="$(task_key "$SECONDARY_TASK_GROUP" "$SECONDARY_TASK_ID")"
|
|
62
60
|
PRIMARY_REFERENCE_EXPECTATIONS_FILE="$(task_root "$PRIMARY_TASK_GROUP" "$PRIMARY_TASK_ID")/instruction-set/reference-expectations.md"
|
|
@@ -155,36 +153,12 @@ if ! validate_task_catalog "$SECONDARY_TASK_KEY" "$PRIMARY_TASK_KEY" "$SECONDARY
|
|
|
155
153
|
fi
|
|
156
154
|
pass "latest-task.json and task-catalog.json now reflect distinct primary and secondary tasks"
|
|
157
155
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
assert_contains "$MARKER_FILE" "$MARKER"
|
|
166
|
-
if ! validate_latest_task_pointer "$SECONDARY_TASK_GROUP" "$SECONDARY_TASK_ID"; then
|
|
167
|
-
fail "latest-task.json changed unexpectedly during secondary rerun without refresh"
|
|
168
|
-
fi
|
|
169
|
-
if ! validate_task_catalog "$SECONDARY_TASK_KEY" "$PRIMARY_TASK_KEY" "$SECONDARY_TASK_KEY"; then
|
|
170
|
-
fail "task-catalog.json changed unexpectedly during secondary rerun without refresh"
|
|
171
|
-
fi
|
|
172
|
-
pass "Rerun without refresh preserved the modified project-local asset and retained both catalog entries"
|
|
173
|
-
|
|
174
|
-
step "Verifying that rerun with refresh regenerates project-local assets"
|
|
175
|
-
if ! run_okstra "$SECONDARY_TASK_GROUP" "$SECONDARY_TASK_ID" "$SECONDARY_BRIEF_FILENAME" --refresh-assets; then
|
|
176
|
-
fail "Secondary task rerun with --refresh-assets failed"
|
|
177
|
-
fi
|
|
178
|
-
assert_not_contains "$MARKER_FILE" "$MARKER"
|
|
179
|
-
if ! validate_seeded_assets match; then
|
|
180
|
-
fail "Refreshed project-local okstra assets do not match the source files"
|
|
181
|
-
fi
|
|
182
|
-
if ! validate_latest_task_pointer "$SECONDARY_TASK_GROUP" "$SECONDARY_TASK_ID"; then
|
|
183
|
-
fail "latest-task.json became invalid after refresh"
|
|
184
|
-
fi
|
|
185
|
-
if ! validate_task_catalog "$SECONDARY_TASK_KEY" "$PRIMARY_TASK_KEY" "$SECONDARY_TASK_KEY"; then
|
|
186
|
-
fail "task-catalog.json became invalid after refresh"
|
|
187
|
-
fi
|
|
188
|
-
pass "Refresh regenerated the mapped project-local okstra assets while preserving both catalog entries"
|
|
156
|
+
# Removed: the historical "rerun without refresh preserves project-local
|
|
157
|
+
# assets" and "rerun with refresh regenerates project-local assets" stanzas.
|
|
158
|
+
# Those tested a contract from when `okstra install` seeded per-project
|
|
159
|
+
# `.claude/` files; install now writes only to `$HOME/.claude` and
|
|
160
|
+
# `$HOME/.okstra`, so the project-local sentinel is no longer a meaningful
|
|
161
|
+
# contract. The `--refresh-assets` flag was removed entirely in a paired
|
|
162
|
+
# commit; users with old scripts should switch to `okstra install --refresh`.
|
|
189
163
|
|
|
190
164
|
print_summary
|
package/src/wizard.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
1
5
|
import { runPythonModule } from "./_python-helper.mjs";
|
|
2
6
|
import { resolvePaths } from "./paths.mjs";
|
|
3
7
|
|
|
@@ -7,12 +11,14 @@ Used by the okstra-run skill to drive the per-step prompt loop. Each
|
|
|
7
11
|
subcommand round-trips a JSON state file held by the skill.
|
|
8
12
|
|
|
9
13
|
Subcommands:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
new-state-file print a fresh absolute state-file path on stdout (no I/O on the file itself)
|
|
15
|
+
init seed a fresh wizard state and emit the first prompt
|
|
16
|
+
step submit an answer (or fetch the current prompt) and emit next
|
|
17
|
+
render-args emit the final --flag/value map for 'okstra render-bundle'
|
|
18
|
+
confirmation emit the multi-line confirmation echo block
|
|
14
19
|
|
|
15
20
|
Usage:
|
|
21
|
+
okstra wizard new-state-file
|
|
16
22
|
okstra wizard init --state-file <path> --project-root <p> --project-id <id>
|
|
17
23
|
okstra wizard step --state-file <path> [--answer <value>]
|
|
18
24
|
okstra wizard render-args --state-file <path>
|
|
@@ -50,11 +56,21 @@ export async function run(args) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const [sub, ...rest] = args;
|
|
53
|
-
if (!["init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
59
|
+
if (!["new-state-file", "init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
54
60
|
process.stderr.write(`error: unknown wizard subcommand '${sub}'\n\n${USAGE}`);
|
|
55
61
|
return 2;
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
if (sub === "new-state-file") {
|
|
65
|
+
if (rest.length > 0) {
|
|
66
|
+
process.stderr.write("error: new-state-file takes no arguments\n");
|
|
67
|
+
return 2;
|
|
68
|
+
}
|
|
69
|
+
const dir = mkdtempSync(join(tmpdir(), "okstra-wizard-"));
|
|
70
|
+
process.stdout.write(join(dir, "state.json") + "\n");
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
let flags;
|
|
59
75
|
try {
|
|
60
76
|
flags = parseFlags(rest);
|