ramstack 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 (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/agents/comment-sicko.md +32 -0
  4. package/agents/poteto-agent.md +9 -0
  5. package/cli/rstack.mjs +539 -0
  6. package/package.json +29 -0
  7. package/skills/architect/README.md +25 -0
  8. package/skills/architect/SKILL.md +83 -0
  9. package/skills/architect/agents/openai.yaml +5 -0
  10. package/skills/architect/references/design-red-flags.md +33 -0
  11. package/skills/architect/references/rationale-template.md +35 -0
  12. package/skills/architect/references/runner-prompt.md +20 -0
  13. package/skills/arena/README.md +21 -0
  14. package/skills/arena/SKILL.md +71 -0
  15. package/skills/arena/agents/openai.yaml +5 -0
  16. package/skills/automate-me/README.md +21 -0
  17. package/skills/automate-me/SKILL.md +109 -0
  18. package/skills/automate-me/agents/openai.yaml +5 -0
  19. package/skills/blast-radius/README.md +21 -0
  20. package/skills/blast-radius/SKILL.md +50 -0
  21. package/skills/blast-radius/agents/openai.yaml +5 -0
  22. package/skills/bro/README.md +16 -0
  23. package/skills/bro/SKILL.md +7 -0
  24. package/skills/bro/agents/openai.yaml +5 -0
  25. package/skills/create-verification-skill/README.md +22 -0
  26. package/skills/create-verification-skill/SKILL.md +44 -0
  27. package/skills/create-verification-skill/agents/openai.yaml +5 -0
  28. package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
  29. package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
  30. package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
  31. package/skills/figure-it-out/README.md +22 -0
  32. package/skills/figure-it-out/SKILL.md +55 -0
  33. package/skills/figure-it-out/agents/openai.yaml +5 -0
  34. package/skills/how/README.md +25 -0
  35. package/skills/how/SKILL.md +134 -0
  36. package/skills/how/agents/openai.yaml +3 -0
  37. package/skills/how/references/critic-prompt.md +59 -0
  38. package/skills/how/references/critique-rubric.md +58 -0
  39. package/skills/how/references/explainer-prompt.md +55 -0
  40. package/skills/how/references/explorer-prompt.md +52 -0
  41. package/skills/interrogate/README.md +21 -0
  42. package/skills/interrogate/SKILL.md +112 -0
  43. package/skills/interrogate/agents/openai.yaml +5 -0
  44. package/skills/interrogate/references/code-quality-review.md +47 -0
  45. package/skills/interrogate/references/lead-judgment.md +58 -0
  46. package/skills/interrogate/references/reviewer-prompt.md +72 -0
  47. package/skills/interrogate/references/rubric.md +77 -0
  48. package/skills/maintain-verification-skill/README.md +21 -0
  49. package/skills/maintain-verification-skill/SKILL.md +39 -0
  50. package/skills/maintain-verification-skill/agents/openai.yaml +5 -0
  51. package/skills/no-comments/README.md +22 -0
  52. package/skills/no-comments/SKILL.md +24 -0
  53. package/skills/no-comments/agents/openai.yaml +5 -0
  54. package/skills/poteto-mode/README.md +26 -0
  55. package/skills/poteto-mode/SKILL.md +140 -0
  56. package/skills/poteto-mode/agents/openai.yaml +5 -0
  57. package/skills/poteto-mode/playbooks/authoring-a-skill.md +13 -0
  58. package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
  59. package/skills/poteto-mode/playbooks/autopilot-full.md +13 -0
  60. package/skills/poteto-mode/playbooks/autopilot-stack.md +16 -0
  61. package/skills/poteto-mode/playbooks/babysit.md +27 -0
  62. package/skills/poteto-mode/playbooks/bug-fix.md +17 -0
  63. package/skills/poteto-mode/playbooks/eval.md +27 -0
  64. package/skills/poteto-mode/playbooks/feature.md +21 -0
  65. package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
  66. package/skills/poteto-mode/playbooks/investigation.md +14 -0
  67. package/skills/poteto-mode/playbooks/multi-phase-plan.md +3 -0
  68. package/skills/poteto-mode/playbooks/opening-a-pr.md +11 -0
  69. package/skills/poteto-mode/playbooks/orchestrate.md +113 -0
  70. package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
  71. package/skills/poteto-mode/playbooks/perf-issue.md +24 -0
  72. package/skills/poteto-mode/playbooks/prototype.md +14 -0
  73. package/skills/poteto-mode/playbooks/refactoring.md +16 -0
  74. package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
  75. package/skills/poteto-mode/playbooks/session-pickup.md +13 -0
  76. package/skills/poteto-mode/playbooks/shipping.md +20 -0
  77. package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
  78. package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
  79. package/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -0
  80. package/skills/poteto-mode/references/bugbot-triage.md +142 -0
  81. package/skills/poteto-mode/references/plan.md +105 -0
  82. package/skills/poteto-mode/scripts/bootstrap.ts +62 -0
  83. package/skills/poteto-mode/scripts/bun.lock +67 -0
  84. package/skills/poteto-mode/scripts/orch/orch.test.ts +634 -0
  85. package/skills/poteto-mode/scripts/orch/orch.ts +578 -0
  86. package/skills/poteto-mode/scripts/orch/store.ts +1607 -0
  87. package/skills/poteto-mode/scripts/package.json +16 -0
  88. package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
  89. package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
  90. package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +118 -0
  91. package/skills/poteto-mode/scripts/watch-pr/github.test.ts +306 -0
  92. package/skills/poteto-mode/scripts/watch-pr/github.ts +699 -0
  93. package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +420 -0
  94. package/skills/poteto-mode/scripts/watch-pr/policy.ts +832 -0
  95. package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
  96. package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
  97. package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
  98. package/skills/poteto-mode/scripts/watch-pr/types.ts +401 -0
  99. package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
  100. package/skills/poteto-mode/scripts/worktree-audit.sh +86 -0
  101. package/skills/principle-boundary-discipline/README.md +17 -0
  102. package/skills/principle-boundary-discipline/SKILL.md +34 -0
  103. package/skills/principle-boundary-discipline/agents/openai.yaml +5 -0
  104. package/skills/principle-build-the-lever/README.md +17 -0
  105. package/skills/principle-build-the-lever/SKILL.md +23 -0
  106. package/skills/principle-build-the-lever/agents/openai.yaml +5 -0
  107. package/skills/principle-encode-lessons-in-structure/README.md +17 -0
  108. package/skills/principle-encode-lessons-in-structure/SKILL.md +31 -0
  109. package/skills/principle-encode-lessons-in-structure/agents/openai.yaml +5 -0
  110. package/skills/principle-exhaust-the-design-space/README.md +17 -0
  111. package/skills/principle-exhaust-the-design-space/SKILL.md +21 -0
  112. package/skills/principle-exhaust-the-design-space/agents/openai.yaml +5 -0
  113. package/skills/principle-experience-first/README.md +17 -0
  114. package/skills/principle-experience-first/SKILL.md +19 -0
  115. package/skills/principle-experience-first/agents/openai.yaml +5 -0
  116. package/skills/principle-fix-root-causes/README.md +17 -0
  117. package/skills/principle-fix-root-causes/SKILL.md +23 -0
  118. package/skills/principle-fix-root-causes/agents/openai.yaml +5 -0
  119. package/skills/principle-foundational-thinking/README.md +17 -0
  120. package/skills/principle-foundational-thinking/SKILL.md +21 -0
  121. package/skills/principle-foundational-thinking/agents/openai.yaml +5 -0
  122. package/skills/principle-guard-the-context-window/README.md +17 -0
  123. package/skills/principle-guard-the-context-window/SKILL.md +17 -0
  124. package/skills/principle-guard-the-context-window/agents/openai.yaml +5 -0
  125. package/skills/principle-laziness-protocol/README.md +17 -0
  126. package/skills/principle-laziness-protocol/SKILL.md +18 -0
  127. package/skills/principle-laziness-protocol/agents/openai.yaml +5 -0
  128. package/skills/principle-make-operations-idempotent/README.md +17 -0
  129. package/skills/principle-make-operations-idempotent/SKILL.md +24 -0
  130. package/skills/principle-make-operations-idempotent/agents/openai.yaml +5 -0
  131. package/skills/principle-migrate-callers-then-delete-legacy-apis/README.md +17 -0
  132. package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +22 -0
  133. package/skills/principle-migrate-callers-then-delete-legacy-apis/agents/openai.yaml +5 -0
  134. package/skills/principle-minimize-reader-load/README.md +17 -0
  135. package/skills/principle-minimize-reader-load/SKILL.md +23 -0
  136. package/skills/principle-minimize-reader-load/agents/openai.yaml +5 -0
  137. package/skills/principle-model-the-domain/README.md +17 -0
  138. package/skills/principle-model-the-domain/SKILL.md +26 -0
  139. package/skills/principle-model-the-domain/agents/openai.yaml +5 -0
  140. package/skills/principle-never-block-on-the-human/README.md +16 -0
  141. package/skills/principle-never-block-on-the-human/SKILL.md +23 -0
  142. package/skills/principle-never-block-on-the-human/agents/openai.yaml +5 -0
  143. package/skills/principle-outcome-oriented-execution/README.md +16 -0
  144. package/skills/principle-outcome-oriented-execution/SKILL.md +22 -0
  145. package/skills/principle-outcome-oriented-execution/agents/openai.yaml +5 -0
  146. package/skills/principle-prove-it-works/README.md +18 -0
  147. package/skills/principle-prove-it-works/SKILL.md +33 -0
  148. package/skills/principle-prove-it-works/agents/openai.yaml +5 -0
  149. package/skills/principle-redesign-from-first-principles/README.md +16 -0
  150. package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
  151. package/skills/principle-redesign-from-first-principles/agents/openai.yaml +5 -0
  152. package/skills/principle-separate-before-serializing-shared-state/README.md +16 -0
  153. package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
  154. package/skills/principle-separate-before-serializing-shared-state/agents/openai.yaml +5 -0
  155. package/skills/principle-sequence-verifiable-units/README.md +18 -0
  156. package/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
  157. package/skills/principle-sequence-verifiable-units/agents/openai.yaml +5 -0
  158. package/skills/principle-subtract-before-you-add/README.md +17 -0
  159. package/skills/principle-subtract-before-you-add/SKILL.md +22 -0
  160. package/skills/principle-subtract-before-you-add/agents/openai.yaml +5 -0
  161. package/skills/principle-type-system-discipline/README.md +19 -0
  162. package/skills/principle-type-system-discipline/SKILL.md +31 -0
  163. package/skills/principle-type-system-discipline/agents/openai.yaml +5 -0
  164. package/skills/recall/README.md +23 -0
  165. package/skills/recall/SKILL.md +35 -0
  166. package/skills/recall/agents/openai.yaml +5 -0
  167. package/skills/reflect/README.md +24 -0
  168. package/skills/reflect/SKILL.md +77 -0
  169. package/skills/reflect/agents/openai.yaml +5 -0
  170. package/skills/reflect/references/divergent-reviewer.md +43 -0
  171. package/skills/reflect/references/judgment-reviewer.md +42 -0
  172. package/skills/reflect/references/synthesizer.md +56 -0
  173. package/skills/reflect/references/tooling-reviewer.md +57 -0
  174. package/skills/setup-rstack/README.md +25 -0
  175. package/skills/setup-rstack/SKILL.md +81 -0
  176. package/skills/setup-rstack/agents/openai.yaml +3 -0
  177. package/skills/show-me-your-work/README.md +23 -0
  178. package/skills/show-me-your-work/SKILL.md +82 -0
  179. package/skills/show-me-your-work/agents/openai.yaml +5 -0
  180. package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
  181. package/skills/show-me-your-work/scripts/log.sh +40 -0
  182. package/skills/swarm/README.md +21 -0
  183. package/skills/swarm/SKILL.md +46 -0
  184. package/skills/swarm/agents/openai.yaml +5 -0
  185. package/skills/tdd/README.md +21 -0
  186. package/skills/tdd/SKILL.md +44 -0
  187. package/skills/tdd/agents/openai.yaml +5 -0
  188. package/skills/teach/README.md +22 -0
  189. package/skills/teach/SKILL.md +21 -0
  190. package/skills/teach/agents/openai.yaml +5 -0
  191. package/skills/technical-writing/README.md +23 -0
  192. package/skills/technical-writing/SKILL.md +130 -0
  193. package/skills/technical-writing/agents/openai.yaml +5 -0
  194. package/skills/typescript-best-practices/README.md +22 -0
  195. package/skills/typescript-best-practices/SKILL.md +28 -0
  196. package/skills/typescript-best-practices/agents/openai.yaml +3 -0
  197. package/skills/typescript-best-practices/references/patterns.md +292 -0
  198. package/skills/unslop/README.md +21 -0
  199. package/skills/unslop/SKILL.md +80 -0
  200. package/skills/unslop/agents/openai.yaml +3 -0
  201. package/skills/why/README.md +26 -0
  202. package/skills/why/SKILL.md +229 -0
  203. package/skills/why/agents/openai.yaml +3 -0
  204. package/skills/why/references/epistemics.md +144 -0
  205. package/skills/why/references/investigator-prompt.md +103 -0
  206. package/skills/why/references/source-playbook.md +17 -0
  207. package/skills/why/references/sources/code-archaeology.md +88 -0
  208. package/skills/why/references/sources/databricks.md +70 -0
  209. package/skills/why/references/sources/datadog.md +99 -0
  210. package/skills/why/references/sources/incident-postmortem.md +15 -0
  211. package/skills/why/references/sources/linear.md +48 -0
  212. package/skills/why/references/sources/notion.md +55 -0
  213. package/skills/why/references/sources/sentry.md +100 -0
  214. package/skills/why/references/sources/slack.md +54 -0
  215. package/skills/why/references/synthesizer-prompt.md +135 -0
@@ -0,0 +1,634 @@
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import {
3
+ chmod,
4
+ mkdir,
5
+ mkdtemp,
6
+ readFile,
7
+ readdir,
8
+ rm,
9
+ writeFile,
10
+ } from "node:fs/promises";
11
+ import { realpathSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import {
15
+ NotFoundError,
16
+ UserError,
17
+ openStore,
18
+ parseVerdict,
19
+ type OpenStoreOptions,
20
+ type Store,
21
+ } from "./store.ts";
22
+
23
+ const SCRIPT = join(import.meta.dir, "orch.ts");
24
+ const directories: string[] = [];
25
+ const handles: Store[] = [];
26
+
27
+ interface RunResult {
28
+ readonly code: number;
29
+ readonly stdout: string;
30
+ readonly stderr: string;
31
+ }
32
+
33
+ async function makeDirectory(): Promise<string> {
34
+ const directory = await mkdtemp(join(tmpdir(), "orch-test-"));
35
+ directories.push(directory);
36
+ return directory;
37
+ }
38
+
39
+ function useStore(
40
+ directory: string,
41
+ options?: OpenStoreOptions
42
+ ): Store {
43
+ const store = openStore(directory, options);
44
+ handles.push(store);
45
+ return store;
46
+ }
47
+
48
+ async function initializedStore(): Promise<{
49
+ readonly directory: string;
50
+ readonly store: Store;
51
+ }> {
52
+ const directory = await makeDirectory();
53
+ const store = useStore(directory);
54
+ await store.init();
55
+ return { directory, store };
56
+ }
57
+
58
+ function git({
59
+ args,
60
+ repo,
61
+ }: {
62
+ args: readonly string[];
63
+ repo: string;
64
+ }): string {
65
+ const result = Bun.spawnSync(["git", "-C", repo, ...args]);
66
+ if (result.exitCode !== 0) {
67
+ throw new Error(
68
+ `git ${args.join(" ")} failed: ${result.stderr.toString()}`
69
+ );
70
+ }
71
+ return result.stdout.toString().trim();
72
+ }
73
+
74
+ async function makeGitStack(directory: string): Promise<{
75
+ readonly repo: string;
76
+ readonly mergedSha: string;
77
+ readonly closedSha: string;
78
+ readonly openSha: string;
79
+ }> {
80
+ const repo = join(directory, "repo");
81
+ await mkdir(repo);
82
+ git({ repo, args: ["init", "--initial-branch=main"] });
83
+ git({ repo, args: ["config", "user.name", "Orch Test"] });
84
+ git({ repo, args: ["config", "user.email", "orch@example.com"] });
85
+ await writeFile(join(repo, "main.txt"), "main\n");
86
+ git({ repo, args: ["add", "."] });
87
+ git({ repo, args: ["commit", "-m", "main"] });
88
+
89
+ const branches = ["stack/merged", "stack/closed", "stack/open"];
90
+ for (const [index, branch] of branches.entries()) {
91
+ git({ repo, args: ["checkout", "-b", branch] });
92
+ await writeFile(join(repo, `stack-${index}.txt`), `${branch}\n`);
93
+ git({ repo, args: ["add", "."] });
94
+ git({ repo, args: ["commit", "-m", branch] });
95
+ }
96
+
97
+ return {
98
+ repo,
99
+ mergedSha: git({ repo, args: ["rev-parse", "stack/merged"] }),
100
+ closedSha: git({ repo, args: ["rev-parse", "stack/closed"] }),
101
+ openSha: git({ repo, args: ["rev-parse", "stack/open"] }),
102
+ };
103
+ }
104
+
105
+ async function withFakeGt<T>({
106
+ directory,
107
+ operation,
108
+ output,
109
+ }: {
110
+ directory: string;
111
+ operation: (outputPath: string) => Promise<T>;
112
+ output: string;
113
+ }): Promise<T> {
114
+ const bin = join(directory, "bin");
115
+ const outputPath = join(directory, "gt-output.txt");
116
+ await mkdir(bin);
117
+ await writeFile(outputPath, output);
118
+ const gt = join(bin, "gt");
119
+ await writeFile(
120
+ gt,
121
+ `#!/usr/bin/env bash
122
+ set -euo pipefail
123
+ if [ "$(pwd -P)" != "${realpathSync(join(directory, "repo"))}" ]; then
124
+ printf 'gt ran outside the fixture repo: %s\\n' "$(pwd -P)" >&2
125
+ exit 2
126
+ fi
127
+ case "$*" in
128
+ "--no-interactive log short --stack --reverse")
129
+ cat "${outputPath}"
130
+ ;;
131
+ "--no-interactive info stack/merged")
132
+ printf 'stack/merged\\nPR #10 (Merged) merged change\\n'
133
+ ;;
134
+ "--no-interactive info stack/closed")
135
+ printf 'stack/closed\\nPR #13 (Closed) closed change\\n'
136
+ ;;
137
+ "--no-interactive info stack/open")
138
+ printf 'stack/open\\nPR #11 (Needs approvals) open change\\n'
139
+ ;;
140
+ *)
141
+ printf 'unexpected gt arguments: %s\\n' "$*" >&2
142
+ exit 2
143
+ ;;
144
+ esac
145
+ `
146
+ );
147
+ await chmod(gt, 0o755);
148
+
149
+ const originalPath = process.env.PATH;
150
+ process.env.PATH = `${bin}:${originalPath ?? ""}`;
151
+ try {
152
+ return await operation(outputPath);
153
+ } finally {
154
+ if (originalPath === undefined) {
155
+ delete process.env.PATH;
156
+ } else {
157
+ process.env.PATH = originalPath;
158
+ }
159
+ }
160
+ }
161
+
162
+ function runCli(
163
+ args: readonly string[],
164
+ env: Readonly<Record<string, string | undefined>> = process.env
165
+ ): RunResult {
166
+ const result = Bun.spawnSync([process.execPath, SCRIPT, ...args], { env });
167
+ return {
168
+ code: result.exitCode,
169
+ stdout: result.stdout.toString(),
170
+ stderr: result.stderr.toString(),
171
+ };
172
+ }
173
+
174
+ afterEach(async () => {
175
+ for (const store of handles.splice(0).reverse()) {
176
+ await store.close();
177
+ }
178
+ for (const directory of directories.splice(0)) {
179
+ await rm(directory, { recursive: true, force: true });
180
+ }
181
+ });
182
+
183
+ describe("Store", () => {
184
+ it("initializes an idempotent plain-file store and releases its lock", async () => {
185
+ const directory = await makeDirectory();
186
+ const store = useStore(directory);
187
+
188
+ expect(await store.init()).toEqual({ store: directory });
189
+ const firstUnits = await readFile(join(directory, "units.tsv"), "utf8");
190
+ const firstLedger = await readFile(
191
+ join(directory, "ledger.tsv"),
192
+ "utf8"
193
+ );
194
+
195
+ expect(await store.init()).toEqual({ store: directory });
196
+ expect(await readFile(join(directory, "units.tsv"), "utf8")).toBe(
197
+ firstUnits
198
+ );
199
+ expect(await readFile(join(directory, "ledger.tsv"), "utf8")).toBe(
200
+ firstLedger
201
+ );
202
+ expect((await readdir(directory)).sort()).toEqual([
203
+ ".orch.lock",
204
+ "frontier.json",
205
+ "gates.md",
206
+ "inbox",
207
+ "ledger.tsv",
208
+ "preferences.md",
209
+ "units.tsv",
210
+ ]);
211
+
212
+ await store.close();
213
+ expect(await readdir(directory)).not.toContain(".orch.lock");
214
+ });
215
+
216
+ it("composes unit add, set, get, list, and counts", async () => {
217
+ const { store } = await initializedStore();
218
+
219
+ expect(
220
+ await store.units.add({
221
+ id: "u1",
222
+ track: "build",
223
+ brief: "briefs/u1.md",
224
+ })
225
+ ).toMatchObject({ id: "u1", state: "pending" });
226
+ expect(
227
+ await store.units.add({ id: "=SUM(A1)", track: "+build" })
228
+ ).toMatchObject({ id: "'=SUM(A1)", track: "'+build" });
229
+
230
+ const updated = await store.units.set({
231
+ id: "u1",
232
+ state: "done",
233
+ branch: "poteto/u1",
234
+ pr: 184530,
235
+ sha: "abc123",
236
+ });
237
+ expect(updated).toEqual({
238
+ id: "u1",
239
+ track: "build",
240
+ state: "done",
241
+ branch: "poteto/u1",
242
+ pr: "184530",
243
+ sha: "abc123",
244
+ brief: "briefs/u1.md",
245
+ });
246
+ expect(await store.units.get("u1")).toEqual(updated);
247
+ expect(
248
+ await store.units.list({ state: "done", track: "build" })
249
+ ).toEqual([updated]);
250
+ expect(await store.units.counts()).toEqual({ done: 1, pending: 1 });
251
+ await expect(
252
+ store.units.add({ id: "u1", track: "build" })
253
+ ).rejects.toThrow("unit u1 already exists");
254
+ await expect(
255
+ store.units.set({ id: "missing", state: "done" })
256
+ ).rejects.toBeInstanceOf(NotFoundError);
257
+ });
258
+
259
+ it("records, replaces, checks, and summarizes typed ledger verdicts", async () => {
260
+ const { store } = await initializedStore();
261
+
262
+ try {
263
+ await store.ledger.check({ pr: 184530, sha: "abc123" });
264
+ throw new Error("expected ledger check to fail");
265
+ } catch (error) {
266
+ expect(error).toBeInstanceOf(NotFoundError);
267
+ if (error instanceof NotFoundError) {
268
+ expect(error.output).toEqual({
269
+ compact: "NOT-VERIFIED",
270
+ json: {
271
+ pr: "184530",
272
+ sha: "abc123",
273
+ verdict: "NOT-VERIFIED",
274
+ },
275
+ });
276
+ }
277
+ }
278
+ expect(() => parseVerdict("looks-good")).toThrow("verdict must be");
279
+
280
+ const recorded = await store.ledger.record({
281
+ pr: 184530,
282
+ sha: "abc123",
283
+ verdict: "unit-test-verified",
284
+ evidence: "reports/verify.md",
285
+ verifier: "sol",
286
+ });
287
+ expect(await store.ledger.check({ pr: 184530, sha: "abc123" })).toEqual(
288
+ recorded
289
+ );
290
+ expect(await store.ledger.summary()).toEqual({
291
+ "unit-test-verified": 1,
292
+ });
293
+
294
+ await store.ledger.record({
295
+ pr: 184530,
296
+ sha: "abc123",
297
+ verdict: "live-ui-verified",
298
+ evidence: "reports/live.md",
299
+ });
300
+ expect(await store.ledger.summary()).toEqual({
301
+ "live-ui-verified": 1,
302
+ });
303
+ });
304
+
305
+ it("pushes, peeks, and atomically drains inbox pointers", async () => {
306
+ const { directory, store } = await initializedStore();
307
+
308
+ const first = await store.inbox.push({
309
+ agent: "worker-1",
310
+ unit: "u1",
311
+ status: "done",
312
+ report: "reports/u1.md",
313
+ });
314
+ expect(first.pointer).toMatchObject({ unit: "u1", status: "done" });
315
+ expect(first.filename).toEndWith(".tsv");
316
+ await store.inbox.push({
317
+ agent: "worker-2",
318
+ unit: "u2",
319
+ status: "failed",
320
+ });
321
+
322
+ expect(await store.inbox.count()).toBe(2);
323
+ expect(await store.inbox.peek()).toHaveLength(2);
324
+ expect(await store.inbox.count()).toBe(2);
325
+ expect(await store.inbox.drain()).toHaveLength(2);
326
+ expect(await store.inbox.count()).toBe(0);
327
+ expect(await readdir(join(directory, "inbox"))).toEqual([]);
328
+ expect(
329
+ (await readdir(directory)).filter((name) =>
330
+ name.startsWith(".inbox-drain-")
331
+ )
332
+ ).toEqual([]);
333
+ });
334
+
335
+ it("replaces a stale lock whose holder pid is dead", async () => {
336
+ const { directory } = await initializedStore();
337
+ const exited = Bun.spawn(["true"]);
338
+ await exited.exited;
339
+ await writeFile(join(directory, ".orch.lock"), `${exited.pid}\n`);
340
+
341
+ const stale: string[] = [];
342
+ const recovered = useStore(directory, {
343
+ onStaleLock: (holder) => stale.push(holder),
344
+ });
345
+ expect(
346
+ await recovered.units.add({ id: "u1", track: "build" })
347
+ ).toMatchObject({ id: "u1" });
348
+ expect(stale).toEqual([String(exited.pid)]);
349
+ await recovered.close();
350
+ expect(await readdir(directory)).not.toContain(".orch.lock");
351
+ });
352
+
353
+ it("blocks a writer and steals the pid lock only with force", async () => {
354
+ const { directory, store } = await initializedStore();
355
+ await store.close();
356
+ await writeFile(join(directory, ".orch.lock"), `${process.pid}\n`);
357
+
358
+ const blocked = useStore(directory);
359
+ await expect(
360
+ blocked.units.add({ id: "u1", track: "build" })
361
+ ).rejects.toThrow(`store lock held by pid ${process.pid}`);
362
+
363
+ const stolen: string[] = [];
364
+ const forced = useStore(directory, {
365
+ force: true,
366
+ onLockStolen: (holder) => stolen.push(holder),
367
+ });
368
+ expect(
369
+ await forced.units.add({ id: "u1", track: "build" })
370
+ ).toMatchObject({ id: "u1" });
371
+ expect(stolen).toEqual([String(process.pid)]);
372
+ await forced.close();
373
+ expect(await readdir(directory)).not.toContain(".orch.lock");
374
+ });
375
+
376
+ it("parks gates, stores standing orders, and renders status", async () => {
377
+ const { directory, store } = await initializedStore();
378
+ await store.units.add({ id: "u1", track: "build" });
379
+ expect(
380
+ await store.gates.park({
381
+ id: "release",
382
+ question: "Ship now?",
383
+ options: "ship,wait",
384
+ defaultAnswer: "wait",
385
+ })
386
+ ).toMatchObject({ kind: "open", id: "release" });
387
+ expect(
388
+ await store.standing.add({ line: "Never force push." })
389
+ ).toEqual({ number: 1, line: "Never force push." });
390
+
391
+ const first = await store.status.render();
392
+ expect(first.changed).toBe("first render");
393
+ expect(first.summary.openGateIds).toEqual(["release"]);
394
+ expect(await readFile(join(directory, "status.md"), "utf8")).toContain(
395
+ "| release | open | Ship now? |"
396
+ );
397
+ expect((await store.status.render()).changed).toBe("no derived changes");
398
+
399
+ expect(
400
+ await store.gates.resolve({ id: "release", answer: "ship" })
401
+ ).toMatchObject({ kind: "resolved", answer: "ship" });
402
+ expect((await store.status.render()).changed).toBe("open gates 1->0");
403
+ expect(await store.gates.list()).toEqual([]);
404
+ expect(await store.standing.show()).toEqual([
405
+ { number: 1, line: "Never force push." },
406
+ ]);
407
+ });
408
+
409
+ it("resolves the ordered Graphite frontier and validates an optional pin", async () => {
410
+ const { directory, store } = await initializedStore();
411
+ const stack = await makeGitStack(directory);
412
+ const output = `◯ main
413
+ ◯ stack/merged
414
+ ◯ stack/closed
415
+ ◉ stack/open (current)
416
+ `;
417
+
418
+ await withFakeGt({
419
+ directory,
420
+ output,
421
+ operation: async () => {
422
+ expect(await store.frontier.set({ repo: stack.repo })).toEqual({
423
+ generation: 1,
424
+ prs: [
425
+ {
426
+ pr: 10,
427
+ branches: "stack/merged",
428
+ sha: stack.mergedSha,
429
+ state: "MERGED",
430
+ },
431
+ {
432
+ pr: 13,
433
+ branches: "stack/closed",
434
+ sha: stack.closedSha,
435
+ state: "CLOSED",
436
+ },
437
+ {
438
+ pr: 11,
439
+ branches: "stack/open",
440
+ sha: stack.openSha,
441
+ state: "OPEN",
442
+ },
443
+ ],
444
+ lowestUnmerged: 11,
445
+ });
446
+ expect(
447
+ (
448
+ await store.frontier.set({
449
+ repo: stack.repo,
450
+ prs: [10, 13, 11],
451
+ })
452
+ ).generation
453
+ ).toBe(2);
454
+ expect((await store.frontier.show()).generation).toBe(2);
455
+ await expect(
456
+ store.frontier.set({
457
+ repo: stack.repo,
458
+ prs: [10, 11, 12],
459
+ })
460
+ ).rejects.toThrow(
461
+ "frontier pin mismatch: missing from gt: 12; extra in gt: 13"
462
+ );
463
+ await expect(
464
+ store.frontier.set({
465
+ repo: stack.repo,
466
+ prs: [13, 10, 11],
467
+ })
468
+ ).rejects.toThrow(
469
+ "frontier pin mismatch: order differs: expected 13,10,11; gt 10,13,11"
470
+ );
471
+ await expect(
472
+ store.frontier.set({
473
+ repo: stack.repo,
474
+ prs: [10, 10],
475
+ })
476
+ ).rejects.toThrow("--prs must not contain duplicates");
477
+ },
478
+ });
479
+ });
480
+
481
+ it("rejects unparseable Graphite output loudly", async () => {
482
+ const { directory, store } = await initializedStore();
483
+ const stack = await makeGitStack(directory);
484
+
485
+ await withFakeGt({
486
+ directory,
487
+ output: "◯ main\nthis line is not Graphite output\n",
488
+ operation: async () => {
489
+ await expect(
490
+ store.frontier.set({ repo: stack.repo })
491
+ ).rejects.toThrow(
492
+ 'gt log short output has an unparseable line 2: "this line is not Graphite output"'
493
+ );
494
+ },
495
+ });
496
+ });
497
+
498
+ it("rejects malformed TSV, verdict, frontier, and inbox data", async () => {
499
+ const { directory, store } = await initializedStore();
500
+
501
+ await writeFile(join(directory, "units.tsv"), "wrong\n");
502
+ await expect(store.units.list()).rejects.toThrow(
503
+ "units.tsv has an invalid header"
504
+ );
505
+ await writeFile(
506
+ join(directory, "units.tsv"),
507
+ "id\ttrack\tstate\tbranch\tpr\tsha\tbrief\nshort\trow\n"
508
+ );
509
+ await expect(store.units.list()).rejects.toThrow(
510
+ "units.tsv has a malformed row"
511
+ );
512
+
513
+ await writeFile(
514
+ join(directory, "ledger.tsv"),
515
+ "pr\tsha\tverdict\tevidence\tverifier\tts\n1\tsha\tinvalid\treport\tme\tnow\n"
516
+ );
517
+ await expect(store.ledger.summary()).rejects.toThrow(
518
+ "ledger.tsv has invalid verdict invalid"
519
+ );
520
+
521
+ await writeFile(join(directory, "frontier.json"), '{"generation":"1"}\n');
522
+ await expect(store.frontier.show()).rejects.toThrow(
523
+ "frontier.json has an invalid shape"
524
+ );
525
+
526
+ await writeFile(join(directory, "inbox", "bad.tsv"), "too\tshort\n");
527
+ await expect(store.inbox.peek()).rejects.toThrow(
528
+ "inbox pointer bad.tsv is malformed"
529
+ );
530
+ });
531
+
532
+ it("rejects operations after close", async () => {
533
+ const { store } = await initializedStore();
534
+ await store.close();
535
+ await expect(store.units.list()).rejects.toThrow("store is closed");
536
+ await expect(store.status.render()).rejects.toBeInstanceOf(UserError);
537
+ });
538
+ });
539
+
540
+ describe("orch CLI", () => {
541
+ it("prints commander help and rejects invalid parsing with exit 1", async () => {
542
+ const help = runCli(["--help"]);
543
+ expect(help.code).toBe(0);
544
+ expect(help.stdout).toContain("Commands:");
545
+ expect(help.stdout).toContain("unit");
546
+ expect(help.stdout).toContain("ledger");
547
+
548
+ const frontierHelp = runCli(["frontier", "set", "--help"]);
549
+ expect(frontierHelp.code).toBe(0);
550
+ expect(frontierHelp.stdout).toContain("--repo <dir>");
551
+ expect(frontierHelp.stdout).toContain("--prs <n,...>");
552
+
553
+ const directory = await makeDirectory();
554
+ const invalid = runCli(["--store", directory, "unit", "add", "u1"]);
555
+ expect(invalid.code).toBe(1);
556
+ expect(invalid.stderr).toContain("required option '--track <track>'");
557
+ });
558
+
559
+ it("accepts ORCH_STORE and emits complete JSON", async () => {
560
+ const directory = await makeDirectory();
561
+ const env = { ...process.env, ORCH_STORE: directory };
562
+ expect(runCli(["init"], env).code).toBe(0);
563
+
564
+ const added = runCli(
565
+ ["unit", "add", "u1", "--track", "build", "--json"],
566
+ env
567
+ );
568
+ expect(added.code).toBe(0);
569
+ expect(JSON.parse(added.stdout)).toEqual({
570
+ id: "u1",
571
+ track: "build",
572
+ state: "pending",
573
+ branch: "",
574
+ pr: "",
575
+ sha: "",
576
+ brief: "",
577
+ });
578
+ });
579
+
580
+ it("maps user and not-found outcomes to the preserved exit codes", async () => {
581
+ const directory = await makeDirectory();
582
+ expect(runCli(["--store", directory, "init"]).code).toBe(0);
583
+
584
+ const missingRepo = runCli([
585
+ "--store",
586
+ directory,
587
+ "frontier",
588
+ "set",
589
+ ]);
590
+ expect(missingRepo.code).toBe(1);
591
+ expect(missingRepo.stderr).toContain(
592
+ "set --repo <dir> or ORCH_REPO"
593
+ );
594
+
595
+ const userError = runCli([
596
+ "--store",
597
+ directory,
598
+ "unit",
599
+ "add",
600
+ "",
601
+ "--track",
602
+ "build",
603
+ ]);
604
+ expect(userError.code).toBe(1);
605
+ expect(userError.stderr).toContain("unit id must not be empty");
606
+
607
+ const missingUnit = runCli([
608
+ "--store",
609
+ directory,
610
+ "unit",
611
+ "get",
612
+ "missing",
613
+ ]);
614
+ expect(missingUnit.code).toBe(2);
615
+ expect(missingUnit.stderr).toContain("unit missing not found");
616
+
617
+ const missingLedger = runCli([
618
+ "--store",
619
+ directory,
620
+ "--json",
621
+ "ledger",
622
+ "check",
623
+ "184530",
624
+ "abc123",
625
+ ]);
626
+ expect(missingLedger.code).toBe(2);
627
+ expect(JSON.parse(missingLedger.stdout)).toEqual({
628
+ pr: "184530",
629
+ sha: "abc123",
630
+ verdict: "NOT-VERIFIED",
631
+ });
632
+ expect(missingLedger.stderr).toBe("");
633
+ });
634
+ });