@wopr-network/defcon 0.2.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 (243) hide show
  1. package/README.md +274 -0
  2. package/dist/api/router.d.ts +24 -0
  3. package/dist/api/router.js +44 -0
  4. package/dist/api/server.d.ts +13 -0
  5. package/dist/api/server.js +280 -0
  6. package/dist/api/wire-types.d.ts +46 -0
  7. package/dist/api/wire-types.js +5 -0
  8. package/dist/config/db-path.d.ts +1 -0
  9. package/dist/config/db-path.js +1 -0
  10. package/dist/config/exporter.d.ts +3 -0
  11. package/dist/config/exporter.js +87 -0
  12. package/dist/config/index.d.ts +4 -0
  13. package/dist/config/index.js +4 -0
  14. package/dist/config/seed-loader.d.ts +10 -0
  15. package/dist/config/seed-loader.js +108 -0
  16. package/dist/config/zod-schemas.d.ts +165 -0
  17. package/dist/config/zod-schemas.js +283 -0
  18. package/dist/cors.d.ts +8 -0
  19. package/dist/cors.js +21 -0
  20. package/dist/engine/constants.d.ts +1 -0
  21. package/dist/engine/constants.js +1 -0
  22. package/dist/engine/engine.d.ts +69 -0
  23. package/dist/engine/engine.js +485 -0
  24. package/dist/engine/event-emitter.d.ts +9 -0
  25. package/dist/engine/event-emitter.js +19 -0
  26. package/dist/engine/event-types.d.ts +105 -0
  27. package/dist/engine/event-types.js +1 -0
  28. package/dist/engine/flow-spawner.d.ts +8 -0
  29. package/dist/engine/flow-spawner.js +28 -0
  30. package/dist/engine/gate-command-validator.d.ts +6 -0
  31. package/dist/engine/gate-command-validator.js +46 -0
  32. package/dist/engine/gate-evaluator.d.ts +12 -0
  33. package/dist/engine/gate-evaluator.js +233 -0
  34. package/dist/engine/handlebars.d.ts +9 -0
  35. package/dist/engine/handlebars.js +51 -0
  36. package/dist/engine/index.d.ts +12 -0
  37. package/dist/engine/index.js +7 -0
  38. package/dist/engine/invocation-builder.d.ts +18 -0
  39. package/dist/engine/invocation-builder.js +58 -0
  40. package/dist/engine/on-enter.d.ts +8 -0
  41. package/dist/engine/on-enter.js +102 -0
  42. package/dist/engine/ssrf-guard.d.ts +22 -0
  43. package/dist/engine/ssrf-guard.js +159 -0
  44. package/dist/engine/state-machine.d.ts +12 -0
  45. package/dist/engine/state-machine.js +74 -0
  46. package/dist/execution/active-runner.d.ts +45 -0
  47. package/dist/execution/active-runner.js +165 -0
  48. package/dist/execution/admin-schemas.d.ts +116 -0
  49. package/dist/execution/admin-schemas.js +125 -0
  50. package/dist/execution/cli.d.ts +57 -0
  51. package/dist/execution/cli.js +498 -0
  52. package/dist/execution/handlers/admin.d.ts +67 -0
  53. package/dist/execution/handlers/admin.js +200 -0
  54. package/dist/execution/handlers/flow.d.ts +25 -0
  55. package/dist/execution/handlers/flow.js +289 -0
  56. package/dist/execution/handlers/query.d.ts +31 -0
  57. package/dist/execution/handlers/query.js +64 -0
  58. package/dist/execution/index.d.ts +4 -0
  59. package/dist/execution/index.js +3 -0
  60. package/dist/execution/mcp-helpers.d.ts +42 -0
  61. package/dist/execution/mcp-helpers.js +23 -0
  62. package/dist/execution/mcp-server.d.ts +33 -0
  63. package/dist/execution/mcp-server.js +1020 -0
  64. package/dist/execution/provision-worktree.d.ts +16 -0
  65. package/dist/execution/provision-worktree.js +123 -0
  66. package/dist/execution/tool-schemas.d.ts +40 -0
  67. package/dist/execution/tool-schemas.js +44 -0
  68. package/dist/gates/blocking-graph.d.ts +26 -0
  69. package/dist/gates/blocking-graph.js +102 -0
  70. package/dist/gates/test/bad-return-gate.d.ts +1 -0
  71. package/dist/gates/test/bad-return-gate.js +4 -0
  72. package/dist/gates/test/passing-gate.d.ts +2 -0
  73. package/dist/gates/test/passing-gate.js +3 -0
  74. package/dist/gates/test/slow-gate.d.ts +2 -0
  75. package/dist/gates/test/slow-gate.js +5 -0
  76. package/dist/gates/test/throwing-gate.d.ts +1 -0
  77. package/dist/gates/test/throwing-gate.js +3 -0
  78. package/dist/logger.d.ts +8 -0
  79. package/dist/logger.js +12 -0
  80. package/dist/main.d.ts +14 -0
  81. package/dist/main.js +28 -0
  82. package/dist/repositories/drizzle/entity.repo.d.ts +27 -0
  83. package/dist/repositories/drizzle/entity.repo.js +190 -0
  84. package/dist/repositories/drizzle/event.repo.d.ts +12 -0
  85. package/dist/repositories/drizzle/event.repo.js +24 -0
  86. package/dist/repositories/drizzle/flow.repo.d.ts +22 -0
  87. package/dist/repositories/drizzle/flow.repo.js +364 -0
  88. package/dist/repositories/drizzle/gate.repo.d.ts +16 -0
  89. package/dist/repositories/drizzle/gate.repo.js +98 -0
  90. package/dist/repositories/drizzle/index.d.ts +6 -0
  91. package/dist/repositories/drizzle/index.js +7 -0
  92. package/dist/repositories/drizzle/invocation.repo.d.ts +23 -0
  93. package/dist/repositories/drizzle/invocation.repo.js +199 -0
  94. package/dist/repositories/drizzle/schema.d.ts +1932 -0
  95. package/dist/repositories/drizzle/schema.js +155 -0
  96. package/dist/repositories/drizzle/transition-log.repo.d.ts +11 -0
  97. package/dist/repositories/drizzle/transition-log.repo.js +42 -0
  98. package/dist/repositories/interfaces.d.ts +321 -0
  99. package/dist/repositories/interfaces.js +2 -0
  100. package/dist/src/api/router.d.ts +24 -0
  101. package/dist/src/api/router.js +44 -0
  102. package/dist/src/api/server.d.ts +13 -0
  103. package/dist/src/api/server.js +280 -0
  104. package/dist/src/api/wire-types.d.ts +46 -0
  105. package/dist/src/api/wire-types.js +5 -0
  106. package/dist/src/config/db-path.d.ts +1 -0
  107. package/dist/src/config/db-path.js +1 -0
  108. package/dist/src/config/exporter.d.ts +3 -0
  109. package/dist/src/config/exporter.js +87 -0
  110. package/dist/src/config/index.d.ts +4 -0
  111. package/dist/src/config/index.js +4 -0
  112. package/dist/src/config/seed-loader.d.ts +14 -0
  113. package/dist/src/config/seed-loader.js +131 -0
  114. package/dist/src/config/zod-schemas.d.ts +165 -0
  115. package/dist/src/config/zod-schemas.js +283 -0
  116. package/dist/src/cors.d.ts +8 -0
  117. package/dist/src/cors.js +21 -0
  118. package/dist/src/engine/constants.d.ts +1 -0
  119. package/dist/src/engine/constants.js +1 -0
  120. package/dist/src/engine/engine.d.ts +69 -0
  121. package/dist/src/engine/engine.js +485 -0
  122. package/dist/src/engine/event-emitter.d.ts +9 -0
  123. package/dist/src/engine/event-emitter.js +19 -0
  124. package/dist/src/engine/event-types.d.ts +105 -0
  125. package/dist/src/engine/event-types.js +1 -0
  126. package/dist/src/engine/flow-spawner.d.ts +8 -0
  127. package/dist/src/engine/flow-spawner.js +28 -0
  128. package/dist/src/engine/gate-command-validator.d.ts +6 -0
  129. package/dist/src/engine/gate-command-validator.js +46 -0
  130. package/dist/src/engine/gate-evaluator.d.ts +12 -0
  131. package/dist/src/engine/gate-evaluator.js +233 -0
  132. package/dist/src/engine/handlebars.d.ts +9 -0
  133. package/dist/src/engine/handlebars.js +51 -0
  134. package/dist/src/engine/index.d.ts +12 -0
  135. package/dist/src/engine/index.js +7 -0
  136. package/dist/src/engine/invocation-builder.d.ts +18 -0
  137. package/dist/src/engine/invocation-builder.js +58 -0
  138. package/dist/src/engine/on-enter.d.ts +8 -0
  139. package/dist/src/engine/on-enter.js +102 -0
  140. package/dist/src/engine/ssrf-guard.d.ts +22 -0
  141. package/dist/src/engine/ssrf-guard.js +159 -0
  142. package/dist/src/engine/state-machine.d.ts +12 -0
  143. package/dist/src/engine/state-machine.js +74 -0
  144. package/dist/src/execution/active-runner.d.ts +45 -0
  145. package/dist/src/execution/active-runner.js +165 -0
  146. package/dist/src/execution/admin-schemas.d.ts +116 -0
  147. package/dist/src/execution/admin-schemas.js +125 -0
  148. package/dist/src/execution/cli.d.ts +57 -0
  149. package/dist/src/execution/cli.js +501 -0
  150. package/dist/src/execution/handlers/admin.d.ts +67 -0
  151. package/dist/src/execution/handlers/admin.js +200 -0
  152. package/dist/src/execution/handlers/flow.d.ts +25 -0
  153. package/dist/src/execution/handlers/flow.js +289 -0
  154. package/dist/src/execution/handlers/query.d.ts +31 -0
  155. package/dist/src/execution/handlers/query.js +64 -0
  156. package/dist/src/execution/index.d.ts +4 -0
  157. package/dist/src/execution/index.js +3 -0
  158. package/dist/src/execution/mcp-helpers.d.ts +42 -0
  159. package/dist/src/execution/mcp-helpers.js +23 -0
  160. package/dist/src/execution/mcp-server.d.ts +33 -0
  161. package/dist/src/execution/mcp-server.js +1020 -0
  162. package/dist/src/execution/provision-worktree.d.ts +16 -0
  163. package/dist/src/execution/provision-worktree.js +123 -0
  164. package/dist/src/execution/tool-schemas.d.ts +40 -0
  165. package/dist/src/execution/tool-schemas.js +44 -0
  166. package/dist/src/logger.d.ts +8 -0
  167. package/dist/src/logger.js +12 -0
  168. package/dist/src/main.d.ts +14 -0
  169. package/dist/src/main.js +28 -0
  170. package/dist/src/repositories/drizzle/entity.repo.d.ts +27 -0
  171. package/dist/src/repositories/drizzle/entity.repo.js +190 -0
  172. package/dist/src/repositories/drizzle/event.repo.d.ts +12 -0
  173. package/dist/src/repositories/drizzle/event.repo.js +24 -0
  174. package/dist/src/repositories/drizzle/flow.repo.d.ts +22 -0
  175. package/dist/src/repositories/drizzle/flow.repo.js +364 -0
  176. package/dist/src/repositories/drizzle/gate.repo.d.ts +16 -0
  177. package/dist/src/repositories/drizzle/gate.repo.js +98 -0
  178. package/dist/src/repositories/drizzle/index.d.ts +6 -0
  179. package/dist/src/repositories/drizzle/index.js +7 -0
  180. package/dist/src/repositories/drizzle/invocation.repo.d.ts +23 -0
  181. package/dist/src/repositories/drizzle/invocation.repo.js +199 -0
  182. package/dist/src/repositories/drizzle/schema.d.ts +1932 -0
  183. package/dist/src/repositories/drizzle/schema.js +155 -0
  184. package/dist/src/repositories/drizzle/transition-log.repo.d.ts +11 -0
  185. package/dist/src/repositories/drizzle/transition-log.repo.js +42 -0
  186. package/dist/src/repositories/interfaces.d.ts +321 -0
  187. package/dist/src/repositories/interfaces.js +2 -0
  188. package/dist/src/utils/redact.d.ts +2 -0
  189. package/dist/src/utils/redact.js +62 -0
  190. package/dist/utils/redact.d.ts +2 -0
  191. package/dist/utils/redact.js +62 -0
  192. package/drizzle/.gitkeep +0 -0
  193. package/drizzle/0000_simple_surge.sql +144 -0
  194. package/drizzle/0001_peaceful_marvel_apes.sql +18 -0
  195. package/drizzle/0002_add_invocations_created_at.sql +1 -0
  196. package/drizzle/0003_drop_integration_config.sql +1 -0
  197. package/drizzle/0004_add_flow_discipline.sql +2 -0
  198. package/drizzle/0004_lucky_silverclaw.sql +5 -0
  199. package/drizzle/0005_old_blue_shield.sql +2 -0
  200. package/drizzle/0006_solid_magik.sql +2 -0
  201. package/drizzle/0007_fancy_luke_cage.sql +1 -0
  202. package/drizzle/0008_thick_dark_beast.sql +1 -0
  203. package/drizzle/0009_brief_midnight.sql +1 -0
  204. package/drizzle/0010_amusing_bastion.sql +1 -0
  205. package/drizzle/meta/0000_snapshot.json +996 -0
  206. package/drizzle/meta/0004_snapshot.json +1008 -0
  207. package/drizzle/meta/0005_snapshot.json +1023 -0
  208. package/drizzle/meta/0006_snapshot.json +1037 -0
  209. package/drizzle/meta/0007_snapshot.json +1044 -0
  210. package/drizzle/meta/0008_snapshot.json +1051 -0
  211. package/drizzle/meta/0009_snapshot.json +1058 -0
  212. package/drizzle/meta/0010_snapshot.json +1065 -0
  213. package/drizzle/meta/_journal.json +83 -0
  214. package/gates/.gitkeep +0 -0
  215. package/gates/blocking-graph.d.ts +26 -0
  216. package/gates/blocking-graph.js +102 -0
  217. package/gates/blocking-graph.ts +121 -0
  218. package/gates/check-design-posted.sh +39 -0
  219. package/gates/check-merge.sh +51 -0
  220. package/gates/check-pr-capacity.sh +17 -0
  221. package/gates/check-review-ready.sh +47 -0
  222. package/gates/check-spec-posted.sh +34 -0
  223. package/gates/check-unblocked.sh +56 -0
  224. package/gates/ci-green.sh +9 -0
  225. package/gates/merge-queue.sh +14 -0
  226. package/gates/review-bots-ready.sh +9 -0
  227. package/gates/spec-posted.sh +31 -0
  228. package/gates/test/bad-return-gate.d.ts +1 -0
  229. package/gates/test/bad-return-gate.js +4 -0
  230. package/gates/test/bad-return-gate.ts +4 -0
  231. package/gates/test/passing-gate.d.ts +2 -0
  232. package/gates/test/passing-gate.js +3 -0
  233. package/gates/test/passing-gate.ts +5 -0
  234. package/gates/test/slow-gate.d.ts +2 -0
  235. package/gates/test/slow-gate.js +5 -0
  236. package/gates/test/slow-gate.ts +7 -0
  237. package/gates/test/throwing-gate.d.ts +1 -0
  238. package/gates/test/throwing-gate.js +3 -0
  239. package/gates/test/throwing-gate.ts +3 -0
  240. package/gates/test-fail.sh +2 -0
  241. package/gates/test-pass.sh +2 -0
  242. package/gates/timeout-gate-script.sh +3 -0
  243. package/package.json +64 -0
@@ -0,0 +1,16 @@
1
+ export interface ProvisionWorktreeResult {
2
+ worktreePath: string;
3
+ branch: string;
4
+ repo: string;
5
+ }
6
+ export declare function parseIssueNumber(issueKey: string): string;
7
+ export declare function repoName(repo: string): string;
8
+ export declare function validateRepoName(name: string): string;
9
+ export declare function buildBranch(issueKey: string): string;
10
+ export declare function buildWorktreePath(repo: string, issueKey: string, basePath: string): string;
11
+ export declare function provisionWorktree(opts: {
12
+ repo: string;
13
+ issueKey: string;
14
+ basePath?: string;
15
+ cloneRoot?: string;
16
+ }): ProvisionWorktreeResult;
@@ -0,0 +1,123 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ export function parseIssueNumber(issueKey) {
6
+ const match = issueKey.match(/^[A-Za-z]+[-](\d+)$/);
7
+ if (!match) {
8
+ throw new Error(`Invalid issue key: ${issueKey}. Expected format: WOP-123`);
9
+ }
10
+ return match[1];
11
+ }
12
+ export function repoName(repo) {
13
+ const parts = repo.replace(/\/$/, "").split("/");
14
+ return parts[parts.length - 1] || repo;
15
+ }
16
+ export function validateRepoName(name) {
17
+ if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
18
+ throw new Error(`Invalid repo name: ${name}`);
19
+ }
20
+ if (name === "." || name === "..") {
21
+ throw new Error(`Invalid repo name: ${name}`);
22
+ }
23
+ return name;
24
+ }
25
+ export function buildBranch(issueKey) {
26
+ const num = parseIssueNumber(issueKey);
27
+ return `agent/coder-${num}/${issueKey.toLowerCase()}`;
28
+ }
29
+ export function buildWorktreePath(repo, issueKey, basePath) {
30
+ const name = repoName(repo);
31
+ const num = parseIssueNumber(issueKey);
32
+ return join(basePath, `wopr-${name}-coder-${num}`);
33
+ }
34
+ function run(cmd, args, cwd) {
35
+ try {
36
+ return execFileSync(cmd, args, {
37
+ cwd,
38
+ encoding: "utf-8",
39
+ stdio: ["pipe", "pipe", "pipe"],
40
+ }).trim();
41
+ }
42
+ catch (err) {
43
+ const e = err;
44
+ throw new Error(`${cmd} ${args.join(" ")} failed: ${e.stderr ?? e.message ?? String(err)}`);
45
+ }
46
+ }
47
+ export function provisionWorktree(opts) {
48
+ const basePath = opts.basePath ?? join(homedir(), "worktrees");
49
+ const cloneRoot = opts.cloneRoot ?? homedir();
50
+ const name = validateRepoName(repoName(opts.repo));
51
+ const clonePath = join(cloneRoot, name);
52
+ if (!resolve(clonePath).startsWith(`${resolve(cloneRoot)}/`) && resolve(clonePath) !== resolve(cloneRoot)) {
53
+ throw new Error(`Repo name escapes cloneRoot: ${name}`);
54
+ }
55
+ const worktreePath = buildWorktreePath(opts.repo, opts.issueKey, basePath);
56
+ const branch = buildBranch(opts.issueKey);
57
+ // Idempotent: if worktree already exists, verify and return
58
+ if (existsSync(worktreePath)) {
59
+ try {
60
+ run("git", ["rev-parse", "--git-dir"], worktreePath);
61
+ const currentBranch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
62
+ if (currentBranch !== branch) {
63
+ throw new Error(`Worktree at ${worktreePath} is on branch ${currentBranch}, expected ${branch}`);
64
+ }
65
+ const remoteUrl = run("git", ["remote", "get-url", "origin"], worktreePath);
66
+ const repoPath = opts.repo.replace(/\.git$/, "");
67
+ const urlMatchesRepo = remoteUrl.endsWith(`/${repoPath}`) ||
68
+ remoteUrl.endsWith(`/${repoPath}.git`) ||
69
+ remoteUrl.endsWith(`:${repoPath}`) ||
70
+ remoteUrl.endsWith(`:${repoPath}.git`);
71
+ if (!urlMatchesRepo) {
72
+ throw new Error(`Worktree at ${worktreePath} has unexpected remote: ${remoteUrl} (expected to contain ${opts.repo})`);
73
+ }
74
+ return { worktreePath, branch, repo: opts.repo };
75
+ }
76
+ catch (err) {
77
+ if (err instanceof Error && err.message.startsWith("Worktree at")) {
78
+ throw err;
79
+ }
80
+ throw new Error(`Path ${worktreePath} exists but is not a git worktree`);
81
+ }
82
+ }
83
+ // Clone if repo not present
84
+ if (!existsSync(clonePath)) {
85
+ const cloneUrl = `https://github.com/${opts.repo}.git`;
86
+ process.stderr.write(`Cloning ${cloneUrl} to ${clonePath}...\n`);
87
+ run("git", ["clone", cloneUrl, clonePath]);
88
+ }
89
+ // Fetch latest
90
+ process.stderr.write(`Fetching origin in ${clonePath}...\n`);
91
+ run("git", ["fetch", "origin"], clonePath);
92
+ // Create worktree
93
+ process.stderr.write(`Creating worktree at ${worktreePath}...\n`);
94
+ const defaultBranchRef = run("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], clonePath);
95
+ const defaultBranch = defaultBranchRef.replace("origin/", "");
96
+ try {
97
+ run("git", ["worktree", "add", worktreePath, "-b", branch, `origin/${defaultBranch}`], clonePath);
98
+ }
99
+ catch (err) {
100
+ // Branch may already exist — check if a worktree with the right branch is now present
101
+ if (existsSync(worktreePath)) {
102
+ try {
103
+ run("git", ["rev-parse", "--git-dir"], worktreePath);
104
+ const currentBranch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
105
+ if (currentBranch === branch) {
106
+ // Worktree already exists on the correct branch — idempotent success
107
+ return { worktreePath, branch, repo: opts.repo };
108
+ }
109
+ }
110
+ catch {
111
+ // fall through to rethrow original error
112
+ }
113
+ }
114
+ throw err;
115
+ }
116
+ // Install dependencies
117
+ const hasPnpmLock = existsSync(join(worktreePath, "pnpm-lock.yaml"));
118
+ const hasYarnLock = existsSync(join(worktreePath, "yarn.lock"));
119
+ const installCmd = hasPnpmLock ? "pnpm" : hasYarnLock ? "yarn" : "npm";
120
+ process.stderr.write(`Running ${installCmd} install in ${worktreePath}...\n`);
121
+ execFileSync(installCmd, ["install"], { cwd: worktreePath, stdio: "inherit" });
122
+ return { worktreePath, branch, repo: opts.repo };
123
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from "zod/v4";
2
+ export declare const FlowClaimSchema: z.ZodObject<{
3
+ worker_id: z.ZodOptional<z.ZodString>;
4
+ role: z.ZodString;
5
+ flow: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ export declare const FlowGetPromptSchema: z.ZodObject<{
8
+ entity_id: z.ZodString;
9
+ }, z.core.$strip>;
10
+ export declare const FlowReportSchema: z.ZodObject<{
11
+ entity_id: z.ZodString;
12
+ signal: z.ZodString;
13
+ artifacts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
14
+ worker_id: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>;
16
+ export declare const FlowFailSchema: z.ZodObject<{
17
+ entity_id: z.ZodString;
18
+ error: z.ZodString;
19
+ }, z.core.$strip>;
20
+ export declare const QueryEntitySchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ }, z.core.$strip>;
23
+ export declare const QueryEntitiesSchema: z.ZodObject<{
24
+ flow: z.ZodString;
25
+ state: z.ZodString;
26
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
27
+ }, z.core.$strip>;
28
+ export declare const QueryInvocationsSchema: z.ZodObject<{
29
+ entity_id: z.ZodString;
30
+ }, z.core.$strip>;
31
+ export declare const QueryFlowSchema: z.ZodObject<{
32
+ name: z.ZodString;
33
+ }, z.core.$strip>;
34
+ export declare const FlowSeedSchema: z.ZodObject<{
35
+ flow: z.ZodString;
36
+ refs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
37
+ adapter: z.ZodString;
38
+ id: z.ZodString;
39
+ }, z.core.$loose>>>;
40
+ }, z.core.$strip>;
@@ -0,0 +1,44 @@
1
+ import { z } from "zod/v4";
2
+ export const FlowClaimSchema = z.object({
3
+ worker_id: z.string().min(1).optional(),
4
+ role: z.string().min(1),
5
+ flow: z.string().min(1).optional(),
6
+ });
7
+ export const FlowGetPromptSchema = z.object({
8
+ entity_id: z.string().min(1),
9
+ });
10
+ export const FlowReportSchema = z.object({
11
+ entity_id: z.string().min(1),
12
+ signal: z.string().min(1),
13
+ artifacts: z.record(z.string(), z.unknown()).optional(),
14
+ worker_id: z.string().min(1).optional(),
15
+ });
16
+ export const FlowFailSchema = z.object({
17
+ entity_id: z.string().min(1),
18
+ error: z.string().min(1),
19
+ });
20
+ export const QueryEntitySchema = z.object({
21
+ id: z.string().min(1),
22
+ });
23
+ export const QueryEntitiesSchema = z.object({
24
+ flow: z.string().min(1),
25
+ state: z.string().min(1),
26
+ limit: z.coerce.number().int().min(1).max(100).optional(),
27
+ });
28
+ export const QueryInvocationsSchema = z.object({
29
+ entity_id: z.string().min(1),
30
+ });
31
+ export const QueryFlowSchema = z.object({
32
+ name: z.string().min(1),
33
+ });
34
+ export const FlowSeedSchema = z.object({
35
+ flow: z.string().min(1),
36
+ refs: z
37
+ .record(z.string(), z
38
+ .object({
39
+ adapter: z.string().min(1),
40
+ id: z.string().min(1),
41
+ })
42
+ .passthrough())
43
+ .optional(),
44
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Custom gate: checks whether all Linear blockers for an entity's issue
3
+ * have corresponding merged PRs on GitHub.
4
+ *
5
+ * Referenced by seed as: "gates/blocking-graph.ts:isUnblocked"
6
+ *
7
+ * NOTE: Function gates are not yet evaluated by the engine (gate-evaluator.ts
8
+ * throws for type "function"). This file exists as the implementation target
9
+ * for when function gate support lands.
10
+ *
11
+ * NOTE: This file is intentionally outside src/ — it is loaded dynamically at
12
+ * runtime, not compiled by the main build.
13
+ */
14
+ import type { Entity } from "../src/repositories/interfaces.js";
15
+ export interface BlockingGraphResult {
16
+ passed: boolean;
17
+ output: string;
18
+ }
19
+ /**
20
+ * Check if all blocking issues for the given entity have merged PRs.
21
+ *
22
+ * Expects entity.refs.linear.id to be the Linear issue ID.
23
+ * Uses LINEAR_API_KEY env var for authentication.
24
+ * Uses `gh` CLI to check PR merge status on GitHub.
25
+ */
26
+ export declare function isUnblocked(entity: Entity): Promise<BlockingGraphResult>;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Custom gate: checks whether all Linear blockers for an entity's issue
3
+ * have corresponding merged PRs on GitHub.
4
+ *
5
+ * Referenced by seed as: "gates/blocking-graph.ts:isUnblocked"
6
+ *
7
+ * NOTE: Function gates are not yet evaluated by the engine (gate-evaluator.ts
8
+ * throws for type "function"). This file exists as the implementation target
9
+ * for when function gate support lands.
10
+ *
11
+ * NOTE: This file is intentionally outside src/ — it is loaded dynamically at
12
+ * runtime, not compiled by the main build.
13
+ */
14
+ import { execFile } from "node:child_process";
15
+ import { promisify } from "node:util";
16
+ import { LinearClient } from "@linear/sdk";
17
+ const execFileAsync = promisify(execFile);
18
+ /**
19
+ * Check if all blocking issues for the given entity have merged PRs.
20
+ *
21
+ * Expects entity.refs.linear.id to be the Linear issue ID.
22
+ * Uses LINEAR_API_KEY env var for authentication.
23
+ * Uses `gh` CLI to check PR merge status on GitHub.
24
+ */
25
+ export async function isUnblocked(entity) {
26
+ const linearApiKey = process.env.LINEAR_API_KEY;
27
+ if (!linearApiKey) {
28
+ return { passed: false, output: "LINEAR_API_KEY not set" };
29
+ }
30
+ const issueId = entity.refs?.linear?.id;
31
+ if (!issueId) {
32
+ return { passed: false, output: "Entity has no linear ref" };
33
+ }
34
+ const client = new LinearClient({ apiKey: linearApiKey });
35
+ let issue;
36
+ let inverseRelations;
37
+ try {
38
+ issue = await client.issue(issueId);
39
+ inverseRelations = await issue.inverseRelations();
40
+ }
41
+ catch (err) {
42
+ const message = err instanceof Error ? err.message : String(err);
43
+ return { passed: false, output: `Linear API error: ${message}` };
44
+ }
45
+ const blockers = inverseRelations.nodes.filter((r) => r.type === "blocks");
46
+ if (blockers.length === 0) {
47
+ return { passed: true, output: "No blockers" };
48
+ }
49
+ const unmerged = [];
50
+ for (const relation of blockers) {
51
+ try {
52
+ const blockerIssue = await relation.issue;
53
+ if (!blockerIssue)
54
+ continue;
55
+ const identifier = blockerIssue.identifier;
56
+ // Resolve the PR via Linear attachment — the attachment URL tells us which repo
57
+ const attachments = await blockerIssue.attachments();
58
+ const prAttachment = attachments.nodes.find((a) => {
59
+ if (!a.url)
60
+ return false;
61
+ let hostname;
62
+ try {
63
+ hostname = new URL(a.url).hostname;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ return hostname === "github.com" && a.url.includes("/pull/");
69
+ });
70
+ if (!prAttachment?.url) {
71
+ unmerged.push(`${identifier} (no PR found)`);
72
+ continue;
73
+ }
74
+ const match = prAttachment.url.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
75
+ if (!match) {
76
+ unmerged.push(`${identifier} (unrecognized PR URL)`);
77
+ continue;
78
+ }
79
+ const [, repo, prNum] = match;
80
+ try {
81
+ const { stdout } = await execFileAsync("gh", ["pr", "view", prNum, "--repo", repo, "--json", "state", "--jq", ".state"], { timeout: 10000 });
82
+ if (stdout.trim() !== "MERGED") {
83
+ unmerged.push(identifier);
84
+ }
85
+ }
86
+ catch {
87
+ unmerged.push(`${identifier} (gh check failed)`);
88
+ }
89
+ }
90
+ catch (err) {
91
+ const errMsg = err instanceof Error ? err.message : String(err);
92
+ unmerged.push(`(blocker check failed: ${errMsg})`);
93
+ }
94
+ }
95
+ if (unmerged.length > 0) {
96
+ return {
97
+ passed: false,
98
+ output: `Blocked by unmerged: ${unmerged.join(", ")}`,
99
+ };
100
+ }
101
+ return { passed: true, output: `All ${blockers.length} blockers merged` };
102
+ }
@@ -0,0 +1 @@
1
+ export declare function check(): unknown;
@@ -0,0 +1,4 @@
1
+ // Fixture: returns wrong shape (no `passed` boolean) to test return-shape validation
2
+ export function check() {
3
+ return { status: "ok" }; // wrong shape — missing `passed`
4
+ }
@@ -0,0 +1,2 @@
1
+ import type { GateEvalResult } from "../../src/engine/gate-evaluator.js";
2
+ export declare function check(): GateEvalResult;
@@ -0,0 +1,3 @@
1
+ export function check() {
2
+ return { passed: true, output: "all good", timedOut: false };
3
+ }
@@ -0,0 +1,2 @@
1
+ import type { GateEvalResult } from "../../src/engine/gate-evaluator.js";
2
+ export declare function check(): Promise<GateEvalResult>;
@@ -0,0 +1,5 @@
1
+ export function check() {
2
+ return new Promise(() => {
3
+ // intentionally never resolves — for timeout testing
4
+ });
5
+ }
@@ -0,0 +1 @@
1
+ export declare function check(): never;
@@ -0,0 +1,3 @@
1
+ export function check() {
2
+ throw new Error("gate exploded");
3
+ }
@@ -0,0 +1,8 @@
1
+ export interface Logger {
2
+ error(msg: string, ...args: unknown[]): void;
3
+ warn(msg: string, ...args: unknown[]): void;
4
+ info(msg: string, ...args: unknown[]): void;
5
+ debug(msg: string, ...args: unknown[]): void;
6
+ }
7
+ export declare const consoleLogger: Logger;
8
+ export declare const noopLogger: Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,12 @@
1
+ export const consoleLogger = {
2
+ error: (msg, ...args) => console.error(msg, ...args),
3
+ warn: (msg, ...args) => console.warn(msg, ...args),
4
+ info: (msg, ...args) => console.info(msg, ...args),
5
+ debug: (msg, ...args) => console.debug(msg, ...args),
6
+ };
7
+ export const noopLogger = {
8
+ error: () => { },
9
+ warn: () => { },
10
+ info: () => { },
11
+ debug: () => { },
12
+ };
package/dist/main.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle } from "drizzle-orm/better-sqlite3";
3
+ import * as schema from "./repositories/drizzle/schema.js";
4
+ export declare function createDatabase(dbPath?: string): {
5
+ db: ReturnType<typeof drizzle<typeof schema>>;
6
+ sqlite: Database.Database;
7
+ };
8
+ export declare function runMigrations(db: ReturnType<typeof drizzle>, migrationsFolder?: string): void;
9
+ export declare function bootstrap(dbPath?: string): {
10
+ db: ReturnType<typeof drizzle>;
11
+ sqlite: Database.Database;
12
+ };
13
+ export * from "./api/wire-types.js";
14
+ export * from "./engine/index.js";
package/dist/main.js ADDED
@@ -0,0 +1,28 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle } from "drizzle-orm/better-sqlite3";
3
+ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
4
+ import { DB_PATH } from "./config/db-path.js";
5
+ import * as schema from "./repositories/drizzle/schema.js";
6
+ export function createDatabase(dbPath = DB_PATH) {
7
+ const sqlite = new Database(dbPath);
8
+ sqlite.pragma("journal_mode = WAL");
9
+ sqlite.pragma("foreign_keys = ON");
10
+ const db = drizzle(sqlite, { schema });
11
+ return { db, sqlite };
12
+ }
13
+ export function runMigrations(db, migrationsFolder = "./drizzle") {
14
+ migrate(db, { migrationsFolder });
15
+ }
16
+ export function bootstrap(dbPath = DB_PATH) {
17
+ const { db, sqlite } = createDatabase(dbPath);
18
+ try {
19
+ runMigrations(db);
20
+ }
21
+ catch (err) {
22
+ sqlite.close();
23
+ throw err;
24
+ }
25
+ return { db, sqlite };
26
+ }
27
+ export * from "./api/wire-types.js";
28
+ export * from "./engine/index.js";
@@ -0,0 +1,27 @@
1
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
+ import type { Artifacts, Entity, IEntityRepository, Refs } from "../interfaces.js";
3
+ import type * as schema from "./schema.js";
4
+ type Db = BetterSQLite3Database<typeof schema>;
5
+ export declare class DrizzleEntityRepository implements IEntityRepository {
6
+ private db;
7
+ constructor(db: Db);
8
+ private toEntity;
9
+ create(flowId: string, initialState: string, refs?: Refs): Promise<Entity>;
10
+ get(id: string): Promise<Entity | null>;
11
+ findByFlowAndState(flowId: string, state: string, limit?: number): Promise<Entity[]>;
12
+ hasAnyInFlowAndState(flowId: string, stateNames: string[]): Promise<boolean>;
13
+ transition(id: string, toState: string, _trigger: string, artifacts?: Partial<Artifacts>, _invocationId?: string | null): Promise<Entity>;
14
+ updateArtifacts(id: string, artifacts: Partial<Artifacts>): Promise<void>;
15
+ claim(flowId: string, state: string, agentId: string): Promise<Entity | null>;
16
+ claimById(entityId: string, agentId: string): Promise<Entity | null>;
17
+ release(entityId: string, agentId: string): Promise<void>;
18
+ appendSpawnedChild(parentId: string, entry: {
19
+ childId: string;
20
+ childFlow: string;
21
+ spawnedAt: string;
22
+ }): Promise<void>;
23
+ reapExpired(ttlMs: number): Promise<string[]>;
24
+ setAffinity(entityId: string, workerId: string, role: string, expiresAt: Date): Promise<void>;
25
+ clearExpiredAffinity(): Promise<string[]>;
26
+ }
27
+ export {};