okstra 0.63.0 → 0.64.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 (29) hide show
  1. package/docs/kr/architecture.md +1 -1
  2. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +320 -0
  3. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +42 -0
  4. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +337 -0
  5. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +66 -0
  6. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +87 -0
  7. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +113 -0
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/SKILL.md +5 -2
  11. package/runtime/agents/TODO.md +9 -2
  12. package/runtime/agents/workers/claude-worker.md +1 -1
  13. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +23 -4
  14. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  15. package/runtime/prompts/wizard/prompts.ko.json +17 -1
  16. package/runtime/python/okstra_ctl/backfill.py +23 -4
  17. package/runtime/python/okstra_ctl/consumers.py +118 -1
  18. package/runtime/python/okstra_ctl/paths.py +11 -0
  19. package/runtime/python/okstra_ctl/run.py +147 -67
  20. package/runtime/python/okstra_ctl/run_context.py +2 -0
  21. package/runtime/python/okstra_ctl/wizard.py +127 -29
  22. package/runtime/skills/okstra-convergence/SKILL.md +3 -1
  23. package/runtime/skills/okstra-report-writer/SKILL.md +2 -0
  24. package/runtime/skills/okstra-run/SKILL.md +1 -1
  25. package/runtime/skills/okstra-team-contract/SKILL.md +37 -0
  26. package/runtime/templates/reports/final-report.template.md +1 -1
  27. package/runtime/validators/validate-run.py +20 -3
  28. package/src/install.mjs +21 -0
  29. package/src/uninstall.mjs +17 -17
@@ -151,7 +151,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
151
151
  {%- endif %}
152
152
 
153
153
  {% if header.taskType == 'implementation-planning' %}
154
- ## 5.5 Implementation Plan Deliverables
154
+ ## 5.4 Implementation Plan Deliverables
155
155
 
156
156
  ### Option Candidates{% if t("sectionAside.optionCandidates") != "Option Candidates" %} ({{ t("sectionAside.optionCandidates") }}){% endif %}
157
157
 
@@ -748,6 +748,20 @@ def _parse_diff_summary_files(content: str) -> list[str]:
748
748
  return _DIFF_ROW_PATH_RE.findall(section.group(0))
749
749
 
750
750
 
751
+ def _task_root_from_run_dir(run_dir: Path) -> Path:
752
+ """run_dir 에서 `runs` 디렉터리를 앵커로 task_root 를 복원한다.
753
+
754
+ 일반 task-type: run_dir = task_root/runs/<task-type> → task_root.
755
+ implementation(stage 격리): run_dir = task_root/runs/implementation/stage-<N>
756
+ → 같은 task_root. `runs` 를 위로 탐색하므로 stage-<N> 레벨이 추가돼도
757
+ 안전하다. `runs` 가 조상에 없으면 기존 동작(두 단계 위)로 폴백한다.
758
+ """
759
+ for parent in run_dir.parents:
760
+ if parent.name == "runs":
761
+ return parent.parent
762
+ return run_dir.parent.parent
763
+
764
+
751
765
  def _validate_conformance(report_path: Path, failures: list[str],
752
766
  surface_patterns: object = None) -> None:
753
767
  """Tier 3 conformance 게이트(implementation / final-verification).
@@ -759,10 +773,13 @@ def _validate_conformance(report_path: Path, failures: list[str],
759
773
  """
760
774
  # conformance 산출물은 task-level(<task_root>/qa)에 있어 planning/
761
775
  # implementation/final-verification 가 공유한다. report_path 는
762
- # task_root/runs/<task-type>/reports/final-report.md 이므로 task_root 는
763
- # run_dir(=report_path.parent.parent)에서 단계 위.
776
+ # task_root/runs/<task-type>/reports/final-report.md (implementation
777
+ # stage 격리로 runs/implementation/stage-<N>/reports/...) 이므로 고정 parent
778
+ # 카운트 대신 `runs` 디렉터리를 앵커로 task_root 를 찾는다 — stage-<N> 레벨이
779
+ # 있어도 task_root/qa 로 정확히 떨어진다.
764
780
  run_dir = report_path.parent.parent
765
- qa_dir = run_dir.parent.parent / "qa"
781
+ task_root = _task_root_from_run_dir(run_dir)
782
+ qa_dir = task_root / "qa"
766
783
  manifest_path = qa_dir / "conformance-manifest.json"
767
784
  if not manifest_path.is_file():
768
785
  return
package/src/install.mjs CHANGED
@@ -44,6 +44,7 @@ Usage:
44
44
 
45
45
  Effect (copy mode):
46
46
  ${"$HOME"}/.okstra/lib/python <- runtime/python
47
+ ${"$HOME"}/.okstra/lib/validators <- runtime/validators (stage/report structure validators)
47
48
  ${"$HOME"}/.okstra/bin <- runtime/bin
48
49
  ${"$HOME"}/.okstra/templates <- runtime/templates (report.css / report.js / *.template.md)
49
50
  ${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
@@ -577,12 +578,27 @@ export async function runInstall(args) {
577
578
  join(paths.home, "schemas"),
578
579
  { refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
579
580
  );
581
+ // validators/ tree — validate-implementation-plan-stages.py (+ lib/) is
582
+ // resolved at runtime by run.py / validate-run.py via the installed package's
583
+ // `parents[2]/validators`, i.e. ~/.okstra/lib/validators. Without this step a
584
+ // copy-mode UPGRADE bumps the version stamp but never refreshes the
585
+ // validators, so a stale validator (e.g. a pre-renumber section schema)
586
+ // survives and render-bundle's stage-structure check diverges from the
587
+ // current template (observed: render-bundle demanding `## 4.5 Stage Map`
588
+ // while the report + wizard validator use `## 5.5`). Mode 0o755 preserves the
589
+ // executable .sh helpers in the tree.
590
+ const validatorsResult = await copyTreeIfChanged(
591
+ join(runtimeRoot, "validators"),
592
+ join(paths.home, "lib", "validators"),
593
+ { refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o755 },
594
+ );
580
595
 
581
596
  if (!opts.quiet) {
582
597
  summarise("python", pythonResult, paths.pythonpath);
583
598
  summarise("bin", binResult, paths.bin);
584
599
  summarise("templates", templatesResult, join(paths.home, "templates"));
585
600
  summarise("schemas", schemasResult, join(paths.home, "schemas"));
601
+ summarise("validators", validatorsResult, join(paths.home, "lib", "validators"));
586
602
  }
587
603
 
588
604
  if (pythonResult.missingSource && binResult.missingSource) {
@@ -600,6 +616,11 @@ export async function runInstall(args) {
600
616
  "warning: runtime/schemas is empty. final-report schema validation + excerpt generation will be unavailable — re-run the build step.\n",
601
617
  );
602
618
  }
619
+ if (validatorsResult.missingSource) {
620
+ process.stderr.write(
621
+ "warning: runtime/validators is empty. stage-structure / report validation will use stale or missing validators — re-run the build step.\n",
622
+ );
623
+ }
603
624
 
604
625
  const skillResult = await installSkillsCopy(runtimeRoot, opts);
605
626
  await writeSkillsManifest(paths.home, skillResult.installed, { dryRun: opts.dryRun });
package/src/uninstall.mjs CHANGED
@@ -53,10 +53,10 @@ const AGENTS_MANIFEST_REL = "installed-agents.json";
53
53
  const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.claude/skills, ~/.claude/agents
54
54
 
55
55
  Usage:
56
- okstra uninstall Remove ~/.okstra/{lib, bin/<known>, version,
56
+ okstra uninstall Remove ~/.okstra/{lib (python + validators),
57
+ bin/<known>, schemas, templates, version,
57
58
  dev-link, installed-skills.json,
58
- installed-agents.json,
59
- templates/settings.local.json} AND
59
+ installed-agents.json} AND
60
60
  ~/.claude/skills/<name> AND
61
61
  ~/.claude/agents/<worker>.md for every
62
62
  entry in the install manifests (fallback:
@@ -156,6 +156,12 @@ export async function runUninstall(args) {
156
156
  process.stdout.write(` home: ${paths.home}\n`);
157
157
  }
158
158
  await removePath(paths.pythonpath, opts);
159
+ // lib/validators — installed wholesale by copy mode (runtime/validators).
160
+ // Without removing it, `lib` stays non-empty so the rmdir below is skipped and
161
+ // a stale validator survives uninstall → reinstall, diverging from the current
162
+ // template's section schema (observed: a pre-renumber `## 4.5 Stage Map`
163
+ // validator outliving every later install).
164
+ await removePath(join(paths.home, "lib", "validators"), opts);
159
165
  for (const name of BIN_ENTRYPOINTS) {
160
166
  await removePath(join(paths.bin, name), opts);
161
167
  }
@@ -174,6 +180,10 @@ export async function runUninstall(args) {
174
180
  }
175
181
  }
176
182
 
183
+ // schemas/ tree — installed by copy mode (runtime/schemas). Older uninstall
184
+ // never removed it, leaving a stale final-report schema behind.
185
+ await removePath(join(paths.home, "schemas"), opts);
186
+
177
187
  // Remove the skills we own. Manifest is authoritative; fall back to the
178
188
  // hard-coded okstra-* names if it is missing (e.g. an install from an old
179
189
  // version that did not write the manifest).
@@ -196,24 +206,14 @@ export async function runUninstall(args) {
196
206
  }
197
207
  await removePath(join(paths.home, AGENTS_MANIFEST_REL), opts);
198
208
 
199
- await removePath(join(paths.home, "templates", "settings.local.json"), opts);
209
+ // templates/ tree — installed wholesale by copy mode (runtime/templates),
210
+ // including the seeded settings.local.json sidecar. Remove the whole tree so
211
+ // an upgrade/reinstall never serves a stale report.css / *.template.md.
212
+ await removePath(join(paths.home, "templates"), opts);
200
213
  // Per-project <PROJECT>/.claude/settings.local.json symlinks are NOT removed
201
214
  // here — uninstall is machine-level and does not know which projects opted
202
215
  // in. They will dangle until the user removes them manually or re-runs
203
216
  // okstra install + okstra setup.
204
- // Remove templates/ if now empty.
205
- const templatesDir = join(paths.home, "templates");
206
- if (await pathExists(templatesDir)) {
207
- try {
208
- const entries = await fs.readdir(templatesDir);
209
- if (entries.length === 0) {
210
- if (!opts.dryRun) await fs.rmdir(templatesDir);
211
- if (!opts.quiet) process.stdout.write(` removed empty: ${templatesDir}\n`);
212
- }
213
- } catch {
214
- /* ignore */
215
- }
216
- }
217
217
 
218
218
  await removePath(join(paths.home, "version"), opts);
219
219
  await removePath(join(paths.home, "dev-link"), opts);