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.
Files changed (48) hide show
  1. package/README.kr.md +16 -0
  2. package/README.md +16 -0
  3. package/docs/kr/architecture.md +3 -7
  4. package/docs/kr/cli.md +47 -4
  5. package/docs/kr/performance-improvement-plan-v2.md +23 -0
  6. package/docs/kr/performance-improvement-plan.md +22 -0
  7. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +254 -0
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/SKILL.md +30 -2
  11. package/runtime/bin/okstra.sh +1 -1
  12. package/runtime/prompts/profiles/_common-contract.md +30 -1
  13. package/runtime/prompts/profiles/error-analysis.md +12 -0
  14. package/runtime/prompts/profiles/implementation-planning.md +23 -0
  15. package/runtime/prompts/profiles/requirements-discovery.md +20 -0
  16. package/runtime/python/lib/okstra/cli.sh +8 -7
  17. package/runtime/python/lib/okstra/globals.sh +3 -1
  18. package/runtime/python/lib/okstra/usage.sh +8 -4
  19. package/runtime/python/okstra_ctl/render.py +35 -0
  20. package/runtime/python/okstra_ctl/run.py +27 -6
  21. package/runtime/python/okstra_ctl/run_context.py +1 -1
  22. package/runtime/python/okstra_ctl/wizard.py +259 -10
  23. package/runtime/python/okstra_token_usage/blocks.py +5 -1
  24. package/runtime/python/okstra_token_usage/claude.py +16 -1
  25. package/runtime/python/okstra_token_usage/collect.py +17 -3
  26. package/runtime/python/okstra_token_usage/pricing.py +159 -24
  27. package/runtime/skills/okstra-brief/SKILL.md +532 -65
  28. package/runtime/skills/okstra-context-loader/SKILL.md +25 -11
  29. package/runtime/skills/okstra-convergence/SKILL.md +235 -8
  30. package/runtime/skills/okstra-history/SKILL.md +68 -37
  31. package/runtime/skills/okstra-logs/SKILL.md +26 -4
  32. package/runtime/skills/okstra-report-finder/SKILL.md +49 -22
  33. package/runtime/skills/okstra-report-writer/SKILL.md +59 -64
  34. package/runtime/skills/okstra-run/SKILL.md +53 -39
  35. package/runtime/skills/okstra-schedule/SKILL.md +51 -20
  36. package/runtime/skills/okstra-setup/SKILL.md +31 -12
  37. package/runtime/skills/okstra-status/SKILL.md +20 -8
  38. package/runtime/skills/okstra-team-contract/SKILL.md +27 -15
  39. package/runtime/skills/okstra-time-summary/SKILL.md +53 -16
  40. package/runtime/templates/reports/final-report.template.md +34 -0
  41. package/runtime/templates/reports/settings.template.json +7 -4
  42. package/runtime/validators/lib/fixtures.sh +10 -2
  43. package/runtime/validators/lib/validate-assets.sh +50 -24
  44. package/runtime/validators/validate-brief.py +385 -0
  45. package/runtime/validators/validate-brief.sh +35 -0
  46. package/runtime/validators/validate-run.py +71 -0
  47. package/runtime/validators/validate-workflow.sh +7 -33
  48. 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
- step "Verifying that rerun without refresh preserves project-local assets"
159
- require_file "$MARKER_FILE"
160
- printf '\n%s\n' "$MARKER" >>"$MARKER_FILE"
161
- assert_contains "$MARKER_FILE" "$MARKER"
162
- if ! run_okstra "$SECONDARY_TASK_GROUP" "$SECONDARY_TASK_ID" "$SECONDARY_BRIEF_FILENAME"; then
163
- fail "Secondary task rerun without refresh failed"
164
- fi
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
- init seed a fresh wizard state and emit the first prompt
11
- step submit an answer (or fetch the current prompt) and emit next
12
- render-args emit the final --flag/value map for 'okstra render-bundle'
13
- confirmation emit the multi-line confirmation echo block
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);