okstra 0.34.1 → 0.36.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 (101) hide show
  1. package/README.kr.md +26 -16
  2. package/README.md +26 -16
  3. package/docs/kr/architecture.md +59 -45
  4. package/docs/kr/cli.md +61 -18
  5. package/docs/pr-template-usage.md +65 -0
  6. package/docs/project-structure-overview.md +358 -354
  7. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
  8. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
  9. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
  10. package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
  11. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
  12. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
  13. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
  14. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
  15. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
  16. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
  17. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
  18. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
  19. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
  20. package/docs/task-process/README.md +74 -0
  21. package/docs/task-process/common-flow.md +166 -0
  22. package/docs/task-process/error-analysis.md +101 -0
  23. package/docs/task-process/final-verification.md +167 -0
  24. package/docs/task-process/implementation-planning.md +128 -0
  25. package/docs/task-process/implementation.md +149 -0
  26. package/docs/task-process/release-handoff.md +206 -0
  27. package/docs/task-process/requirements-discovery.md +115 -0
  28. package/package.json +1 -1
  29. package/runtime/BUILD.json +2 -2
  30. package/runtime/agents/SKILL.md +12 -2
  31. package/runtime/agents/workers/claude-worker.md +26 -0
  32. package/runtime/agents/workers/codex-worker.md +27 -1
  33. package/runtime/agents/workers/gemini-worker.md +27 -1
  34. package/runtime/agents/workers/report-writer-worker.md +8 -1
  35. package/runtime/bin/okstra-central.sh +6 -6
  36. package/runtime/bin/okstra-codex-exec.sh +49 -28
  37. package/runtime/bin/okstra-gemini-exec.sh +39 -21
  38. package/runtime/bin/okstra-render-final-report.py +13 -2
  39. package/runtime/bin/okstra-wrapper-status.py +155 -0
  40. package/runtime/bin/okstra.sh +2 -2
  41. package/runtime/prompts/profiles/_common-contract.md +11 -6
  42. package/runtime/prompts/profiles/error-analysis.md +3 -7
  43. package/runtime/prompts/profiles/implementation-planning.md +22 -21
  44. package/runtime/prompts/profiles/implementation.md +28 -11
  45. package/runtime/prompts/profiles/improvement-discovery.md +42 -0
  46. package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
  47. package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
  48. package/runtime/prompts/profiles/kr/final-verification.md +48 -0
  49. package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
  50. package/runtime/prompts/profiles/kr/implementation.md +144 -0
  51. package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
  52. package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
  53. package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
  54. package/runtime/prompts/profiles/release-handoff.md +1 -1
  55. package/runtime/prompts/profiles/requirements-discovery.md +8 -12
  56. package/runtime/prompts/wizard/prompts.ko.json +230 -0
  57. package/runtime/python/lib/okstra/cli.sh +2 -49
  58. package/runtime/python/lib/okstra/globals.sh +21 -21
  59. package/runtime/python/lib/okstra/interactive.sh +7 -7
  60. package/runtime/python/okstra_ctl/clarification_items.py +3 -9
  61. package/runtime/python/okstra_ctl/consumers.py +53 -0
  62. package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
  63. package/runtime/python/okstra_ctl/i18n.py +73 -0
  64. package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
  65. package/runtime/python/okstra_ctl/index.py +1 -1
  66. package/runtime/python/okstra_ctl/paths.py +23 -20
  67. package/runtime/python/okstra_ctl/render.py +147 -202
  68. package/runtime/python/okstra_ctl/render_final_report.py +53 -10
  69. package/runtime/python/okstra_ctl/run.py +292 -107
  70. package/runtime/python/okstra_ctl/run_context.py +22 -0
  71. package/runtime/python/okstra_ctl/seeding.py +186 -0
  72. package/runtime/python/okstra_ctl/wizard.py +348 -127
  73. package/runtime/python/okstra_ctl/workflow.py +21 -2
  74. package/runtime/python/okstra_ctl/worktree.py +54 -1
  75. package/runtime/python/okstra_project/resolver.py +4 -3
  76. package/runtime/python/okstra_token_usage/report.py +2 -2
  77. package/runtime/schemas/final-report-v1.0.schema.json +22 -16
  78. package/runtime/skills/okstra-brief/SKILL.md +124 -31
  79. package/runtime/skills/okstra-convergence/SKILL.md +2 -3
  80. package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
  81. package/runtime/skills/okstra-run/SKILL.md +5 -4
  82. package/runtime/skills/okstra-schedule/SKILL.md +4 -4
  83. package/runtime/skills/okstra-setup/SKILL.md +27 -0
  84. package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
  85. package/runtime/templates/okstra.CLAUDE.md +104 -0
  86. package/runtime/templates/reports/final-report.template.md +93 -98
  87. package/runtime/templates/reports/i18n/en.json +135 -0
  88. package/runtime/templates/reports/i18n/ko.json +135 -0
  89. package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
  90. package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
  91. package/runtime/templates/reports/task-brief.template.md +2 -2
  92. package/runtime/validators/lib/fixtures.sh +30 -0
  93. package/runtime/validators/lib/runners.sh +1 -1
  94. package/runtime/validators/validate-implementation-plan-stages.py +211 -0
  95. package/runtime/validators/validate-run.py +121 -26
  96. package/runtime/validators/validate-workflow.sh +2 -2
  97. package/runtime/validators/validate_improvement_report.py +275 -0
  98. package/src/config.mjs +18 -0
  99. package/src/install.mjs +41 -14
  100. package/src/setup.mjs +133 -1
  101. package/src/uninstall.mjs +21 -1
package/src/install.mjs CHANGED
@@ -15,6 +15,10 @@ const SETTINGS_TEMPLATE_SRC_REL = ["templates", "reports", "settings.template.js
15
15
  // Destination under ~/.okstra/. Project-local .claude/settings.local.json symlinks here.
16
16
  const SETTINGS_TEMPLATE_DST_REL = ["templates", "settings.local.json"];
17
17
 
18
+ // okstra-managed CLAUDE.md template. Per-project <PROJECT>/.project-docs/okstra/CLAUDE.md
19
+ // symlinks here; <PROJECT>/CLAUDE.md gets an `@.project-docs/okstra/CLAUDE.md` import line.
20
+ const CLAUDE_MD_TEMPLATE_REL = ["templates", "okstra.CLAUDE.md"];
21
+
18
22
  const PYTHON_PACKAGES = ["okstra_project", "okstra_ctl", "okstra_token_usage", "okstra_vendor", "lib"];
19
23
  const BIN_ENTRYPOINTS = [
20
24
  "okstra.sh",
@@ -42,7 +46,7 @@ Usage:
42
46
  Effect (copy mode):
43
47
  ${"$HOME"}/.okstra/lib/python <- runtime/python
44
48
  ${"$HOME"}/.okstra/bin <- runtime/bin
45
- ${"$HOME"}/.okstra/templates <- runtime/templates (report.css / report.js / *.template.md)
49
+ ${"$HOME"}/.okstra/templates <- runtime/templates (report.css / report.js / *.template.md / okstra.CLAUDE.md)
46
50
  ${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
47
51
  ${"$HOME"}/.claude/skills/<name> <- runtime/skills/<name> (per skill)
48
52
  ${"$HOME"}/.claude/agents/<worker>.md <- runtime/agents/workers/<worker>.md
@@ -54,6 +58,7 @@ Effect (link mode):
54
58
  ${"$HOME"}/.okstra/lib/python/<pkg> -> <repo>/scripts/<pkg> (symlink)
55
59
  ${"$HOME"}/.okstra/bin/<name>.sh -> <repo>/scripts/<name>.sh
56
60
  ${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
61
+ ${"$HOME"}/.okstra/templates/okstra.CLAUDE.md -> <repo>/templates/okstra.CLAUDE.md
57
62
  ${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> (symlink dir)
58
63
  ${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md
59
64
  ${"$HOME"}/.okstra/dev-link <- <repo> path stamp
@@ -64,6 +69,11 @@ project-local <project>/.claude/settings.local.json that okstra-setup
64
69
  provisions, granting per-project Claude Code permissions for okstra
65
70
  worker wrapper scripts without modifying the user's global settings.
66
71
 
72
+ The okstra.CLAUDE.md file is the symlink target referenced by every
73
+ project-local <project>/.project-docs/okstra/CLAUDE.md that okstra-setup provisions;
74
+ <project>/CLAUDE.md gets an "@.project-docs/okstra/CLAUDE.md" import block so Claude
75
+ Code automatically picks up the okstra runtime guidance.
76
+
67
77
  Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ so
68
78
  that Claude Code's subagent discovery picks them up; they cannot live
69
79
  inside the package alone because the harness only scans ~/.claude/agents/
@@ -245,7 +255,8 @@ async function installLinkMode(repoPath, paths, opts) {
245
255
  const agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
246
256
  await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
247
257
 
248
- await installSettingsTemplate(repoAbs, paths, { mode: "link", dryRun, quiet });
258
+ await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, SETTINGS_TEMPLATE_DESCRIPTOR);
259
+ await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, CLAUDE_MD_TEMPLATE_DESCRIPTOR);
249
260
 
250
261
  if (!dryRun) {
251
262
  await writeFileAtomic(join(paths.home, "dev-link"), repoAbs + "\n", 0o644);
@@ -395,13 +406,14 @@ async function installAgentsLink(repoAbs, opts) {
395
406
  return { installed: names };
396
407
  }
397
408
 
398
- async function installSettingsTemplate(srcRoot, paths, opts) {
409
+ async function installNamedTemplate(srcRoot, paths, opts, descriptor) {
399
410
  const { mode, refresh = false, dryRun = false, quiet = false } = opts;
400
- const src = join(srcRoot, ...SETTINGS_TEMPLATE_SRC_REL);
401
- const dst = join(paths.home, ...SETTINGS_TEMPLATE_DST_REL);
411
+ const { srcRel, dstRel, label } = descriptor;
412
+ const src = join(srcRoot, ...srcRel);
413
+ const dst = join(paths.home, ...dstRel);
402
414
 
403
415
  if (!(await fileExists(src))) {
404
- if (!quiet) process.stdout.write(` settings template: source missing — skipped (${src})\n`);
416
+ if (!quiet) process.stdout.write(` ${label}: source missing — skipped (${src})\n`);
405
417
  return { installed: false };
406
418
  }
407
419
 
@@ -409,7 +421,7 @@ async function installSettingsTemplate(srcRoot, paths, opts) {
409
421
 
410
422
  if (mode === "link") {
411
423
  const action = await ensureSymlink(src, dst, { dryRun });
412
- if (!quiet) process.stdout.write(` settings template: ${action} (${dst} -> ${src})\n`);
424
+ if (!quiet) process.stdout.write(` ${label}: ${action} (${dst} -> ${src})\n`);
413
425
  return { installed: action !== "skipped" };
414
426
  }
415
427
 
@@ -426,7 +438,7 @@ async function installSettingsTemplate(srcRoot, paths, opts) {
426
438
  }
427
439
 
428
440
  if (!needsCopy) {
429
- if (!quiet) process.stdout.write(` settings template: skipped (hash match)\n`);
441
+ if (!quiet) process.stdout.write(` ${label}: skipped (hash match)\n`);
430
442
  return { installed: false };
431
443
  }
432
444
 
@@ -436,10 +448,22 @@ async function installSettingsTemplate(srcRoot, paths, opts) {
436
448
  const buf = await fs.readFile(src);
437
449
  await writeFileAtomic(dst, buf, 0o644);
438
450
  }
439
- if (!quiet) process.stdout.write(` settings template: copied -> ${dst}\n`);
451
+ if (!quiet) process.stdout.write(` ${label}: copied -> ${dst}\n`);
440
452
  return { installed: true };
441
453
  }
442
454
 
455
+ const SETTINGS_TEMPLATE_DESCRIPTOR = {
456
+ srcRel: SETTINGS_TEMPLATE_SRC_REL,
457
+ dstRel: SETTINGS_TEMPLATE_DST_REL,
458
+ label: "settings template",
459
+ };
460
+
461
+ const CLAUDE_MD_TEMPLATE_DESCRIPTOR = {
462
+ srcRel: CLAUDE_MD_TEMPLATE_REL,
463
+ dstRel: CLAUDE_MD_TEMPLATE_REL,
464
+ label: "claude.md template",
465
+ };
466
+
443
467
  async function installSkillsCopy(runtimeRoot, opts) {
444
468
  const { refresh, dryRun, quiet } = opts;
445
469
  const srcRoot = join(runtimeRoot, "skills");
@@ -553,10 +577,11 @@ export async function runInstall(args) {
553
577
  paths.bin,
554
578
  { refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o755 },
555
579
  );
556
- // templates/ tree — report.css / report.js / *.template.md are consumed at
557
- // runtime by okstra-render-report-views.py and final-report assembly. They
558
- // are NOT covered by installSettingsTemplate (which only handles the
559
- // editable settings.local.json sidecar), so without this step copy-mode
580
+ // templates/ tree — report.css / report.js / *.template.md / okstra.CLAUDE.md
581
+ // are consumed at runtime by okstra-render-report-views.py, final-report
582
+ // assembly, and the per-project .project-docs/okstra/CLAUDE.md symlink provisioned by
583
+ // setup. They are NOT covered by installNamedTemplate (which only handles
584
+ // the renamed settings.local.json sidecar), so without this step copy-mode
560
585
  // installs miss every asset other than that single file. See
561
586
  // okstra-render-report-views.py _TEMPLATES_DIRS for the lookup path.
562
587
  const templatesResult = await copyTreeIfChanged(
@@ -588,7 +613,9 @@ export async function runInstall(args) {
588
613
  const agentResult = await installAgentsCopy(runtimeRoot, opts);
589
614
  await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
590
615
 
591
- await installSettingsTemplate(runtimeRoot, paths, { mode: "copy", ...opts });
616
+ await installNamedTemplate(runtimeRoot, paths, { mode: "copy", ...opts }, SETTINGS_TEMPLATE_DESCRIPTOR);
617
+ // okstra.CLAUDE.md is already covered by the runtime/templates tree copy
618
+ // above (same src/dst path, no rename). No second call needed in copy mode.
592
619
 
593
620
  if (!opts.dryRun) {
594
621
  await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
package/src/setup.mjs CHANGED
@@ -293,9 +293,37 @@ export async function run(args) {
293
293
  );
294
294
  }
295
295
 
296
+ let claudeMd = { symlink: null, importInjected: false };
297
+ try {
298
+ claudeMd = await ensureProjectClaudeMd(projectRoot);
299
+ } catch (err) {
300
+ process.stderr.write(
301
+ `warning: failed to wire <PROJECT>/CLAUDE.md @ .project-docs/okstra/CLAUDE.md import — ` +
302
+ `Claude Code sessions in this project will not auto-load okstra guidance. (${err.message})\n`,
303
+ );
304
+ }
305
+
306
+ let agentsMdSymlink = null;
307
+ try {
308
+ agentsMdSymlink = await ensureProjectAgentsMd(projectRoot);
309
+ } catch (err) {
310
+ process.stderr.write(
311
+ `warning: failed to provision <PROJECT>/AGENTS.md symlink — ` +
312
+ `codex / aider sessions in this project will not auto-load okstra guidance. (${err.message})\n`,
313
+ );
314
+ }
315
+
296
316
  process.stdout.write(
297
317
  JSON.stringify(
298
- { ok: true, ...result, projectJsonPath, settingsLocalJson: settingsSymlink },
318
+ {
319
+ ok: true,
320
+ ...result,
321
+ projectJsonPath,
322
+ settingsLocalJson: settingsSymlink,
323
+ claudeMdSymlink: claudeMd.symlink,
324
+ claudeMdImportInjected: claudeMd.importInjected,
325
+ agentsMdSymlink,
326
+ },
299
327
  null,
300
328
  2,
301
329
  ) + "\n",
@@ -348,3 +376,107 @@ async function backupAndReplace(target, template) {
348
376
  await fs.rename(target, backup);
349
377
  await fs.symlink(template, target);
350
378
  }
379
+
380
+ const CLAUDE_MD_TEMPLATE_PATH = join(homedir(), ".okstra", "templates", "okstra.CLAUDE.md");
381
+ const CLAUDE_MD_SYMLINK_REL = join(".project-docs", "okstra", "CLAUDE.md");
382
+ const CLAUDE_MD_IMPORT_LINE = "@.project-docs/okstra/CLAUDE.md";
383
+ const CLAUDE_MD_MARKER_BEGIN = "<!-- okstra:claude-md:begin (managed by okstra setup — do not edit) -->";
384
+ const CLAUDE_MD_MARKER_END = "<!-- okstra:claude-md:end -->";
385
+
386
+ async function ensureProjectClaudeMd(projectRoot) {
387
+ try {
388
+ await fs.access(CLAUDE_MD_TEMPLATE_PATH);
389
+ } catch {
390
+ return { symlink: null, importInjected: false };
391
+ }
392
+
393
+ const symlink = await ensureClaudeMdSymlink(projectRoot);
394
+ const importInjected = await ensureClaudeMdImport(projectRoot);
395
+ return { symlink, importInjected };
396
+ }
397
+
398
+ async function ensureClaudeMdSymlink(projectRoot) {
399
+ const target = join(projectRoot, CLAUDE_MD_SYMLINK_REL);
400
+ await fs.mkdir(join(target, ".."), { recursive: true });
401
+
402
+ let existingStat;
403
+ try {
404
+ existingStat = await fs.lstat(target);
405
+ } catch {
406
+ existingStat = null;
407
+ }
408
+
409
+ if (existingStat?.isSymbolicLink()) {
410
+ const current = await fs.readlink(target);
411
+ const resolved = current.startsWith("/") ? current : join(target, "..", current);
412
+ if (resolved === CLAUDE_MD_TEMPLATE_PATH) return target;
413
+ await backupAndReplace(target, CLAUDE_MD_TEMPLATE_PATH);
414
+ return target;
415
+ }
416
+ if (existingStat) {
417
+ await backupAndReplace(target, CLAUDE_MD_TEMPLATE_PATH);
418
+ return target;
419
+ }
420
+
421
+ await fs.symlink(CLAUDE_MD_TEMPLATE_PATH, target);
422
+ return target;
423
+ }
424
+
425
+ async function ensureClaudeMdImport(projectRoot) {
426
+ const claudeMdPath = join(projectRoot, "CLAUDE.md");
427
+ const block = `${CLAUDE_MD_MARKER_BEGIN}\n${CLAUDE_MD_IMPORT_LINE}\n${CLAUDE_MD_MARKER_END}\n`;
428
+
429
+ let existing;
430
+ try {
431
+ existing = await fs.readFile(claudeMdPath, "utf8");
432
+ } catch (err) {
433
+ if (err.code !== "ENOENT") throw err;
434
+ await fs.writeFile(claudeMdPath, block, { mode: 0o644 });
435
+ return true;
436
+ }
437
+
438
+ if (existing.includes(CLAUDE_MD_MARKER_BEGIN) && existing.includes(CLAUDE_MD_MARKER_END)) {
439
+ return false;
440
+ }
441
+
442
+ const separator = blockSeparatorFor(existing);
443
+ await fs.writeFile(claudeMdPath, existing + separator + block, { mode: 0o644 });
444
+ return true;
445
+ }
446
+
447
+ function blockSeparatorFor(existing) {
448
+ if (existing.length === 0) return "";
449
+ if (existing.endsWith("\n\n")) return "";
450
+ if (existing.endsWith("\n")) return "\n";
451
+ return "\n\n";
452
+ }
453
+
454
+ async function ensureProjectAgentsMd(projectRoot) {
455
+ try {
456
+ await fs.access(CLAUDE_MD_TEMPLATE_PATH);
457
+ } catch {
458
+ return null;
459
+ }
460
+
461
+ const target = join(projectRoot, "AGENTS.md");
462
+
463
+ let existingStat;
464
+ try {
465
+ existingStat = await fs.lstat(target);
466
+ } catch {
467
+ existingStat = null;
468
+ }
469
+
470
+ if (existingStat?.isSymbolicLink()) {
471
+ const current = await fs.readlink(target);
472
+ const resolved = current.startsWith("/") ? current : join(target, "..", current);
473
+ if (resolved === CLAUDE_MD_TEMPLATE_PATH) return target;
474
+ return null;
475
+ }
476
+ if (existingStat) {
477
+ return null;
478
+ }
479
+
480
+ await fs.symlink(CLAUDE_MD_TEMPLATE_PATH, target);
481
+ return target;
482
+ }
package/src/uninstall.mjs CHANGED
@@ -11,14 +11,19 @@ const BIN_ENTRYPOINTS = [
11
11
  "okstra-central.sh",
12
12
  "okstra-token-usage.py",
13
13
  "okstra-error-log.py",
14
+ "okstra-render-report-views.py",
15
+ "okstra-render-final-report.py",
16
+ "okstra-wrapper-status.py",
14
17
  ];
15
18
 
16
19
  const FALLBACK_SKILL_NAMES = [
17
20
  "okstra-setup",
21
+ "okstra-brief",
18
22
  "okstra-run",
19
23
  "okstra-status",
20
24
  "okstra-history",
21
25
  "okstra-schedule",
26
+ "okstra-logs",
22
27
  "okstra-context-loader",
23
28
  "okstra-team-contract",
24
29
  "okstra-convergence",
@@ -44,7 +49,9 @@ const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.
44
49
  Usage:
45
50
  okstra uninstall Remove ~/.okstra/{lib, bin/<known>, version,
46
51
  dev-link, installed-skills.json,
47
- installed-agents.json} AND
52
+ installed-agents.json,
53
+ templates/settings.local.json,
54
+ templates/okstra.CLAUDE.md} AND
48
55
  ~/.claude/skills/<name> AND
49
56
  ~/.claude/agents/<worker>.md for every
50
57
  entry in the install manifests (fallback:
@@ -52,6 +59,12 @@ Usage:
52
59
  Preserves user data: recent.jsonl,
53
60
  active.jsonl, projects/, archive/,
54
61
  state.json, .locks/
62
+ Per-project artifacts created by 'okstra setup'
63
+ (<PROJECT>/.project-docs/okstra/CLAUDE.md symlink,
64
+ <PROJECT>/CLAUDE.md import block,
65
+ <PROJECT>/AGENTS.md symlink,
66
+ <PROJECT>/.claude/settings.local.json symlink)
67
+ are NOT touched — remove them manually if needed.
55
68
  okstra uninstall --purge Remove the entire ~/.okstra directory AND
56
69
  ~/.claude/skills/<okstra-*> AND
57
70
  ~/.claude/agents/<*-worker.md> (DESTRUCTIVE).
@@ -182,6 +195,13 @@ export async function runUninstall(args) {
182
195
  await removePath(join(paths.home, AGENTS_MANIFEST_REL), opts);
183
196
 
184
197
  await removePath(join(paths.home, "templates", "settings.local.json"), opts);
198
+ await removePath(join(paths.home, "templates", "okstra.CLAUDE.md"), opts);
199
+ // Per-project artifacts (<PROJECT>/.project-docs/okstra/CLAUDE.md symlink,
200
+ // <PROJECT>/AGENTS.md symlink, and the @.project-docs/okstra/CLAUDE.md import
201
+ // block inside <PROJECT>/CLAUDE.md) are NOT removed here — uninstall is
202
+ // machine-level and does not know which projects opted in. Symlinks will
203
+ // dangle until the user removes them manually or re-runs okstra install +
204
+ // okstra setup.
185
205
  // Remove templates/ if now empty.
186
206
  const templatesDir = join(paths.home, "templates");
187
207
  if (await pathExists(templatesDir)) {