okstra 0.34.1 → 0.36.1
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 +27 -19
- package/README.md +27 -19
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +353 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +30 -7
- package/runtime/agents/workers/claude-worker.md +31 -6
- package/runtime/agents/workers/codex-worker.md +37 -10
- package/runtime/agents/workers/gemini-worker.md +34 -7
- package/runtime/agents/workers/report-writer-worker.md +19 -10
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/launch.template.md +1 -0
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
- package/runtime/prompts/profiles/_implementation-executor.md +60 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -118
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +26 -20
- package/runtime/python/okstra_ctl/render.py +166 -207
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +299 -108
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/session.py +65 -7
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +102 -218
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-inspect/SKILL.md +581 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +8 -7
- package/runtime/skills/okstra-schedule/SKILL.md +14 -157
- package/runtime/skills/okstra-setup/SKILL.md +28 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/brief.template.md +204 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/schedule.template.md +12 -3
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +108 -0
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +27 -3
- package/runtime/skills/okstra-history/SKILL.md +0 -165
- package/runtime/skills/okstra-logs/SKILL.md +0 -173
- package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
- package/runtime/skills/okstra-status/SKILL.md +0 -246
- package/runtime/skills/okstra-time-summary/SKILL.md +0 -172
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
|
|
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
|
|
409
|
+
async function installNamedTemplate(srcRoot, paths, opts, descriptor) {
|
|
399
410
|
const { mode, refresh = false, dryRun = false, quiet = false } = opts;
|
|
400
|
-
const
|
|
401
|
-
const
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
|
557
|
-
// runtime by okstra-render-report-views.py
|
|
558
|
-
//
|
|
559
|
-
//
|
|
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
|
|
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
|
-
{
|
|
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,18 +11,27 @@ 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
|
-
"okstra-
|
|
20
|
-
"okstra-history",
|
|
23
|
+
"okstra-inspect",
|
|
21
24
|
"okstra-schedule",
|
|
22
25
|
"okstra-context-loader",
|
|
23
26
|
"okstra-team-contract",
|
|
24
27
|
"okstra-convergence",
|
|
25
28
|
"okstra-report-writer",
|
|
29
|
+
// Pre-merge names — kept so uninstall on an upgraded machine still cleans
|
|
30
|
+
// them out of ~/.claude/skills/ once the user runs `okstra uninstall`.
|
|
31
|
+
// Safe to drop after a few releases when nobody has them installed.
|
|
32
|
+
"okstra-status",
|
|
33
|
+
"okstra-history",
|
|
34
|
+
"okstra-logs",
|
|
26
35
|
"okstra-report-finder",
|
|
27
36
|
"okstra-time-summary",
|
|
28
37
|
];
|
|
@@ -44,7 +53,9 @@ const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.
|
|
|
44
53
|
Usage:
|
|
45
54
|
okstra uninstall Remove ~/.okstra/{lib, bin/<known>, version,
|
|
46
55
|
dev-link, installed-skills.json,
|
|
47
|
-
installed-agents.json
|
|
56
|
+
installed-agents.json,
|
|
57
|
+
templates/settings.local.json,
|
|
58
|
+
templates/okstra.CLAUDE.md} AND
|
|
48
59
|
~/.claude/skills/<name> AND
|
|
49
60
|
~/.claude/agents/<worker>.md for every
|
|
50
61
|
entry in the install manifests (fallback:
|
|
@@ -52,6 +63,12 @@ Usage:
|
|
|
52
63
|
Preserves user data: recent.jsonl,
|
|
53
64
|
active.jsonl, projects/, archive/,
|
|
54
65
|
state.json, .locks/
|
|
66
|
+
Per-project artifacts created by 'okstra setup'
|
|
67
|
+
(<PROJECT>/.project-docs/okstra/CLAUDE.md symlink,
|
|
68
|
+
<PROJECT>/CLAUDE.md import block,
|
|
69
|
+
<PROJECT>/AGENTS.md symlink,
|
|
70
|
+
<PROJECT>/.claude/settings.local.json symlink)
|
|
71
|
+
are NOT touched — remove them manually if needed.
|
|
55
72
|
okstra uninstall --purge Remove the entire ~/.okstra directory AND
|
|
56
73
|
~/.claude/skills/<okstra-*> AND
|
|
57
74
|
~/.claude/agents/<*-worker.md> (DESTRUCTIVE).
|
|
@@ -182,6 +199,13 @@ export async function runUninstall(args) {
|
|
|
182
199
|
await removePath(join(paths.home, AGENTS_MANIFEST_REL), opts);
|
|
183
200
|
|
|
184
201
|
await removePath(join(paths.home, "templates", "settings.local.json"), opts);
|
|
202
|
+
await removePath(join(paths.home, "templates", "okstra.CLAUDE.md"), opts);
|
|
203
|
+
// Per-project artifacts (<PROJECT>/.project-docs/okstra/CLAUDE.md symlink,
|
|
204
|
+
// <PROJECT>/AGENTS.md symlink, and the @.project-docs/okstra/CLAUDE.md import
|
|
205
|
+
// block inside <PROJECT>/CLAUDE.md) are NOT removed here — uninstall is
|
|
206
|
+
// machine-level and does not know which projects opted in. Symlinks will
|
|
207
|
+
// dangle until the user removes them manually or re-runs okstra install +
|
|
208
|
+
// okstra setup.
|
|
185
209
|
// Remove templates/ if now empty.
|
|
186
210
|
const templatesDir = join(paths.home, "templates");
|
|
187
211
|
if (await pathExists(templatesDir)) {
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: okstra-history
|
|
3
|
-
description: Use when the user asks to list past okstra runs, check execution history, review previous cross-verification results, or wants to re-run a past okstra task. Trigger words include "okstra history", "past runs", "run history", "re-run", "list tasks".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# OKSTRA History
|
|
7
|
-
|
|
8
|
-
## When to Use
|
|
9
|
-
|
|
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.
|
|
26
|
-
|
|
27
|
-
## Step 0: Verify okstra runtime + project setup
|
|
28
|
-
|
|
29
|
-
Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
|
|
30
|
-
|
|
31
|
-
1. `okstra ensure-installed`
|
|
32
|
-
If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
|
|
33
|
-
|
|
34
|
-
2. `okstra check-project --json`
|
|
35
|
-
Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
|
|
36
|
-
|
|
37
|
-
- `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
|
|
38
|
-
- `ok: true` → carry `projectRoot` as a literal string and use it to locate `.project-docs/okstra/discovery/task-catalog.json`.
|
|
39
|
-
|
|
40
|
-
Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
|
|
41
|
-
|
|
42
|
-
## Step 1: Read the Task Catalog
|
|
43
|
-
|
|
44
|
-
1. Read `.project-docs/okstra/discovery/task-catalog.json`.
|
|
45
|
-
2. Apply filters from user input (all optional, AND-combined):
|
|
46
|
-
- `--task-type <type>` → keep entries whose `taskType` matches.
|
|
47
|
-
- `--latest-run-status <status>` → keep entries whose `latestRunStatus` matches (e.g. `completed`, `contract-violated`, `error`).
|
|
48
|
-
- `--task-group <group>` → keep entries whose `taskGroup` matches.
|
|
49
|
-
3. Sort the surviving `tasks` array by `updatedAt` descending.
|
|
50
|
-
4. Page: default `--limit 20`. After printing the table, if rows were truncated, add `... <N> more (pass --limit <N> to see all)`.
|
|
51
|
-
5. Extract the following fields from each task:
|
|
52
|
-
|
|
53
|
-
| Field | Description |
|
|
54
|
-
|------|------|
|
|
55
|
-
| `taskKey` | Task identifier — always 3 colon-separated segments: `<project-id>:<task-group>:<task-id>` (see `parse_task_key` in `okstra_project/state.py`). |
|
|
56
|
-
| `taskType` | `requirements-discovery` / `error-analysis` / `implementation-planning` / `implementation` / `final-verification` / `release-handoff` |
|
|
57
|
-
| `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`). |
|
|
58
|
-
| `latestRunStatus` | Status of the most recent run (`completed`, `contract-violated`, `error`, ...) |
|
|
59
|
-
| `latestRunManifestPath` | Run-manifest path of the most recent run — feed this into Step 3 to re-run from the latest parameters |
|
|
60
|
-
| `updatedAt` | Last update time (ISO 8601) |
|
|
61
|
-
| `latestReportPath` | Latest report path |
|
|
62
|
-
| `latestResumeCommandPath` | Resume command path (Step 4) |
|
|
63
|
-
| `historyTimelinePath` | `<task-root>/history/timeline.json` (Step 2 reads from here) |
|
|
64
|
-
|
|
65
|
-
6. Output format:
|
|
66
|
-
|
|
67
|
-
```markdown
|
|
68
|
-
## okstra Task History — <project-id>
|
|
69
|
-
|
|
70
|
-
| # | Task Key | Type | currentStatus | latestRunStatus | Last Run | Report |
|
|
71
|
-
|---|----------|------|---------------|------------------|----------|--------|
|
|
72
|
-
| 1 | proj:group:id | error-analysis | completed | completed | 2026-04-05 22:59 | .project-docs/.../final-report-*.md |
|
|
73
|
-
| 2 | proj:group:id2 | final-verification | todo | error | 2026-04-04 15:30 | -- |
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Catalog absent — fallback
|
|
77
|
-
|
|
78
|
-
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.
|
|
79
|
-
|
|
80
|
-
1. Glob `<projectRoot>/.project-docs/okstra/tasks/*/*/task-manifest.json`.
|
|
81
|
-
2. For each manifest, read `taskKey`, `taskGroup`, `taskType`, `currentStatus`, `latestRunStatus`, `updatedAt`, `latestReportPath`, `latestResumeCommandPath`, `latestRunManifestPath`, `historyTimelinePath`.
|
|
82
|
-
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.`
|
|
83
|
-
4. Only if the glob yields zero manifests: respond `There is no okstra execution history yet.`
|
|
84
|
-
|
|
85
|
-
## Step 2: Run History by Task
|
|
86
|
-
|
|
87
|
-
When a user selects a specific task or requests detailed history:
|
|
88
|
-
|
|
89
|
-
1. Retrieve the `historyTimelinePath` path for the task from the task catalog.
|
|
90
|
-
2. Read the `runs` array from `timeline.json`.
|
|
91
|
-
3. Fields to extract from each run:
|
|
92
|
-
|
|
93
|
-
| Field | Description |
|
|
94
|
-
|------|------|
|
|
95
|
-
| `runTimestamp` | Execution time (ISO 8601) |
|
|
96
|
-
| `runDateTimeSegment` | YYYY-MM-DD_HH-MM-SS |
|
|
97
|
-
| `taskType` | `--task-type` argument value |
|
|
98
|
-
| `status` | Run status |
|
|
99
|
-
| `runManifestPath` | This run's `run-manifest-*.json` — feed into Step 3 to re-run from this specific run's parameters |
|
|
100
|
-
| `reportPath` | Final report path |
|
|
101
|
-
| `resumeCommandPath` | Resume Claude session for this run (Step 4) |
|
|
102
|
-
| `relatedTasks` | List of related task-keys |
|
|
103
|
-
|
|
104
|
-
4. Output format:
|
|
105
|
-
|
|
106
|
-
```markdown
|
|
107
|
-
## Runs for <task-key>
|
|
108
|
-
|
|
109
|
-
| # | Timestamp | Type | Status | Report |
|
|
110
|
-
|---|-----------|------|--------|--------|
|
|
111
|
-
| 1 | 2026-04-05 22:59 | error-analysis | completed | .../final-report-*.md |
|
|
112
|
-
| 2 | 2026-04-04 15:30 | error-analysis | error | -- |
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Step 3: Re-run (build a NEW run from an old run's parameters)
|
|
116
|
-
|
|
117
|
-
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.
|
|
118
|
-
|
|
119
|
-
1. Pick the source run-manifest: the `runManifestPath` from a Step 2 timeline entry, or the task's `latestRunManifestPath` from Step 1.
|
|
120
|
-
2. Read the run-manifest JSON and extract required arguments:
|
|
121
|
-
- `projectId` → `--project-id`
|
|
122
|
-
- `taskGroup` → `--task-group`
|
|
123
|
-
- `taskId` → `--task-id`
|
|
124
|
-
- `taskType` → `--task-type`
|
|
125
|
-
- `taskBriefPath` → `--task-brief`
|
|
126
|
-
3. Extract optional arguments (include only when present in the source manifest):
|
|
127
|
-
- `recommendedWorkers` → `--workers` (comma-separated)
|
|
128
|
-
- `relatedTasks` → `--related-tasks`
|
|
129
|
-
- model overrides → `--claude-model`, `--codex-model`, `--gemini-model` (when different from default)
|
|
130
|
-
- for `taskType: implementation`: `teamContract.executor.provider` → `--executor <claude|codex|gemini>` (when different from `claude`)
|
|
131
|
-
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:
|
|
132
|
-
- 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.
|
|
133
|
-
- 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.
|
|
134
|
-
5. Display the assembled command:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
okstra.sh \
|
|
138
|
-
--project-id <project-id> \
|
|
139
|
-
--task-group <task-group> \
|
|
140
|
-
--task-id <task-id> \
|
|
141
|
-
--task-type <task-type> \
|
|
142
|
-
--task-brief <brief-path> \
|
|
143
|
-
--workers <worker-list>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
6. Once the user confirms, execute it using the Bash tool.
|
|
147
|
-
|
|
148
|
-
## Step 4: Resume (continue an interrupted run)
|
|
149
|
-
|
|
150
|
-
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.
|
|
151
|
-
|
|
152
|
-
1. Read `latestResumeCommandPath` from the task catalog (Step 1) — or `resumeCommandPath` from a specific timeline entry (Step 2).
|
|
153
|
-
2. Verify the file exists on disk.
|
|
154
|
-
3. If present:
|
|
155
|
-
```bash
|
|
156
|
-
bash <resume-command-path>
|
|
157
|
-
```
|
|
158
|
-
4. If absent, report: `No resume script available for this run. Use Step 3 to start a fresh run instead.`
|
|
159
|
-
|
|
160
|
-
## Output Rules
|
|
161
|
-
|
|
162
|
-
- Display concisely in a table format
|
|
163
|
-
- Dates in `YYYY-MM-DD HH:MM` format
|
|
164
|
-
- Display status fields as-is from disk (`completed`, `contract-violated`, `todo`, `error`, empty, ...). Do not normalize or remap.
|
|
165
|
-
- Display `--` if no report is available
|