okstra 0.76.0 → 0.78.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 +5 -3
- package/README.md +5 -3
- package/bin/okstra +1 -117
- package/docs/contributor-change-matrix.md +12 -0
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +22 -5
- package/docs/pr-template-usage.md +3 -3
- package/docs/project-structure-overview.md +1 -1
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +1 -1
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +493 -0
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +358 -0
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +134 -0
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +284 -0
- package/package.json +5 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/DO_NOT_EDIT.md +21 -0
- package/runtime/agents/SKILL.md +3 -0
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +6 -4
- package/runtime/bin/okstra-trace-cleanup.sh +16 -12
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/launch.template.md +4 -13
- package/runtime/prompts/profiles/error-analysis.md +6 -0
- package/runtime/prompts/profiles/forbidden-actions.json +69 -0
- package/runtime/prompts/profiles/implementation.md +1 -8
- package/runtime/prompts/profiles/improvement-discovery.md +5 -0
- package/runtime/prompts/profiles/release-handoff.md +3 -17
- package/runtime/python/okstra_ctl/analysis_packet.py +17 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +1552 -0
- package/runtime/python/okstra_ctl/context_cost.py +1 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +897 -0
- package/runtime/python/okstra_ctl/doctor.py +292 -0
- package/runtime/python/okstra_ctl/lead_events.py +129 -0
- package/runtime/python/okstra_ctl/lead_runtime.py +72 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/pr_template.py +1 -1
- package/runtime/python/okstra_ctl/render.py +211 -29
- package/runtime/python/okstra_ctl/run.py +89 -18
- package/runtime/python/okstra_ctl/team.py +267 -0
- package/runtime/python/okstra_ctl/tmux.py +181 -10
- package/runtime/python/okstra_ctl/workflow.py +30 -71
- package/runtime/python/okstra_ctl/wrapper_status.py +55 -0
- package/runtime/python/okstra_token_usage/codex.py +12 -7
- package/runtime/python/okstra_token_usage/collect.py +243 -54
- package/runtime/python/okstra_token_usage/gemini.py +12 -7
- package/runtime/schemas/final-report-v1.0.schema.json +3 -1
- package/runtime/skills/okstra-convergence/SKILL.md +3 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +3 -0
- package/runtime/skills/okstra-setup/SKILL.md +1 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +12 -0
- package/runtime/validators/forbidden_actions.py +135 -0
- package/runtime/validators/validate-run.py +112 -22
- package/runtime/validators/validate_session_conformance.py +127 -5
- package/src/cli-registry.mjs +277 -0
- package/src/codex-dispatch.mjs +70 -0
- package/src/codex-run.mjs +68 -0
- package/src/doctor.mjs +130 -5
- package/src/install.mjs +123 -26
- package/src/paths.mjs +17 -3
- package/src/render-bundle.mjs +2 -0
- package/src/runtime-manifest.mjs +29 -0
- package/src/team.mjs +63 -0
- package/src/uninstall.mjs +27 -4
- /package/runtime/{skills/okstra-run/templates → templates/prd}/pr-body.template.md +0 -0
package/src/install.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { join, relative, resolve as resolveAbs } from "node:path";
|
|
|
5
5
|
import { getPackageRoot } from "./version.mjs";
|
|
6
6
|
import { resolvePaths } from "./paths.mjs";
|
|
7
7
|
import { fileExists } from "./_proc.mjs";
|
|
8
|
+
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "./runtime-manifest.mjs";
|
|
8
9
|
|
|
9
10
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
10
11
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
@@ -20,6 +21,7 @@ const PYTHON_PACKAGES = ["okstra_project", "okstra_ctl", "okstra_token_usage", "
|
|
|
20
21
|
const BIN_ENTRYPOINTS = [
|
|
21
22
|
"okstra.sh",
|
|
22
23
|
"okstra-codex-exec.sh",
|
|
24
|
+
"okstra-claude-exec.sh",
|
|
23
25
|
"okstra-gemini-exec.sh",
|
|
24
26
|
"okstra-trace-cleanup.sh",
|
|
25
27
|
"okstra-team-reconcile.sh",
|
|
@@ -36,6 +38,7 @@ const INSTALL_USAGE = `okstra install — install runtime into ~/.okstra
|
|
|
36
38
|
|
|
37
39
|
Usage:
|
|
38
40
|
okstra install Install/update runtime (copy mode)
|
|
41
|
+
okstra install --runtime <claude-code|codex|external|all>
|
|
39
42
|
okstra install --link <p> Dev only: symlink ~/.okstra/{lib/python,bin}
|
|
40
43
|
to the source repo at <p>. agents/ resolves
|
|
41
44
|
to <p>/agents. Not advertised to end users.
|
|
@@ -50,6 +53,7 @@ Effect (copy mode):
|
|
|
50
53
|
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
51
54
|
${"$HOME"}/.claude/skills/<name> <- runtime/skills/<name> (per skill)
|
|
52
55
|
${"$HOME"}/.claude/agents/<worker>.md <- runtime/agents/workers/<worker>.md
|
|
56
|
+
${"$HOME"}/.okstra/installed-runtimes.json <- manifest of installed runtime adapters
|
|
53
57
|
${"$HOME"}/.okstra/installed-skills.json <- manifest of installed skills
|
|
54
58
|
${"$HOME"}/.okstra/installed-agents.json <- manifest of installed workers
|
|
55
59
|
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
@@ -78,11 +82,37 @@ const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
|
78
82
|
|
|
79
83
|
Usage:
|
|
80
84
|
okstra ensure-installed Verify install; reinstall if version/files drift
|
|
85
|
+
okstra ensure-installed --runtime <claude-code|codex|external|all>
|
|
81
86
|
okstra ensure-installed -q Same, but suppress success output
|
|
82
87
|
|
|
83
88
|
Skills call this on every run. Returns 0 quickly when the install is fresh.
|
|
84
89
|
`;
|
|
85
90
|
|
|
91
|
+
const INSTALL_RUNTIMES = new Set(["claude-code", "codex", "external", "all"]);
|
|
92
|
+
|
|
93
|
+
export function runtimeIncludesClaudeAssets(runtime) {
|
|
94
|
+
return runtime === "claude-code" || runtime === "all";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readRuntimeFlag(args, index) {
|
|
98
|
+
const arg = args[index];
|
|
99
|
+
if (arg.startsWith("--runtime=")) {
|
|
100
|
+
return { value: arg.slice("--runtime=".length), nextIndex: index };
|
|
101
|
+
}
|
|
102
|
+
const next = args[index + 1];
|
|
103
|
+
if (!next || next.startsWith("--")) {
|
|
104
|
+
throw new Error("--runtime requires one of: claude-code, codex, external, all");
|
|
105
|
+
}
|
|
106
|
+
return { value: next, nextIndex: index + 1 };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function validateInstallRuntime(value) {
|
|
110
|
+
if (!INSTALL_RUNTIMES.has(value)) {
|
|
111
|
+
throw new Error(`unknown runtime '${value}' (expected: claude-code, codex, external, all)`);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
async function hashFile(path) {
|
|
87
117
|
const buf = await fs.readFile(path);
|
|
88
118
|
return createHash("sha256").update(buf).digest("hex");
|
|
@@ -223,7 +253,7 @@ async function ensureSymlink(target, linkPath, opts) {
|
|
|
223
253
|
}
|
|
224
254
|
|
|
225
255
|
async function installLinkMode(repoPath, paths, opts) {
|
|
226
|
-
const { dryRun, quiet } = opts;
|
|
256
|
+
const { dryRun, quiet, runtime } = opts;
|
|
227
257
|
const repoAbs = resolveAbs(repoPath);
|
|
228
258
|
|
|
229
259
|
if (!(await dirExists(repoAbs))) {
|
|
@@ -240,6 +270,7 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
240
270
|
|
|
241
271
|
if (!quiet) {
|
|
242
272
|
process.stdout.write(`installing okstra runtime in LINK mode (package ${paths.package})\n`);
|
|
273
|
+
process.stdout.write(` runtime: ${runtime}\n`);
|
|
243
274
|
process.stdout.write(` repo: ${repoAbs}\n`);
|
|
244
275
|
process.stdout.write(` home: ${paths.home}\n`);
|
|
245
276
|
}
|
|
@@ -271,14 +302,19 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
271
302
|
if (!quiet) process.stdout.write(` bin/${name}: ${action}\n`);
|
|
272
303
|
}
|
|
273
304
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
305
|
+
if (runtimeIncludesClaudeAssets(runtime)) {
|
|
306
|
+
const skillResult = await installSkillsLink(repoAbs, { dryRun, quiet });
|
|
307
|
+
const leadSkillResult = await installLeadSkillLink(repoAbs, { dryRun, quiet });
|
|
308
|
+
await writeSkillsManifest(paths.home, [...skillResult.installed, ...leadSkillResult], { dryRun });
|
|
277
309
|
|
|
278
|
-
|
|
279
|
-
|
|
310
|
+
const agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
|
|
311
|
+
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
|
|
312
|
+
} else if (!quiet) {
|
|
313
|
+
process.stdout.write(" claude assets: skipped for runtime=codex\n");
|
|
314
|
+
}
|
|
280
315
|
|
|
281
316
|
await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
317
|
+
await writeRuntimeManifest(paths.home, runtime, { dryRun });
|
|
282
318
|
|
|
283
319
|
if (!dryRun) {
|
|
284
320
|
await writeFileAtomic(join(paths.home, "dev-link"), repoAbs + "\n", 0o644);
|
|
@@ -345,6 +381,20 @@ async function writeAgentsManifest(home, names, opts) {
|
|
|
345
381
|
);
|
|
346
382
|
}
|
|
347
383
|
|
|
384
|
+
async function writeRuntimeManifest(home, runtime, opts) {
|
|
385
|
+
const { dryRun = false } = opts ?? {};
|
|
386
|
+
const data = buildRuntimeManifest(runtime);
|
|
387
|
+
if (dryRun) {
|
|
388
|
+
process.stdout.write(`[dry-run] write runtime manifest: ${data.runtimes.join(",")}\n`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
await writeFileAtomic(
|
|
392
|
+
join(home, RUNTIMES_MANIFEST_REL),
|
|
393
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
394
|
+
0o644,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
348
398
|
async function listWorkerFiles(workersRoot) {
|
|
349
399
|
try {
|
|
350
400
|
const entries = await fs.readdir(workersRoot, { withFileTypes: true });
|
|
@@ -553,19 +603,24 @@ async function installLeadSkillLink(repoAbs, opts) {
|
|
|
553
603
|
return [LEAD_SKILL_NAME];
|
|
554
604
|
}
|
|
555
605
|
|
|
556
|
-
function parseInstallArgs(args) {
|
|
606
|
+
export function parseInstallArgs(args) {
|
|
557
607
|
const result = {
|
|
558
608
|
dryRun: false,
|
|
559
609
|
refresh: false,
|
|
560
610
|
quiet: false,
|
|
561
611
|
linkRepo: null,
|
|
612
|
+
runtime: "claude-code",
|
|
562
613
|
};
|
|
563
614
|
for (let i = 0; i < args.length; i++) {
|
|
564
615
|
const a = args[i];
|
|
565
616
|
if (a === "--dry-run") result.dryRun = true;
|
|
566
617
|
else if (a === "--refresh") result.refresh = true;
|
|
567
618
|
else if (a === "-q" || a === "--quiet") result.quiet = true;
|
|
568
|
-
else if (a === "--
|
|
619
|
+
else if (a === "--runtime" || a.startsWith("--runtime=")) {
|
|
620
|
+
const parsed = readRuntimeFlag(args, i);
|
|
621
|
+
result.runtime = validateInstallRuntime(parsed.value);
|
|
622
|
+
i = parsed.nextIndex;
|
|
623
|
+
} else if (a === "--link") {
|
|
569
624
|
const next = args[i + 1];
|
|
570
625
|
if (!next || next.startsWith("--")) {
|
|
571
626
|
throw new Error("--link requires a repo path");
|
|
@@ -585,7 +640,13 @@ export async function runInstall(args) {
|
|
|
585
640
|
return 0;
|
|
586
641
|
}
|
|
587
642
|
|
|
588
|
-
|
|
643
|
+
let opts;
|
|
644
|
+
try {
|
|
645
|
+
opts = parseInstallArgs(args);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
648
|
+
return 2;
|
|
649
|
+
}
|
|
589
650
|
const pkgRoot = getPackageRoot();
|
|
590
651
|
const runtimeRoot = join(pkgRoot, "runtime");
|
|
591
652
|
const paths = await resolvePaths();
|
|
@@ -607,6 +668,7 @@ export async function runInstall(args) {
|
|
|
607
668
|
|
|
608
669
|
if (!opts.quiet) {
|
|
609
670
|
process.stdout.write(`installing okstra runtime (package ${paths.package})\n`);
|
|
671
|
+
process.stdout.write(` runtime: ${opts.runtime}\n`);
|
|
610
672
|
process.stdout.write(` source: ${runtimeRoot}\n`);
|
|
611
673
|
process.stdout.write(` home: ${paths.home}\n`);
|
|
612
674
|
}
|
|
@@ -688,18 +750,23 @@ export async function runInstall(args) {
|
|
|
688
750
|
);
|
|
689
751
|
}
|
|
690
752
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
753
|
+
if (runtimeIncludesClaudeAssets(opts.runtime)) {
|
|
754
|
+
const skillResult = await installSkillsCopy(runtimeRoot, opts);
|
|
755
|
+
const leadSkillResult = await installLeadSkillCopy(runtimeRoot, opts);
|
|
756
|
+
await writeSkillsManifest(
|
|
757
|
+
paths.home,
|
|
758
|
+
[...skillResult.installed, ...leadSkillResult],
|
|
759
|
+
{ dryRun: opts.dryRun },
|
|
760
|
+
);
|
|
698
761
|
|
|
699
|
-
|
|
700
|
-
|
|
762
|
+
const agentResult = await installAgentsCopy(runtimeRoot, opts);
|
|
763
|
+
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
|
|
764
|
+
} else if (!opts.quiet) {
|
|
765
|
+
process.stdout.write(" claude assets: skipped for runtime=codex\n");
|
|
766
|
+
}
|
|
701
767
|
|
|
702
768
|
await installNamedTemplate(runtimeRoot, paths, { mode: "copy", ...opts }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
769
|
+
await writeRuntimeManifest(paths.home, opts.runtime, { dryRun: opts.dryRun });
|
|
703
770
|
|
|
704
771
|
if (!opts.dryRun) {
|
|
705
772
|
await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
|
|
@@ -751,12 +818,38 @@ function summarise(label, result, target) {
|
|
|
751
818
|
);
|
|
752
819
|
}
|
|
753
820
|
|
|
821
|
+
export function parseEnsureInstalledArgs(args) {
|
|
822
|
+
const result = {
|
|
823
|
+
quiet: false,
|
|
824
|
+
runtime: "claude-code",
|
|
825
|
+
};
|
|
826
|
+
for (let i = 0; i < args.length; i++) {
|
|
827
|
+
const arg = args[i];
|
|
828
|
+
if (arg === "-q" || arg === "--quiet") {
|
|
829
|
+
result.quiet = true;
|
|
830
|
+
} else if (arg === "--runtime" || arg.startsWith("--runtime=")) {
|
|
831
|
+
const parsed = readRuntimeFlag(args, i);
|
|
832
|
+
result.runtime = validateInstallRuntime(parsed.value);
|
|
833
|
+
i = parsed.nextIndex;
|
|
834
|
+
} else {
|
|
835
|
+
throw new Error(`unknown argument '${arg}'`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
|
|
754
841
|
export async function runEnsureInstalled(args) {
|
|
755
842
|
if (args.includes("--help") || args.includes("-h")) {
|
|
756
843
|
process.stdout.write(ENSURE_USAGE);
|
|
757
844
|
return 0;
|
|
758
845
|
}
|
|
759
|
-
|
|
846
|
+
let opts;
|
|
847
|
+
try {
|
|
848
|
+
opts = parseEnsureInstalledArgs(args);
|
|
849
|
+
} catch (err) {
|
|
850
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
851
|
+
return 2;
|
|
852
|
+
}
|
|
760
853
|
const paths = await resolvePaths();
|
|
761
854
|
|
|
762
855
|
const reasons = [];
|
|
@@ -769,18 +862,22 @@ export async function runEnsureInstalled(args) {
|
|
|
769
862
|
if (!(await fileExists(join(paths.home, "schemas", "final-report-v1.0.schema.json")))) {
|
|
770
863
|
reasons.push(`missing ${join(paths.home, "schemas", "final-report-v1.0.schema.json")}`);
|
|
771
864
|
}
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
865
|
+
if (runtimeIncludesClaudeAssets(opts.runtime)) {
|
|
866
|
+
if (!(await fileExists(join(CLAUDE_SKILLS_DIR, "okstra-setup", "SKILL.md")))) {
|
|
867
|
+
reasons.push(`missing ${CLAUDE_SKILLS_DIR}/okstra-setup/SKILL.md`);
|
|
868
|
+
}
|
|
869
|
+
for (const reason of await agentDriftReasons(paths)) {
|
|
870
|
+
reasons.push(reason);
|
|
871
|
+
}
|
|
777
872
|
}
|
|
778
873
|
|
|
779
874
|
if (reasons.length === 0) {
|
|
780
|
-
if (!quiet) process.stdout.write(`okstra runtime OK (package ${paths.package})\n`);
|
|
875
|
+
if (!opts.quiet) process.stdout.write(`okstra runtime OK (package ${paths.package})\n`);
|
|
781
876
|
return 0;
|
|
782
877
|
}
|
|
783
878
|
|
|
784
879
|
process.stderr.write(`okstra runtime needs install: ${reasons.join("; ")}\n`);
|
|
785
|
-
|
|
880
|
+
const installArgs = ["--runtime", opts.runtime];
|
|
881
|
+
if (opts.quiet) installArgs.push("-q");
|
|
882
|
+
return await runInstall(installArgs);
|
|
786
883
|
}
|
package/src/paths.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { getPackageRoot, getPackageVersion } from "./version.mjs";
|
|
4
5
|
|
|
5
6
|
const USAGE = `okstra paths — print runtime paths
|
|
@@ -52,7 +53,7 @@ const SHELL_EXPORTS = [
|
|
|
52
53
|
];
|
|
53
54
|
|
|
54
55
|
export async function resolvePaths() {
|
|
55
|
-
const home = join(homedir(), ".okstra");
|
|
56
|
+
const home = process.env.OKSTRA_HOME?.trim() || join(homedir(), ".okstra");
|
|
56
57
|
const pkgRoot = getPackageRoot();
|
|
57
58
|
const devLink = await readSimpleFile(join(home, "dev-link"));
|
|
58
59
|
const runtimeRoot = devLink || join(pkgRoot, "runtime");
|
|
@@ -68,18 +69,31 @@ export async function resolvePaths() {
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function workspacePythonpath(paths) {
|
|
73
|
+
const candidates = [
|
|
74
|
+
join(paths.workspace, "python"),
|
|
75
|
+
join(paths.workspace, "scripts"),
|
|
76
|
+
];
|
|
77
|
+
return candidates.find((candidate) => existsSync(candidate)) || "";
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
// Compose the PYTHONPATH spawned python3 subprocesses should see.
|
|
72
81
|
//
|
|
73
82
|
// Precedence (first wins):
|
|
74
83
|
// 1. process.env.PYTHONPATH (user override — e.g. in-repo `PYTHONPATH=scripts`
|
|
75
84
|
// for dev work without re-running `okstra install`).
|
|
76
|
-
// 2. The
|
|
85
|
+
// 2. The package/dev-link workspace runtime selected by `okstra paths`.
|
|
86
|
+
// 3. The installed user-runtime path (~/.okstra/lib/python).
|
|
77
87
|
//
|
|
78
88
|
// This is a colon-joined string suitable for direct assignment into the child
|
|
79
89
|
// process env. Empty segments are dropped so a missing env var does not produce
|
|
80
90
|
// a leading ':' (which Python treats as 'current directory').
|
|
81
91
|
export function buildPythonpath(paths) {
|
|
82
|
-
const fragments = [
|
|
92
|
+
const fragments = [
|
|
93
|
+
process.env.PYTHONPATH,
|
|
94
|
+
workspacePythonpath(paths),
|
|
95
|
+
paths.pythonpath,
|
|
96
|
+
].filter(
|
|
83
97
|
(s) => typeof s === "string" && s.length > 0,
|
|
84
98
|
);
|
|
85
99
|
return fragments.join(":");
|
package/src/render-bundle.mjs
CHANGED
|
@@ -16,6 +16,7 @@ Usage:
|
|
|
16
16
|
[--approved-plan <path>] [--workers <list>] [--directive <text>] \\
|
|
17
17
|
[--lead-model <m>] [--claude-model <m>] [--codex-model <m>] \\
|
|
18
18
|
[--gemini-model <m>] [--report-writer-model <m>] \\
|
|
19
|
+
[--lead-runtime claude-code|codex] \\
|
|
19
20
|
[--related-tasks <list>] [--base-ref <ref>] \\
|
|
20
21
|
[--clarification-response <path>] [--work-category <cat>] \\
|
|
21
22
|
[--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>] \\
|
|
@@ -23,6 +24,7 @@ Usage:
|
|
|
23
24
|
|
|
24
25
|
--stage implementation / final-verification only
|
|
25
26
|
--stages release-handoff only: PR stage bundle csv (empty = whole-task)
|
|
27
|
+
--lead-runtime lead adapter marker; codex is render-only in this version
|
|
26
28
|
--pr-template-path release-handoff only
|
|
27
29
|
--fix-cycle entry phase only: record this re-entry of a done task as a
|
|
28
30
|
bug-fix cycle (yes opens/continues the cycle)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const RUNTIMES_MANIFEST_REL = "installed-runtimes.json";
|
|
2
|
+
|
|
3
|
+
export function runtimeListForSelection(runtime) {
|
|
4
|
+
if (runtime === "all") return ["claude-code", "codex", "external"];
|
|
5
|
+
return [runtime];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildRuntimeManifest(runtime, installedAt = new Date().toISOString()) {
|
|
9
|
+
const includesClaude = runtime === "claude-code" || runtime === "all";
|
|
10
|
+
const includesCodex = runtime === "codex" || runtime === "all";
|
|
11
|
+
const includesExternal = runtime === "external" || runtime === "all";
|
|
12
|
+
return {
|
|
13
|
+
installedAt,
|
|
14
|
+
runtime,
|
|
15
|
+
runtimes: runtimeListForSelection(runtime),
|
|
16
|
+
assets: {
|
|
17
|
+
sharedRuntime: true,
|
|
18
|
+
claudeSkills: includesClaude,
|
|
19
|
+
claudeAgents: includesClaude,
|
|
20
|
+
codexAdapter: includesCodex,
|
|
21
|
+
externalAdapter: includesExternal,
|
|
22
|
+
},
|
|
23
|
+
version: 1,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function runtimeManifestIncludesClaudeAssets(data) {
|
|
28
|
+
return data?.assets?.claudeSkills === true || data?.assets?.claudeAgents === true;
|
|
29
|
+
}
|
package/src/team.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { runPythonModule } from "./_python-helper.mjs";
|
|
2
|
+
import { resolvePaths } from "./paths.mjs";
|
|
3
|
+
|
|
4
|
+
const USAGE = `okstra team — dispatch and await tmux-pane workers for external leads
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
okstra team dispatch --project-root <dir> --run-manifest <path> \
|
|
8
|
+
[--workers <csv>] [--jobs-file <path>] [--dry-run] \
|
|
9
|
+
[--idle-timeout-seconds <n>] [--dispatch-kind <kind>]
|
|
10
|
+
okstra team await --project-root <dir> --run-manifest <path> \
|
|
11
|
+
[--poll-interval-seconds <n>] [--timeout-seconds <n>] \
|
|
12
|
+
[--heartbeat-seconds <n>] [--json]
|
|
13
|
+
okstra team teardown --project-root <dir> --run-manifest <path> \
|
|
14
|
+
[--dry-run] [--json]
|
|
15
|
+
|
|
16
|
+
This command is for leadRuntime=external bundles. It dispatches okstra-owned
|
|
17
|
+
worker panes through tmux when possible and degrades to CLI wrappers when tmux is
|
|
18
|
+
unavailable to the lead harness.
|
|
19
|
+
|
|
20
|
+
--workspace-root and --okstra-bin are owned by this command.
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const OWNED_FLAGS = new Set(["--workspace-root", "--okstra-bin"]);
|
|
24
|
+
|
|
25
|
+
function isOwnedFlag(arg) {
|
|
26
|
+
if (OWNED_FLAGS.has(arg)) return true;
|
|
27
|
+
return [...OWNED_FLAGS].some((flag) => arg.startsWith(`${flag}=`));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildTeamArgs(args, paths) {
|
|
31
|
+
return [
|
|
32
|
+
"--workspace-root", paths.workspace,
|
|
33
|
+
"--okstra-bin", paths.bin,
|
|
34
|
+
...args,
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function run(args) {
|
|
39
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
40
|
+
process.stdout.write(USAGE);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (args.length === 0) {
|
|
44
|
+
process.stdout.write(USAGE);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const forbidden = args.find(isOwnedFlag);
|
|
49
|
+
if (forbidden) {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`error: ${forbidden} is set by 'okstra team' itself — remove it from your args\n`,
|
|
52
|
+
);
|
|
53
|
+
return 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const paths = await resolvePaths();
|
|
57
|
+
const result = await runPythonModule({
|
|
58
|
+
module: "okstra_ctl.team",
|
|
59
|
+
args: buildTeamArgs(args, paths),
|
|
60
|
+
stdio: "inherit-stdout",
|
|
61
|
+
});
|
|
62
|
+
return result.code ?? 0;
|
|
63
|
+
}
|
package/src/uninstall.mjs
CHANGED
|
@@ -2,10 +2,15 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { resolvePaths } from "./paths.mjs";
|
|
5
|
+
import {
|
|
6
|
+
RUNTIMES_MANIFEST_REL,
|
|
7
|
+
runtimeManifestIncludesClaudeAssets,
|
|
8
|
+
} from "./runtime-manifest.mjs";
|
|
5
9
|
|
|
6
10
|
const BIN_ENTRYPOINTS = [
|
|
7
11
|
"okstra.sh",
|
|
8
12
|
"okstra-codex-exec.sh",
|
|
13
|
+
"okstra-claude-exec.sh",
|
|
9
14
|
"okstra-gemini-exec.sh",
|
|
10
15
|
"okstra-trace-cleanup.sh",
|
|
11
16
|
"okstra-team-reconcile.sh",
|
|
@@ -56,8 +61,8 @@ const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.
|
|
|
56
61
|
Usage:
|
|
57
62
|
okstra uninstall Remove ~/.okstra/{lib (python + validators),
|
|
58
63
|
bin/<known>, schemas, templates, version,
|
|
59
|
-
dev-link, installed-
|
|
60
|
-
installed-agents.json} AND
|
|
64
|
+
dev-link, installed-runtimes.json,
|
|
65
|
+
installed-skills.json, installed-agents.json} AND
|
|
61
66
|
~/.claude/skills/<name> AND
|
|
62
67
|
~/.claude/agents/<worker>.md for every
|
|
63
68
|
entry in the install manifests (fallback:
|
|
@@ -188,7 +193,9 @@ export async function runUninstall(args) {
|
|
|
188
193
|
// Remove the skills we own. Manifest is authoritative; fall back to the
|
|
189
194
|
// hard-coded okstra-* names if it is missing (e.g. an install from an old
|
|
190
195
|
// version that did not write the manifest).
|
|
191
|
-
const
|
|
196
|
+
const runtimeManifest = await readRuntimeManifest(paths.home);
|
|
197
|
+
const shouldRemoveClaudeAssets = shouldUseClaudeAssetFallback(runtimeManifest);
|
|
198
|
+
const skillNames = shouldRemoveClaudeAssets ? await readSkillsManifest(paths.home) : [];
|
|
192
199
|
if (!opts.quiet) {
|
|
193
200
|
process.stdout.write(` skills: removing ${skillNames.length} entries from ${CLAUDE_SKILLS_DIR}\n`);
|
|
194
201
|
}
|
|
@@ -198,7 +205,7 @@ export async function runUninstall(args) {
|
|
|
198
205
|
await removePath(join(paths.home, SKILLS_MANIFEST_REL), opts);
|
|
199
206
|
|
|
200
207
|
// Remove worker agents we own. Same authoritative-manifest pattern as skills.
|
|
201
|
-
const agentNames = await readAgentsManifest(paths.home);
|
|
208
|
+
const agentNames = shouldRemoveClaudeAssets ? await readAgentsManifest(paths.home) : [];
|
|
202
209
|
if (!opts.quiet) {
|
|
203
210
|
process.stdout.write(` agents: removing ${agentNames.length} entries from ${CLAUDE_AGENTS_DIR}\n`);
|
|
204
211
|
}
|
|
@@ -218,6 +225,7 @@ export async function runUninstall(args) {
|
|
|
218
225
|
|
|
219
226
|
await removePath(join(paths.home, "version"), opts);
|
|
220
227
|
await removePath(join(paths.home, "dev-link"), opts);
|
|
228
|
+
await removePath(join(paths.home, RUNTIMES_MANIFEST_REL), opts);
|
|
221
229
|
|
|
222
230
|
if (!opts.quiet) {
|
|
223
231
|
process.stdout.write("done. user data preserved (recent.jsonl, projects/, archive/, ...).\n");
|
|
@@ -225,6 +233,21 @@ export async function runUninstall(args) {
|
|
|
225
233
|
return 0;
|
|
226
234
|
}
|
|
227
235
|
|
|
236
|
+
async function readRuntimeManifest(home) {
|
|
237
|
+
try {
|
|
238
|
+
const raw = await fs.readFile(join(home, RUNTIMES_MANIFEST_REL), "utf8");
|
|
239
|
+
const data = JSON.parse(raw);
|
|
240
|
+
return data && typeof data === "object" ? data : null;
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function shouldUseClaudeAssetFallback(runtimeManifest) {
|
|
247
|
+
if (runtimeManifest === null) return true;
|
|
248
|
+
return runtimeManifestIncludesClaudeAssets(runtimeManifest);
|
|
249
|
+
}
|
|
250
|
+
|
|
228
251
|
async function readSkillsManifest(home) {
|
|
229
252
|
try {
|
|
230
253
|
const raw = await fs.readFile(join(home, SKILLS_MANIFEST_REL), "utf8");
|
|
File without changes
|