okstra 0.82.0 → 0.83.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 +7 -6
- package/README.md +6 -6
- package/docs/kr/architecture.md +8 -7
- package/docs/kr/cli.md +4 -4
- package/docs/kr/performance-improvement-plan-v2.md +14 -14
- package/docs/project-structure-overview.md +8 -9
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +504 -0
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +342 -0
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +258 -0
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +387 -0
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +749 -0
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +828 -0
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +490 -0
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +1 -1
- package/docs/task-process/implementation-planning.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/TODO.md +2 -0
- package/runtime/agents/workers/claude-worker.md +8 -8
- package/runtime/agents/workers/codex-worker.md +8 -8
- package/runtime/agents/workers/gemini-worker.md +8 -8
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/globals.sh +0 -1
- package/runtime/bin/okstra-wrapper-status.py +1 -1
- package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/python.md +2 -2
- package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/rust.md +1 -1
- package/runtime/{skills/okstra-coding-preflight/SKILL.md → prompts/coding-preflight/overview.md} +27 -38
- package/runtime/prompts/launch.template.md +5 -3
- package/runtime/{skills/okstra-context-loader/SKILL.md → prompts/lead/context-loader.md} +7 -14
- package/runtime/{skills/okstra-convergence/SKILL.md → prompts/lead/convergence.md} +12 -19
- package/runtime/{agents/SKILL.md → prompts/lead/okstra-lead-contract.md} +53 -59
- package/runtime/{skills/okstra-report-writer/SKILL.md → prompts/lead/report-writer.md} +12 -19
- package/runtime/{skills/okstra-team-contract/SKILL.md → prompts/lead/team-contract.md} +13 -19
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +2 -2
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +4 -4
- package/runtime/prompts/profiles/requirements-discovery.md +2 -2
- package/runtime/python/okstra_ctl/codex_dispatch.py +12 -61
- package/runtime/python/okstra_ctl/context_cost.py +14 -11
- package/runtime/python/okstra_ctl/dispatch_core.py +36 -13
- package/runtime/python/okstra_ctl/paths.py +27 -1
- package/runtime/python/okstra_ctl/render.py +62 -8
- package/runtime/python/okstra_ctl/run.py +5 -5
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +126 -0
- package/runtime/python/okstra_token_usage/claude.py +1 -1
- package/runtime/python/okstra_token_usage/collect.py +1 -1
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +2 -2
- package/runtime/validators/lib/validate-assets.sh +12 -4
- package/runtime/validators/validate-run.py +3 -3
- package/runtime/validators/validate_session_conformance.py +11 -11
- package/src/install.mjs +129 -98
- package/src/skill-catalog.mjs +35 -0
- package/src/uninstall.mjs +5 -0
- /package/runtime/{skills/okstra-coding-preflight/architecture → prompts/coding-preflight/architectures}/hexagonal.md +0 -0
- /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/clean-code.md +0 -0
- /package/runtime/{skills/okstra-coding-preflight/languages/nodejs.md → prompts/coding-preflight/frameworks/node-server.md} +0 -0
- /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/java.md +0 -0
- /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/javascript-typescript.md +0 -0
- /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/kotlin.md +0 -0
- /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/sql.md +0 -0
package/src/install.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { resolvePaths } from "./paths.mjs";
|
|
|
7
7
|
import { fileExists } from "./_proc.mjs";
|
|
8
8
|
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "./runtime-manifest.mjs";
|
|
9
9
|
import { normalizeRuntimeRequest, resolveRuntime } from "./runtime-resolver.mjs";
|
|
10
|
+
import { OBSOLETE_INTERNAL_SKILL_NAMES, USER_SKILL_NAMES } from "./skill-catalog.mjs";
|
|
10
11
|
|
|
11
12
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
12
13
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
@@ -41,6 +42,26 @@ const BIN_ENTRYPOINTS = [
|
|
|
41
42
|
"okstra-spawn-followups.py",
|
|
42
43
|
];
|
|
43
44
|
|
|
45
|
+
// Runtime prompt resources the launch prompt and routed coding-preflight pack
|
|
46
|
+
// depend on. Intentionally an exact allowlist, not a directory wildcard.
|
|
47
|
+
const REQUIRED_PROMPT_RESOURCE_FILES = Object.freeze([
|
|
48
|
+
["prompts", "lead", "okstra-lead-contract.md"],
|
|
49
|
+
["prompts", "lead", "context-loader.md"],
|
|
50
|
+
["prompts", "lead", "team-contract.md"],
|
|
51
|
+
["prompts", "lead", "convergence.md"],
|
|
52
|
+
["prompts", "lead", "report-writer.md"],
|
|
53
|
+
["prompts", "coding-preflight", "overview.md"],
|
|
54
|
+
["prompts", "coding-preflight", "clean-code.md"],
|
|
55
|
+
["prompts", "coding-preflight", "languages", "javascript-typescript.md"],
|
|
56
|
+
["prompts", "coding-preflight", "languages", "python.md"],
|
|
57
|
+
["prompts", "coding-preflight", "languages", "rust.md"],
|
|
58
|
+
["prompts", "coding-preflight", "languages", "java.md"],
|
|
59
|
+
["prompts", "coding-preflight", "languages", "kotlin.md"],
|
|
60
|
+
["prompts", "coding-preflight", "languages", "sql.md"],
|
|
61
|
+
["prompts", "coding-preflight", "frameworks", "node-server.md"],
|
|
62
|
+
["prompts", "coding-preflight", "architectures", "hexagonal.md"],
|
|
63
|
+
]);
|
|
64
|
+
|
|
44
65
|
const INSTALL_USAGE = `okstra install — install runtime into ~/.okstra
|
|
45
66
|
|
|
46
67
|
Usage:
|
|
@@ -57,12 +78,13 @@ Effect (copy mode):
|
|
|
57
78
|
${"$HOME"}/.okstra/lib/validators <- runtime/validators (stage/report structure validators)
|
|
58
79
|
${"$HOME"}/.okstra/bin <- runtime/bin
|
|
59
80
|
${"$HOME"}/.okstra/templates <- runtime/templates (report.css / report.js / *.template.md)
|
|
81
|
+
${"$HOME"}/.okstra/prompts <- runtime/prompts (lead contracts + coding-preflight pack)
|
|
60
82
|
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
61
83
|
${"$HOME"}/.claude/skills/<name> <- skills when ${"$HOME"}/.claude exists
|
|
62
84
|
${"$HOME"}/.codex/skills/<name> <- skills when ${"$HOME"}/.codex exists
|
|
63
85
|
${"$HOME"}/.omp/agent/skills/<name> <- skills fallback when Claude/Codex homes are absent
|
|
64
86
|
${"$HOME"}/.claude/agents/<worker>.md <- worker agents when ${"$HOME"}/.claude exists
|
|
65
|
-
${"$HOME"}/.okstra/installed-runtimes.json <-
|
|
87
|
+
${"$HOME"}/.okstra/installed-runtimes.json <- install request/resolution manifest
|
|
66
88
|
${"$HOME"}/.okstra/installed-skills.json <- target-aware manifest of installed skills
|
|
67
89
|
${"$HOME"}/.okstra/installed-agents.json <- manifest of installed workers
|
|
68
90
|
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
@@ -70,6 +92,7 @@ Effect (copy mode):
|
|
|
70
92
|
Effect (link mode):
|
|
71
93
|
${"$HOME"}/.okstra/lib/python/<pkg> -> <repo>/scripts/<pkg> (symlink)
|
|
72
94
|
${"$HOME"}/.okstra/bin/<name>.sh -> <repo>/scripts/<name>.sh
|
|
95
|
+
${"$HOME"}/.okstra/prompts -> <repo>/prompts
|
|
73
96
|
${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
|
|
74
97
|
${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.claude exists
|
|
75
98
|
${"$HOME"}/.codex/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.codex exists
|
|
@@ -86,6 +109,9 @@ worker wrapper scripts without modifying the user's global settings.
|
|
|
86
109
|
Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ only
|
|
87
110
|
when ${"$HOME"}/.claude exists, so Claude Code's subagent discovery can pick
|
|
88
111
|
them up. Codex/OMP skill targets receive skill markdown only.
|
|
112
|
+
|
|
113
|
+
Default auto runtime installation does not require host detection; it still
|
|
114
|
+
installs shared runtime files and any agent-home skill targets it finds.
|
|
89
115
|
`;
|
|
90
116
|
|
|
91
117
|
const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
@@ -125,6 +151,31 @@ export function validateInstallRuntime(value) {
|
|
|
125
151
|
return runtime;
|
|
126
152
|
}
|
|
127
153
|
|
|
154
|
+
function resolveInstallRuntime(runtime, command) {
|
|
155
|
+
const resolution = resolveRuntime({
|
|
156
|
+
requestedRuntime: runtime,
|
|
157
|
+
command,
|
|
158
|
+
env: process.env,
|
|
159
|
+
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
160
|
+
});
|
|
161
|
+
if (resolution.ok || runtime !== "auto") return resolution;
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
requestedRuntime: "auto",
|
|
165
|
+
resolvedRuntime: null,
|
|
166
|
+
host: "unknown",
|
|
167
|
+
confidence: "none",
|
|
168
|
+
reason: resolution.reason,
|
|
169
|
+
fallbackFrom: null,
|
|
170
|
+
warnings: [resolution.reason],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function formatInstallRuntimeLabel(runtime, resolution) {
|
|
175
|
+
if (resolution.resolvedRuntime) return `${runtime} -> ${resolution.resolvedRuntime}`;
|
|
176
|
+
return `${runtime} (host unresolved; installing target assets)`;
|
|
177
|
+
}
|
|
178
|
+
|
|
128
179
|
async function hashFile(path) {
|
|
129
180
|
const buf = await fs.readFile(path);
|
|
130
181
|
return createHash("sha256").update(buf).digest("hex");
|
|
@@ -139,6 +190,16 @@ async function dirExists(path) {
|
|
|
139
190
|
}
|
|
140
191
|
}
|
|
141
192
|
|
|
193
|
+
async function pathEntryExists(path) {
|
|
194
|
+
try {
|
|
195
|
+
await fs.lstat(path);
|
|
196
|
+
return true;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
if (err.code === "ENOENT") return false;
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
142
203
|
async function* walkFiles(root) {
|
|
143
204
|
let entries;
|
|
144
205
|
try {
|
|
@@ -209,34 +270,6 @@ async function copyTreeIfChanged(srcRoot, dstRoot, opts) {
|
|
|
209
270
|
return { copied, skipped, missingSource };
|
|
210
271
|
}
|
|
211
272
|
|
|
212
|
-
// Single-file variant of copyTreeIfChanged, used for the lead `okstra` skill
|
|
213
|
-
// whose source is one file (agents/SKILL.md) rather than a skill directory.
|
|
214
|
-
async function copyFileIfChanged(src, dst, opts) {
|
|
215
|
-
const { refresh = false, dryRun = false, mode } = opts ?? {};
|
|
216
|
-
let srcBuf;
|
|
217
|
-
try {
|
|
218
|
-
srcBuf = await fs.readFile(src);
|
|
219
|
-
} catch {
|
|
220
|
-
return { copied: 0, skipped: 0, missingSource: true };
|
|
221
|
-
}
|
|
222
|
-
let needsCopy = refresh;
|
|
223
|
-
if (!needsCopy) {
|
|
224
|
-
try {
|
|
225
|
-
const [srcHash, dstHash] = await Promise.all([hashFile(src), hashFile(dst)]);
|
|
226
|
-
needsCopy = srcHash !== dstHash;
|
|
227
|
-
} catch {
|
|
228
|
-
needsCopy = true;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (!needsCopy) return { copied: 0, skipped: 1, missingSource: false };
|
|
232
|
-
if (dryRun) {
|
|
233
|
-
process.stdout.write(`[dry-run] copy ${src} -> ${dst}\n`);
|
|
234
|
-
return { copied: 1, skipped: 0, missingSource: false };
|
|
235
|
-
}
|
|
236
|
-
await writeFileAtomic(dst, srcBuf, mode);
|
|
237
|
-
return { copied: 1, skipped: 0, missingSource: false };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
273
|
async function ensureSymlink(target, linkPath, opts) {
|
|
241
274
|
const { dryRun = false } = opts ?? {};
|
|
242
275
|
try {
|
|
@@ -313,6 +346,15 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
313
346
|
const action = await ensureSymlink(src, dst, { dryRun });
|
|
314
347
|
if (!quiet) process.stdout.write(` bin/${name}: ${action}\n`);
|
|
315
348
|
}
|
|
349
|
+
const promptsSrc = join(repoAbs, "prompts");
|
|
350
|
+
const promptsDst = join(paths.home, "prompts");
|
|
351
|
+
if (await dirExists(promptsSrc)) {
|
|
352
|
+
const action = await ensureSymlink(promptsSrc, promptsDst, { dryRun });
|
|
353
|
+
if (!quiet) process.stdout.write(` prompts: ${action}\n`);
|
|
354
|
+
} else if (!quiet) {
|
|
355
|
+
process.stdout.write(" prompts: missing in repo — skipped\n");
|
|
356
|
+
}
|
|
357
|
+
|
|
316
358
|
|
|
317
359
|
const skillTargets = await detectSkillTargets();
|
|
318
360
|
const installedSkillTargets = await installSkillTargetsLink(repoAbs, skillTargets, { dryRun, quiet });
|
|
@@ -348,16 +390,7 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
348
390
|
);
|
|
349
391
|
}
|
|
350
392
|
return 0;
|
|
351
|
-
}
|
|
352
393
|
|
|
353
|
-
async function listSkillDirs(skillsRoot) {
|
|
354
|
-
try {
|
|
355
|
-
const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
|
356
|
-
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
357
|
-
} catch (err) {
|
|
358
|
-
if (err.code === "ENOENT") return [];
|
|
359
|
-
throw err;
|
|
360
|
-
}
|
|
361
394
|
}
|
|
362
395
|
|
|
363
396
|
async function detectSkillTargets() {
|
|
@@ -584,15 +617,12 @@ const SETTINGS_TEMPLATE_DESCRIPTOR = {
|
|
|
584
617
|
label: "settings template",
|
|
585
618
|
};
|
|
586
619
|
|
|
587
|
-
const LEAD_SKILL_NAME = "okstra";
|
|
588
|
-
|
|
589
620
|
async function installSkillsCopy(runtimeRoot, target, opts) {
|
|
590
621
|
const { refresh, dryRun, quiet } = opts;
|
|
591
622
|
const srcRoot = join(runtimeRoot, "skills");
|
|
592
|
-
const names = await listSkillDirs(srcRoot);
|
|
593
623
|
let copied = 0;
|
|
594
624
|
let skipped = 0;
|
|
595
|
-
for (const name of
|
|
625
|
+
for (const name of USER_SKILL_NAMES) {
|
|
596
626
|
const r = await copyTreeIfChanged(
|
|
597
627
|
join(srcRoot, name),
|
|
598
628
|
join(target.skillsDir, name),
|
|
@@ -601,65 +631,48 @@ async function installSkillsCopy(runtimeRoot, target, opts) {
|
|
|
601
631
|
copied += r.copied;
|
|
602
632
|
skipped += r.skipped;
|
|
603
633
|
}
|
|
604
|
-
const
|
|
634
|
+
const pruned = await pruneObsoleteInternalSkills(target, opts);
|
|
605
635
|
if (!quiet) {
|
|
606
|
-
const totalCopied = copied + lead.copied;
|
|
607
|
-
const totalSkipped = skipped + lead.skipped;
|
|
608
|
-
const totalSkills = names.length + lead.installed.length;
|
|
609
636
|
process.stdout.write(
|
|
610
|
-
` ${target.provider} skills: copied=${
|
|
637
|
+
` ${target.provider} skills: copied=${copied} skipped=${skipped} pruned=${pruned} -> ${target.skillsDir}/ (${USER_SKILL_NAMES.length} skills)\n`,
|
|
611
638
|
);
|
|
612
639
|
}
|
|
613
|
-
return { ...target, installed: [...
|
|
640
|
+
return { ...target, installed: [...USER_SKILL_NAMES] };
|
|
614
641
|
}
|
|
615
642
|
|
|
616
643
|
async function installSkillsLink(repoAbs, target, opts) {
|
|
617
644
|
const { dryRun, quiet } = opts;
|
|
618
645
|
const srcRoot = join(repoAbs, "skills");
|
|
619
|
-
const names = await listSkillDirs(srcRoot);
|
|
620
646
|
if (!dryRun) await fs.mkdir(target.skillsDir, { recursive: true });
|
|
621
|
-
for (const name of
|
|
647
|
+
for (const name of USER_SKILL_NAMES) {
|
|
622
648
|
const src = join(srcRoot, name);
|
|
623
649
|
const dst = join(target.skillsDir, name);
|
|
624
650
|
const action = await ensureSymlink(src, dst, { dryRun });
|
|
625
651
|
if (!quiet) process.stdout.write(` ${target.provider} skills/${name}: ${action}\n`);
|
|
626
652
|
}
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async function installLeadSkillCopy(runtimeRoot, target, opts) {
|
|
632
|
-
const { refresh, dryRun, quiet } = opts;
|
|
633
|
-
const src = join(runtimeRoot, "agents", "SKILL.md");
|
|
634
|
-
const dst = join(target.skillsDir, LEAD_SKILL_NAME, "SKILL.md");
|
|
635
|
-
const r = await copyFileIfChanged(src, dst, { refresh, dryRun, mode: 0o644 });
|
|
636
|
-
if (r.missingSource) {
|
|
637
|
-
if (!quiet) {
|
|
638
|
-
process.stdout.write(
|
|
639
|
-
` ${target.provider} skills/okstra: runtime/agents/SKILL.md missing — skipped\n`,
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
return { installed: [], copied: 0, skipped: 0 };
|
|
653
|
+
const pruned = await pruneObsoleteInternalSkills(target, opts);
|
|
654
|
+
if (!quiet && pruned > 0) {
|
|
655
|
+
process.stdout.write(` ${target.provider} obsolete internal skills: pruned=${pruned}\n`);
|
|
643
656
|
}
|
|
644
|
-
return { installed: [
|
|
657
|
+
return { ...target, installed: [...USER_SKILL_NAMES] };
|
|
645
658
|
}
|
|
646
659
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
660
|
+
// Remove former internal skill directories left by older installs. Exact-name
|
|
661
|
+
// only (OBSOLETE_INTERNAL_SKILL_NAMES) — those contracts now ship as runtime
|
|
662
|
+
// resources under ~/.okstra/prompts/, not as agent skills.
|
|
663
|
+
async function pruneObsoleteInternalSkills(target, opts) {
|
|
664
|
+
let pruned = 0;
|
|
665
|
+
for (const name of OBSOLETE_INTERNAL_SKILL_NAMES) {
|
|
666
|
+
const path = join(target.skillsDir, name);
|
|
667
|
+
if (!(await pathEntryExists(path))) continue;
|
|
668
|
+
pruned += 1;
|
|
669
|
+
if (opts.dryRun) {
|
|
670
|
+
process.stdout.write(`[dry-run] rm ${path}\n`);
|
|
671
|
+
continue;
|
|
655
672
|
}
|
|
656
|
-
|
|
673
|
+
await fs.rm(path, { recursive: true, force: true });
|
|
657
674
|
}
|
|
658
|
-
|
|
659
|
-
if (!dryRun) await fs.mkdir(join(target.skillsDir, LEAD_SKILL_NAME), { recursive: true });
|
|
660
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
661
|
-
if (!quiet) process.stdout.write(` ${target.provider} skills/okstra/SKILL.md: ${action}\n`);
|
|
662
|
-
return [LEAD_SKILL_NAME];
|
|
675
|
+
return pruned;
|
|
663
676
|
}
|
|
664
677
|
|
|
665
678
|
async function installSkillTargetsCopy(runtimeRoot, targets, opts) {
|
|
@@ -725,17 +738,11 @@ export async function runInstall(args) {
|
|
|
725
738
|
const pkgRoot = getPackageRoot();
|
|
726
739
|
const runtimeRoot = join(pkgRoot, "runtime");
|
|
727
740
|
const paths = await resolvePaths();
|
|
728
|
-
const runtimeResolution =
|
|
729
|
-
|
|
730
|
-
command: "install",
|
|
731
|
-
env: process.env,
|
|
732
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
733
|
-
});
|
|
734
|
-
if (!runtimeResolution.ok) {
|
|
741
|
+
const runtimeResolution = resolveInstallRuntime(opts.runtime, "install");
|
|
742
|
+
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
735
743
|
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
736
744
|
return 2;
|
|
737
745
|
}
|
|
738
|
-
const resolvedRuntime = runtimeResolution.resolvedRuntime;
|
|
739
746
|
|
|
740
747
|
if (!opts.dryRun) {
|
|
741
748
|
await fs.mkdir(paths.home, { recursive: true });
|
|
@@ -754,7 +761,7 @@ export async function runInstall(args) {
|
|
|
754
761
|
|
|
755
762
|
if (!opts.quiet) {
|
|
756
763
|
process.stdout.write(`installing okstra runtime (package ${paths.package})\n`);
|
|
757
|
-
process.stdout.write(` runtime: ${opts.runtime
|
|
764
|
+
process.stdout.write(` runtime: ${formatInstallRuntimeLabel(opts.runtime, runtimeResolution)}\n`);
|
|
758
765
|
process.stdout.write(` source: ${runtimeRoot}\n`);
|
|
759
766
|
process.stdout.write(` home: ${paths.home}\n`);
|
|
760
767
|
}
|
|
@@ -806,6 +813,17 @@ export async function runInstall(args) {
|
|
|
806
813
|
join(paths.home, "lib", "validators"),
|
|
807
814
|
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o755 },
|
|
808
815
|
);
|
|
816
|
+
// prompts/ tree — lead/internal operating contracts (prompts/lead/*.md) and
|
|
817
|
+
// the coding-preflight resource pack (prompts/coding-preflight/*) are read at
|
|
818
|
+
// runtime by the lead and executor via absolute ~/.okstra/prompts/ paths
|
|
819
|
+
// (paths.py OKSTRA_LEAD_CONTRACT_PATH / OKSTRA_CODING_PREFLIGHT_DIR). Run-prep
|
|
820
|
+
// reads prompts/profiles + launch.template from the package runtime dir, but
|
|
821
|
+
// the lead's runtime Read needs them under the home tree like templates.
|
|
822
|
+
const promptsResult = await copyTreeIfChanged(
|
|
823
|
+
join(runtimeRoot, "prompts"),
|
|
824
|
+
join(paths.home, "prompts"),
|
|
825
|
+
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
|
|
826
|
+
);
|
|
809
827
|
|
|
810
828
|
if (!opts.quiet) {
|
|
811
829
|
summarise("python", pythonResult, paths.pythonpath);
|
|
@@ -813,6 +831,7 @@ export async function runInstall(args) {
|
|
|
813
831
|
summarise("templates", templatesResult, join(paths.home, "templates"));
|
|
814
832
|
summarise("schemas", schemasResult, join(paths.home, "schemas"));
|
|
815
833
|
summarise("validators", validatorsResult, join(paths.home, "lib", "validators"));
|
|
834
|
+
summarise("prompts", promptsResult, join(paths.home, "prompts"));
|
|
816
835
|
}
|
|
817
836
|
|
|
818
837
|
if (pythonResult.missingSource && binResult.missingSource) {
|
|
@@ -835,6 +854,11 @@ export async function runInstall(args) {
|
|
|
835
854
|
"warning: runtime/validators is empty. stage-structure / report validation will use stale or missing validators — re-run the build step.\n",
|
|
836
855
|
);
|
|
837
856
|
}
|
|
857
|
+
if (promptsResult.missingSource) {
|
|
858
|
+
process.stderr.write(
|
|
859
|
+
"warning: runtime/prompts is empty. lead contracts + coding-preflight resources will be missing — re-run the build step.\n",
|
|
860
|
+
);
|
|
861
|
+
}
|
|
838
862
|
|
|
839
863
|
const skillTargets = await detectSkillTargets();
|
|
840
864
|
const installedSkillTargets = await installSkillTargetsCopy(runtimeRoot, skillTargets, opts);
|
|
@@ -873,7 +897,7 @@ export async function runInstall(args) {
|
|
|
873
897
|
async function skillTargetDriftReasons(targets) {
|
|
874
898
|
const reasons = [];
|
|
875
899
|
for (const target of targets) {
|
|
876
|
-
for (const name of
|
|
900
|
+
for (const name of USER_SKILL_NAMES) {
|
|
877
901
|
const skillPath = join(target.skillsDir, name, "SKILL.md");
|
|
878
902
|
if (!(await fileExists(skillPath))) {
|
|
879
903
|
reasons.push(`missing ${skillPath}`);
|
|
@@ -883,6 +907,15 @@ async function skillTargetDriftReasons(targets) {
|
|
|
883
907
|
return reasons;
|
|
884
908
|
}
|
|
885
909
|
|
|
910
|
+
async function promptResourceDriftReasons(paths) {
|
|
911
|
+
const reasons = [];
|
|
912
|
+
for (const parts of REQUIRED_PROMPT_RESOURCE_FILES) {
|
|
913
|
+
const path = join(paths.home, ...parts);
|
|
914
|
+
if (!(await fileExists(path))) reasons.push(`missing ${path}`);
|
|
915
|
+
}
|
|
916
|
+
return reasons;
|
|
917
|
+
}
|
|
918
|
+
|
|
886
919
|
async function agentDriftReasons(paths) {
|
|
887
920
|
const reasons = [];
|
|
888
921
|
const workersDir = join(paths.agents, "workers");
|
|
@@ -949,13 +982,8 @@ export async function runEnsureInstalled(args) {
|
|
|
949
982
|
return 2;
|
|
950
983
|
}
|
|
951
984
|
const paths = await resolvePaths();
|
|
952
|
-
const runtimeResolution =
|
|
953
|
-
|
|
954
|
-
command: "ensure-installed",
|
|
955
|
-
env: process.env,
|
|
956
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
957
|
-
});
|
|
958
|
-
if (!runtimeResolution.ok) {
|
|
985
|
+
const runtimeResolution = resolveInstallRuntime(opts.runtime, "ensure-installed");
|
|
986
|
+
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
959
987
|
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
960
988
|
return 2;
|
|
961
989
|
}
|
|
@@ -970,6 +998,9 @@ export async function runEnsureInstalled(args) {
|
|
|
970
998
|
if (!(await fileExists(join(paths.home, "schemas", "final-report-v1.0.schema.json")))) {
|
|
971
999
|
reasons.push(`missing ${join(paths.home, "schemas", "final-report-v1.0.schema.json")}`);
|
|
972
1000
|
}
|
|
1001
|
+
for (const reason of await promptResourceDriftReasons(paths)) {
|
|
1002
|
+
reasons.push(reason);
|
|
1003
|
+
}
|
|
973
1004
|
const skillTargets = await detectSkillTargets();
|
|
974
1005
|
for (const reason of await skillTargetDriftReasons(skillTargets)) {
|
|
975
1006
|
reasons.push(reason);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Single source of truth for which okstra skills are user-facing and which
|
|
2
|
+
// former internal skill directories must be pruned from agent skill homes on
|
|
3
|
+
// (re)install. The Claude plugin manifest (.claude-plugin/plugin.json) and the
|
|
4
|
+
// installer both derive their skill list from here, so the public surface
|
|
5
|
+
// cannot drift between discovery and install.
|
|
6
|
+
|
|
7
|
+
export const USER_SKILL_NAMES = Object.freeze([
|
|
8
|
+
"okstra-setup",
|
|
9
|
+
"okstra-brief",
|
|
10
|
+
"okstra-run",
|
|
11
|
+
"okstra-memory",
|
|
12
|
+
"okstra-inspect",
|
|
13
|
+
"okstra-schedule",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// Names okstra used to install as skills before lead/internal contracts moved
|
|
17
|
+
// into the runtime resource tree (~/.okstra/prompts/). Install prunes these
|
|
18
|
+
// exact directories from each target skill home so upgrades do not leave the
|
|
19
|
+
// old internal skills behind. Exact-name only — never wildcard.
|
|
20
|
+
export const OBSOLETE_INTERNAL_SKILL_NAMES = Object.freeze([
|
|
21
|
+
"okstra",
|
|
22
|
+
"okstra-context-loader",
|
|
23
|
+
"okstra-team-contract",
|
|
24
|
+
"okstra-convergence",
|
|
25
|
+
"okstra-report-writer",
|
|
26
|
+
"okstra-coding-preflight",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export function userSkillNames() {
|
|
30
|
+
return [...USER_SKILL_NAMES];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function obsoleteInternalSkillNames() {
|
|
34
|
+
return [...OBSOLETE_INTERNAL_SKILL_NAMES];
|
|
35
|
+
}
|
package/src/uninstall.mjs
CHANGED
|
@@ -206,6 +206,11 @@ export async function runUninstall(args) {
|
|
|
206
206
|
// never removed it, leaving a stale final-report schema behind.
|
|
207
207
|
await removePath(join(paths.home, "schemas"), opts);
|
|
208
208
|
|
|
209
|
+
// prompts/ tree — installed by copy mode (runtime/prompts): lead contracts
|
|
210
|
+
// + coding-preflight resource pack. Remove so an upgrade never serves stale
|
|
211
|
+
// operating contracts.
|
|
212
|
+
await removePath(join(paths.home, "prompts"), opts);
|
|
213
|
+
|
|
209
214
|
// Remove the skills we own. Manifest v2 records every target root; legacy
|
|
210
215
|
// manifests fall back to Claude-only names when the runtime manifest says
|
|
211
216
|
// Claude assets may have been installed.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/java.md
RENAMED
|
File without changes
|
|
File without changes
|
/package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/kotlin.md
RENAMED
|
File without changes
|
/package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/sql.md
RENAMED
|
File without changes
|