okstra 0.1.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 (106) hide show
  1. package/README.md +36 -0
  2. package/bin/okstra +62 -0
  3. package/package.json +30 -0
  4. package/runtime/.gitkeep +0 -0
  5. package/runtime/BUILD.json +5 -0
  6. package/runtime/agents/SKILL.md +243 -0
  7. package/runtime/agents/TODO.md +168 -0
  8. package/runtime/agents/workers/claude-worker.md +106 -0
  9. package/runtime/agents/workers/codex-worker.md +179 -0
  10. package/runtime/agents/workers/gemini-worker.md +179 -0
  11. package/runtime/agents/workers/report-writer-worker.md +116 -0
  12. package/runtime/bin/okstra-central.sh +152 -0
  13. package/runtime/bin/okstra-codex-exec.sh +53 -0
  14. package/runtime/bin/okstra-error-log.py +295 -0
  15. package/runtime/bin/okstra-gemini-exec.sh +55 -0
  16. package/runtime/bin/okstra-token-usage.py +46 -0
  17. package/runtime/bin/okstra.sh +162 -0
  18. package/runtime/prompts/launch.template.md +52 -0
  19. package/runtime/prompts/profiles/error-analysis.md +43 -0
  20. package/runtime/prompts/profiles/final-verification.md +37 -0
  21. package/runtime/prompts/profiles/implementation-planning.md +85 -0
  22. package/runtime/prompts/profiles/implementation.md +71 -0
  23. package/runtime/prompts/profiles/requirements-discovery.md +43 -0
  24. package/runtime/python/lib/okstra/cli.sh +227 -0
  25. package/runtime/python/lib/okstra/globals.sh +157 -0
  26. package/runtime/python/lib/okstra/interactive.sh +411 -0
  27. package/runtime/python/lib/okstra/project-resolver.sh +57 -0
  28. package/runtime/python/lib/okstra/usage.sh +98 -0
  29. package/runtime/python/lib/okstra-ctl/cmd-batch.sh +59 -0
  30. package/runtime/python/lib/okstra-ctl/cmd-list.sh +35 -0
  31. package/runtime/python/lib/okstra-ctl/cmd-open.sh +36 -0
  32. package/runtime/python/lib/okstra-ctl/cmd-projects.sh +26 -0
  33. package/runtime/python/lib/okstra-ctl/cmd-reconcile.sh +27 -0
  34. package/runtime/python/lib/okstra-ctl/cmd-reindex.sh +38 -0
  35. package/runtime/python/lib/okstra-ctl/cmd-rerun.sh +326 -0
  36. package/runtime/python/lib/okstra-ctl/cmd-show.sh +27 -0
  37. package/runtime/python/lib/okstra-ctl/cmd-tail.sh +76 -0
  38. package/runtime/python/lib/okstra-ctl/main.sh +41 -0
  39. package/runtime/python/lib/okstra-ctl/prepare.sh +29 -0
  40. package/runtime/python/lib/okstra-ctl/usage.sh +23 -0
  41. package/runtime/python/okstra_ctl/__init__.py +125 -0
  42. package/runtime/python/okstra_ctl/backfill.py +253 -0
  43. package/runtime/python/okstra_ctl/batch.py +62 -0
  44. package/runtime/python/okstra_ctl/ids.py +84 -0
  45. package/runtime/python/okstra_ctl/index.py +216 -0
  46. package/runtime/python/okstra_ctl/invocation.py +49 -0
  47. package/runtime/python/okstra_ctl/jsonl.py +84 -0
  48. package/runtime/python/okstra_ctl/listing.py +156 -0
  49. package/runtime/python/okstra_ctl/locks.py +42 -0
  50. package/runtime/python/okstra_ctl/material.py +62 -0
  51. package/runtime/python/okstra_ctl/models.py +63 -0
  52. package/runtime/python/okstra_ctl/path_resolve.py +40 -0
  53. package/runtime/python/okstra_ctl/paths.py +251 -0
  54. package/runtime/python/okstra_ctl/project_meta.py +51 -0
  55. package/runtime/python/okstra_ctl/reconcile.py +166 -0
  56. package/runtime/python/okstra_ctl/render.py +1065 -0
  57. package/runtime/python/okstra_ctl/resolver.py +54 -0
  58. package/runtime/python/okstra_ctl/run.py +674 -0
  59. package/runtime/python/okstra_ctl/run_context.py +166 -0
  60. package/runtime/python/okstra_ctl/seeding.py +97 -0
  61. package/runtime/python/okstra_ctl/sequence.py +53 -0
  62. package/runtime/python/okstra_ctl/session.py +33 -0
  63. package/runtime/python/okstra_ctl/tmux.py +27 -0
  64. package/runtime/python/okstra_ctl/workers.py +64 -0
  65. package/runtime/python/okstra_ctl/workflow.py +182 -0
  66. package/runtime/python/okstra_project/__init__.py +41 -0
  67. package/runtime/python/okstra_project/resolver.py +126 -0
  68. package/runtime/python/okstra_project/state.py +170 -0
  69. package/runtime/python/okstra_token_usage/__init__.py +26 -0
  70. package/runtime/python/okstra_token_usage/blocks.py +62 -0
  71. package/runtime/python/okstra_token_usage/claude.py +97 -0
  72. package/runtime/python/okstra_token_usage/cli.py +84 -0
  73. package/runtime/python/okstra_token_usage/codex.py +80 -0
  74. package/runtime/python/okstra_token_usage/collect.py +161 -0
  75. package/runtime/python/okstra_token_usage/gemini.py +77 -0
  76. package/runtime/python/okstra_token_usage/jsonl_io.py +18 -0
  77. package/runtime/python/okstra_token_usage/paths.py +22 -0
  78. package/runtime/python/okstra_token_usage/pricing.py +71 -0
  79. package/runtime/python/okstra_token_usage/report.py +64 -0
  80. package/runtime/templates/prd/brief.template.md +273 -0
  81. package/runtime/templates/project-docs/task-index.template.md +65 -0
  82. package/runtime/templates/reports/error-analysis-input.template.md +80 -0
  83. package/runtime/templates/reports/final-report.template.md +167 -0
  84. package/runtime/templates/reports/final-verification-input.template.md +67 -0
  85. package/runtime/templates/reports/implementation-input.template.md +81 -0
  86. package/runtime/templates/reports/implementation-planning-input.template.md +93 -0
  87. package/runtime/templates/reports/quick-input.template.md +64 -0
  88. package/runtime/templates/reports/schedule.template.md +168 -0
  89. package/runtime/templates/reports/settings.template.json +101 -0
  90. package/runtime/templates/reports/task-brief.template.md +165 -0
  91. package/runtime/validators/lib/common.sh +44 -0
  92. package/runtime/validators/lib/fixtures.sh +322 -0
  93. package/runtime/validators/lib/paths.sh +44 -0
  94. package/runtime/validators/lib/runners.sh +140 -0
  95. package/runtime/validators/lib/summary.sh +15 -0
  96. package/runtime/validators/lib/validate-assets.sh +44 -0
  97. package/runtime/validators/lib/validate-prompt-metadata.sh +267 -0
  98. package/runtime/validators/lib/validate-tasks.sh +335 -0
  99. package/runtime/validators/validate-run.py +568 -0
  100. package/runtime/validators/validate-schedule.py +665 -0
  101. package/runtime/validators/validate-workflow.sh +190 -0
  102. package/src/doctor.mjs +127 -0
  103. package/src/install.mjs +355 -0
  104. package/src/paths.mjs +132 -0
  105. package/src/uninstall.mjs +122 -0
  106. package/src/version.mjs +20 -0
package/src/paths.mjs ADDED
@@ -0,0 +1,132 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { getPackageRoot, getPackageVersion } from "./version.mjs";
4
+
5
+ const USAGE = `okstra paths — print runtime paths
6
+
7
+ Usage:
8
+ okstra paths Print all paths as JSON
9
+ okstra paths --json Same as the default
10
+ okstra paths --field <key> Print one field as a plain string
11
+ okstra paths --shell Print shell 'export' statements for the
12
+ standard OKSTRA_* env vars (sourced by skills)
13
+
14
+ Fields:
15
+ agents Absolute path to agents/. Default: <workspace>/agents.
16
+ workspace Directory containing prompts/, templates/, validators/,
17
+ agents/. Equals packages/okstra/runtime in copy mode,
18
+ or the dev-link repo path in dev mode.
19
+ pythonpath $HOME/.okstra/lib/python
20
+ bin $HOME/.okstra/bin
21
+ home $HOME/.okstra
22
+ version Installed okstra version (from ~/.okstra/version stamp)
23
+ package Currently invoked package version
24
+ dev-link Repo path recorded by 'okstra install --link <repo>'
25
+ (empty string when not in dev mode).
26
+
27
+ Shell mode exports (variable name -> field):
28
+ OKSTRA_WORKSPACE workspace
29
+ OKSTRA_AGENTS_DIR agents
30
+ OKSTRA_PYTHONPATH pythonpath
31
+ OKSTRA_BIN bin
32
+ OKSTRA_HOME home
33
+ `;
34
+
35
+ const VALID_FIELDS = new Set([
36
+ "agents",
37
+ "workspace",
38
+ "pythonpath",
39
+ "bin",
40
+ "home",
41
+ "version",
42
+ "package",
43
+ "dev-link",
44
+ ]);
45
+
46
+ const SHELL_EXPORTS = [
47
+ ["OKSTRA_WORKSPACE", "workspace"],
48
+ ["OKSTRA_AGENTS_DIR", "agents"],
49
+ ["OKSTRA_PYTHONPATH", "pythonpath"],
50
+ ["OKSTRA_BIN", "bin"],
51
+ ["OKSTRA_HOME", "home"],
52
+ ];
53
+
54
+ export async function resolvePaths() {
55
+ const home = join(homedir(), ".okstra");
56
+ const pkgRoot = getPackageRoot();
57
+ const devLink = await readSimpleFile(join(home, "dev-link"));
58
+ const runtimeRoot = devLink || join(pkgRoot, "runtime");
59
+ return {
60
+ agents: join(runtimeRoot, "agents"),
61
+ workspace: runtimeRoot,
62
+ pythonpath: join(home, "lib", "python"),
63
+ bin: join(home, "bin"),
64
+ home,
65
+ package: await getPackageVersion(),
66
+ version: await readSimpleFile(join(home, "version")),
67
+ "dev-link": devLink || "",
68
+ };
69
+ }
70
+
71
+ async function readSimpleFile(path) {
72
+ try {
73
+ const { readFile } = await import("node:fs/promises");
74
+ return (await readFile(path, "utf8")).trim();
75
+ } catch {
76
+ return "";
77
+ }
78
+ }
79
+
80
+ export async function run(args) {
81
+ if (args.includes("--help") || args.includes("-h")) {
82
+ process.stdout.write(USAGE);
83
+ return 0;
84
+ }
85
+
86
+ let field = null;
87
+ let mode = "json";
88
+ for (let i = 0; i < args.length; i++) {
89
+ const a = args[i];
90
+ if (a === "--json") {
91
+ mode = "json";
92
+ } else if (a === "--shell") {
93
+ mode = "shell";
94
+ } else if (a === "--field") {
95
+ field = args[i + 1];
96
+ i++;
97
+ if (!field) {
98
+ process.stderr.write("error: --field requires a name\n");
99
+ return 2;
100
+ }
101
+ if (!VALID_FIELDS.has(field)) {
102
+ process.stderr.write(`error: unknown field '${field}'. Valid: ${[...VALID_FIELDS].join(", ")}\n`);
103
+ return 2;
104
+ }
105
+ mode = "field";
106
+ } else {
107
+ process.stderr.write(`error: unknown argument '${a}'\n\n${USAGE}`);
108
+ return 2;
109
+ }
110
+ }
111
+
112
+ const paths = await resolvePaths();
113
+
114
+ if (mode === "field") {
115
+ process.stdout.write(`${paths[field]}\n`);
116
+ return 0;
117
+ }
118
+ if (mode === "shell") {
119
+ for (const [varName, key] of SHELL_EXPORTS) {
120
+ process.stdout.write(`export ${varName}=${shellQuote(paths[key])}\n`);
121
+ }
122
+ return 0;
123
+ }
124
+ process.stdout.write(`${JSON.stringify(paths, null, 2)}\n`);
125
+ return 0;
126
+ }
127
+
128
+ function shellQuote(value) {
129
+ if (value === "" || value == null) return "''";
130
+ if (/^[A-Za-z0-9_\/.\-+:@%=]+$/.test(value)) return value;
131
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
132
+ }
@@ -0,0 +1,122 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { resolvePaths } from "./paths.mjs";
4
+
5
+ const BIN_ENTRYPOINTS = [
6
+ "okstra.sh",
7
+ "okstra-codex-exec.sh",
8
+ "okstra-gemini-exec.sh",
9
+ "okstra-central.sh",
10
+ "okstra-token-usage.py",
11
+ "okstra-error-log.py",
12
+ ];
13
+
14
+ const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra
15
+
16
+ Usage:
17
+ okstra uninstall Remove lib/, bin/{known entries}, version, dev-link
18
+ Preserves user data: recent.jsonl, active.jsonl,
19
+ projects/, archive/, state.json, .locks/
20
+ okstra uninstall --purge Remove the entire ~/.okstra directory (DESTRUCTIVE)
21
+ Requires -y or an interactive confirmation
22
+ okstra uninstall --dry-run Print the plan without touching disk
23
+ okstra uninstall -y Skip confirmation prompt for --purge
24
+ `;
25
+
26
+ async function pathExists(p) {
27
+ try {
28
+ await fs.lstat(p);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ async function removePath(p, opts) {
36
+ const { dryRun = false, quiet = false } = opts ?? {};
37
+ if (!(await pathExists(p))) {
38
+ if (!quiet) process.stdout.write(` not present: ${p}\n`);
39
+ return false;
40
+ }
41
+ if (dryRun) {
42
+ process.stdout.write(`[dry-run] rm ${p}\n`);
43
+ return true;
44
+ }
45
+ await fs.rm(p, { recursive: true, force: true });
46
+ if (!quiet) process.stdout.write(` removed: ${p}\n`);
47
+ return true;
48
+ }
49
+
50
+ async function promptConfirm(question) {
51
+ if (!process.stdin.isTTY) return false;
52
+ process.stdout.write(`${question} [y/N] `);
53
+ return await new Promise((resolveP) => {
54
+ process.stdin.setEncoding("utf8");
55
+ process.stdin.once("data", (chunk) => {
56
+ const answer = chunk.toString().trim().toLowerCase();
57
+ resolveP(answer === "y" || answer === "yes");
58
+ });
59
+ });
60
+ }
61
+
62
+ export async function runUninstall(args) {
63
+ if (args.includes("--help") || args.includes("-h")) {
64
+ process.stdout.write(USAGE);
65
+ return 0;
66
+ }
67
+ const opts = {
68
+ dryRun: args.includes("--dry-run"),
69
+ quiet: args.includes("-q") || args.includes("--quiet"),
70
+ purge: args.includes("--purge"),
71
+ yes: args.includes("-y") || args.includes("--yes"),
72
+ };
73
+ const paths = await resolvePaths();
74
+
75
+ if (!(await pathExists(paths.home))) {
76
+ if (!opts.quiet) process.stdout.write(`okstra not installed (no ${paths.home})\n`);
77
+ return 0;
78
+ }
79
+
80
+ if (opts.purge) {
81
+ if (!opts.yes && !opts.dryRun) {
82
+ const ok = await promptConfirm(`purge entire ${paths.home}? user data will be lost.`);
83
+ if (!ok) {
84
+ process.stdout.write("aborted.\n");
85
+ return 1;
86
+ }
87
+ }
88
+ if (!opts.quiet) process.stdout.write(`purging ${paths.home}\n`);
89
+ await removePath(paths.home, opts);
90
+ return 0;
91
+ }
92
+
93
+ if (!opts.quiet) {
94
+ process.stdout.write(`uninstalling okstra runtime\n`);
95
+ process.stdout.write(` home: ${paths.home}\n`);
96
+ }
97
+ await removePath(paths.pythonpath, opts);
98
+ for (const name of BIN_ENTRYPOINTS) {
99
+ await removePath(join(paths.bin, name), opts);
100
+ }
101
+ // Clean now-empty parents (best-effort).
102
+ for (const dir of [join(paths.home, "lib"), paths.bin]) {
103
+ if (await pathExists(dir)) {
104
+ try {
105
+ const entries = await fs.readdir(dir);
106
+ if (entries.length === 0) {
107
+ if (!opts.dryRun) await fs.rmdir(dir);
108
+ if (!opts.quiet) process.stdout.write(` removed empty: ${dir}\n`);
109
+ }
110
+ } catch {
111
+ /* ignore */
112
+ }
113
+ }
114
+ }
115
+ await removePath(join(paths.home, "version"), opts);
116
+ await removePath(join(paths.home, "dev-link"), opts);
117
+
118
+ if (!opts.quiet) {
119
+ process.stdout.write("done. user data preserved (recent.jsonl, projects/, archive/, ...).\n");
120
+ }
121
+ return 0;
122
+ }
@@ -0,0 +1,20 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, join } from "node:path";
4
+
5
+ const here = dirname(fileURLToPath(import.meta.url));
6
+ const packageJsonPath = join(here, "..", "package.json");
7
+
8
+ let cachedVersion = null;
9
+
10
+ export async function getPackageVersion() {
11
+ if (cachedVersion) return cachedVersion;
12
+ const raw = await readFile(packageJsonPath, "utf8");
13
+ const { version } = JSON.parse(raw);
14
+ cachedVersion = version;
15
+ return version;
16
+ }
17
+
18
+ export function getPackageRoot() {
19
+ return join(here, "..");
20
+ }