@h-rig/runtime 0.0.6-alpha.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 (176) hide show
  1. package/README.md +27 -0
  2. package/dist/bin/rig-agent-dispatch.js +9615 -0
  3. package/dist/bin/rig-agent.js +9512 -0
  4. package/dist/bin/rig-browser-tool.js +269 -0
  5. package/dist/src/agent-mode.js +48 -0
  6. package/dist/src/baked-secrets.js +121 -0
  7. package/dist/src/binary-build-worker.js +312 -0
  8. package/dist/src/binary-run.js +540 -0
  9. package/dist/src/boundaries.js +1 -0
  10. package/dist/src/build-time-config.js +25 -0
  11. package/dist/src/control-plane/agent-roles.js +27 -0
  12. package/dist/src/control-plane/agent-wrapper.js +9621 -0
  13. package/dist/src/control-plane/authority-files.js +582 -0
  14. package/dist/src/control-plane/browser-contract.js +135 -0
  15. package/dist/src/control-plane/controlled-bash.js +1111 -0
  16. package/dist/src/control-plane/errors.js +13 -0
  17. package/dist/src/control-plane/harness-main.js +10828 -0
  18. package/dist/src/control-plane/hook-materializer.js +75 -0
  19. package/dist/src/control-plane/hooks/audit-trail.js +353 -0
  20. package/dist/src/control-plane/hooks/completion-verification.js +7552 -0
  21. package/dist/src/control-plane/hooks/import-guard.js +890 -0
  22. package/dist/src/control-plane/hooks/inject-context.js +4189 -0
  23. package/dist/src/control-plane/hooks/post-edit-lint.js +43 -0
  24. package/dist/src/control-plane/hooks/safety-guard.js +910 -0
  25. package/dist/src/control-plane/hooks/scope-guard.js +907 -0
  26. package/dist/src/control-plane/hooks/shared.js +44 -0
  27. package/dist/src/control-plane/hooks/submodule-branch.js +7797 -0
  28. package/dist/src/control-plane/hooks/task-runtime-start.js +7799 -0
  29. package/dist/src/control-plane/hooks/test-integrity-guard.js +891 -0
  30. package/dist/src/control-plane/materialize-task-config.js +453 -0
  31. package/dist/src/control-plane/memory-sync/cli.js +2019 -0
  32. package/dist/src/control-plane/memory-sync/db.js +753 -0
  33. package/dist/src/control-plane/memory-sync/embed.js +281 -0
  34. package/dist/src/control-plane/memory-sync/index.js +2049 -0
  35. package/dist/src/control-plane/memory-sync/query.js +294 -0
  36. package/dist/src/control-plane/memory-sync/read.js +784 -0
  37. package/dist/src/control-plane/memory-sync/types.js +6 -0
  38. package/dist/src/control-plane/memory-sync/write.js +1547 -0
  39. package/dist/src/control-plane/native/git-native.js +490 -0
  40. package/dist/src/control-plane/native/git-ops.js +2860 -0
  41. package/dist/src/control-plane/native/harness-cli.js +9721 -0
  42. package/dist/src/control-plane/native/pr-automation.js +373 -0
  43. package/dist/src/control-plane/native/profile-ops.js +481 -0
  44. package/dist/src/control-plane/native/repo-ops.js +2342 -0
  45. package/dist/src/control-plane/native/root-resolver.js +66 -0
  46. package/dist/src/control-plane/native/run-ops.js +3281 -0
  47. package/dist/src/control-plane/native/runtime-native-sidecar.js +299 -0
  48. package/dist/src/control-plane/native/runtime-native.js +392 -0
  49. package/dist/src/control-plane/native/scope-rules.js +17 -0
  50. package/dist/src/control-plane/native/task-ops.js +6320 -0
  51. package/dist/src/control-plane/native/task-state.js +1512 -0
  52. package/dist/src/control-plane/native/utils.js +535 -0
  53. package/dist/src/control-plane/native/validator-binaries.js +889 -0
  54. package/dist/src/control-plane/native/validator.js +2197 -0
  55. package/dist/src/control-plane/native/verifier.js +3249 -0
  56. package/dist/src/control-plane/native/workspace-ops.js +1635 -0
  57. package/dist/src/control-plane/plugin-host-context.js +334 -0
  58. package/dist/src/control-plane/project-main-pre-run-sync.js +630 -0
  59. package/dist/src/control-plane/provider/claude-stream-records.js +158 -0
  60. package/dist/src/control-plane/provider/codex-app-server.js +885 -0
  61. package/dist/src/control-plane/provider/codex-exec-records.js +203 -0
  62. package/dist/src/control-plane/provider/rig-task-run-skill.js +39 -0
  63. package/dist/src/control-plane/provider/runtime-instructions.js +96 -0
  64. package/dist/src/control-plane/remote.js +854 -0
  65. package/dist/src/control-plane/repos/index.js +473 -0
  66. package/dist/src/control-plane/repos/layout.js +124 -0
  67. package/dist/src/control-plane/repos/mirror/bootstrap.js +268 -0
  68. package/dist/src/control-plane/repos/mirror/refresh.js +398 -0
  69. package/dist/src/control-plane/repos/mirror/state.js +167 -0
  70. package/dist/src/control-plane/repos/registry.js +77 -0
  71. package/dist/src/control-plane/repos/types.js +1 -0
  72. package/dist/src/control-plane/runtime/agent-mode.js +48 -0
  73. package/dist/src/control-plane/runtime/baked-secrets.js +120 -0
  74. package/dist/src/control-plane/runtime/claude-tool-router-binary.js +343 -0
  75. package/dist/src/control-plane/runtime/claude-tool-router.js +520 -0
  76. package/dist/src/control-plane/runtime/context.js +216 -0
  77. package/dist/src/control-plane/runtime/events.js +218 -0
  78. package/dist/src/control-plane/runtime/guard-types.js +6 -0
  79. package/dist/src/control-plane/runtime/guard.js +880 -0
  80. package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +1194 -0
  81. package/dist/src/control-plane/runtime/image/index.js +2255 -0
  82. package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +1191 -0
  83. package/dist/src/control-plane/runtime/image.js +2255 -0
  84. package/dist/src/control-plane/runtime/index.js +8511 -0
  85. package/dist/src/control-plane/runtime/isolation/discovery.js +599 -0
  86. package/dist/src/control-plane/runtime/isolation/home.js +1217 -0
  87. package/dist/src/control-plane/runtime/isolation/index.js +8193 -0
  88. package/dist/src/control-plane/runtime/isolation/runner.js +2651 -0
  89. package/dist/src/control-plane/runtime/isolation/shared.js +501 -0
  90. package/dist/src/control-plane/runtime/isolation/toolchain.js +1892 -0
  91. package/dist/src/control-plane/runtime/isolation/types.js +1 -0
  92. package/dist/src/control-plane/runtime/isolation/worktree.js +509 -0
  93. package/dist/src/control-plane/runtime/isolation.js +8193 -0
  94. package/dist/src/control-plane/runtime/overlay.js +67 -0
  95. package/dist/src/control-plane/runtime/plugin-mode.js +41 -0
  96. package/dist/src/control-plane/runtime/plugins.js +1131 -0
  97. package/dist/src/control-plane/runtime/provisioning-env.js +220 -0
  98. package/dist/src/control-plane/runtime/queue.js +8358 -0
  99. package/dist/src/control-plane/runtime/rig-shell.js +205 -0
  100. package/dist/src/control-plane/runtime/rig-tools.js +182 -0
  101. package/dist/src/control-plane/runtime/runner-context.js +1 -0
  102. package/dist/src/control-plane/runtime/runtime-paths.js +184 -0
  103. package/dist/src/control-plane/runtime/sandbox/backend-bwrap.js +311 -0
  104. package/dist/src/control-plane/runtime/sandbox/backend-none.js +21 -0
  105. package/dist/src/control-plane/runtime/sandbox/backend-seatbelt.js +268 -0
  106. package/dist/src/control-plane/runtime/sandbox/backend.js +1718 -0
  107. package/dist/src/control-plane/runtime/sandbox/orchestrator.js +1745 -0
  108. package/dist/src/control-plane/runtime/sandbox/utils.js +137 -0
  109. package/dist/src/control-plane/runtime/sandbox-backend-bwrap.js +311 -0
  110. package/dist/src/control-plane/runtime/sandbox-backend-none.js +21 -0
  111. package/dist/src/control-plane/runtime/sandbox-backend-seatbelt.js +268 -0
  112. package/dist/src/control-plane/runtime/sandbox-backend.js +1718 -0
  113. package/dist/src/control-plane/runtime/sandbox-orchestrator.js +1745 -0
  114. package/dist/src/control-plane/runtime/sandbox-utils.js +137 -0
  115. package/dist/src/control-plane/runtime/snapshot/index.js +454 -0
  116. package/dist/src/control-plane/runtime/snapshot/sidecar.js +502 -0
  117. package/dist/src/control-plane/runtime/snapshot/task-run.js +1578 -0
  118. package/dist/src/control-plane/runtime/snapshot-sidecar.js +498 -0
  119. package/dist/src/control-plane/runtime/snapshot.js +454 -0
  120. package/dist/src/control-plane/runtime/task-run-snapshot.js +1578 -0
  121. package/dist/src/control-plane/runtime/tool-gateway.js +422 -0
  122. package/dist/src/control-plane/runtime/tooling/browser-tools.js +32 -0
  123. package/dist/src/control-plane/runtime/tooling/claude-router-binary.js +343 -0
  124. package/dist/src/control-plane/runtime/tooling/claude-router.js +524 -0
  125. package/dist/src/control-plane/runtime/tooling/file-tools.js +182 -0
  126. package/dist/src/control-plane/runtime/tooling/gateway.js +422 -0
  127. package/dist/src/control-plane/runtime/tooling/index.js +1290 -0
  128. package/dist/src/control-plane/runtime/tooling/shell.js +205 -0
  129. package/dist/src/control-plane/runtime/types.js +1 -0
  130. package/dist/src/control-plane/setup-version.js +14 -0
  131. package/dist/src/control-plane/state-sync/index.js +1509 -0
  132. package/dist/src/control-plane/state-sync/read.js +856 -0
  133. package/dist/src/control-plane/state-sync/reconcile.js +260 -0
  134. package/dist/src/control-plane/state-sync/repo.js +302 -0
  135. package/dist/src/control-plane/state-sync/types.js +111 -0
  136. package/dist/src/control-plane/state-sync/write.js +1469 -0
  137. package/dist/src/control-plane/task-fields.js +38 -0
  138. package/dist/src/control-plane/task-source-bootstrap.js +46 -0
  139. package/dist/src/control-plane/task-source.js +30 -0
  140. package/dist/src/control-plane/tasks/legacy-task-config-source.js +130 -0
  141. package/dist/src/control-plane/tasks/plugin-task-source.js +103 -0
  142. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +611 -0
  143. package/dist/src/control-plane/tasks/source-lifecycle.js +1093 -0
  144. package/dist/src/control-plane/tasks/task-record-reader.js +9 -0
  145. package/dist/src/control-plane/validators/boundary/public-apis.js +107 -0
  146. package/dist/src/control-plane/validators/integration/_shared.js +51 -0
  147. package/dist/src/control-plane/validators/integration/adm-audit-http.js +85 -0
  148. package/dist/src/control-plane/validators/integration/adm-auth-http.js +78 -0
  149. package/dist/src/control-plane/validators/integration/adm-issuer-http.js +80 -0
  150. package/dist/src/control-plane/validators/integration/adm-migration.js +78 -0
  151. package/dist/src/control-plane/validators/integration/adm-scaffold.js +78 -0
  152. package/dist/src/control-plane/validators/runtime-registration.js +64 -0
  153. package/dist/src/control-plane/validators/shared.js +683 -0
  154. package/dist/src/events.js +218 -0
  155. package/dist/src/execution.js +35 -0
  156. package/dist/src/index.js +1633 -0
  157. package/dist/src/layout.js +145 -0
  158. package/dist/src/local-server.js +202 -0
  159. package/dist/src/plugins.js +329 -0
  160. package/dist/src/remote-http.js +83 -0
  161. package/dist/src/runtime-context.js +216 -0
  162. package/dist/src/types.js +1 -0
  163. package/native/darwin-arm64/bin/rig-git +0 -0
  164. package/native/darwin-arm64/bin/rig-shell +0 -0
  165. package/native/darwin-arm64/bin/rig-tools +0 -0
  166. package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
  167. package/native/darwin-arm64/lib/runtime-native.dylib +0 -0
  168. package/native/darwin-arm64/manifest.json +1 -0
  169. package/native/linux-x64/bin/rig-git +0 -0
  170. package/native/linux-x64/bin/rig-shell +0 -0
  171. package/native/linux-x64/bin/rig-tools +0 -0
  172. package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
  173. package/native/linux-x64/lib/runtime-native.so +0 -0
  174. package/native/linux-x64/manifest.json +1 -0
  175. package/package.json +74 -0
  176. package/skills/rig-task-run.md +71 -0
@@ -0,0 +1,2197 @@
1
+ // @bun
2
+ // packages/runtime/src/control-plane/native/validator.ts
3
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4
+ import { resolve as resolve14 } from "path";
5
+
6
+ // packages/runtime/src/layout.ts
7
+ import { existsSync } from "fs";
8
+ import { basename, dirname, resolve } from "path";
9
+ var RIG_DEFINITION_DIRNAME = "rig";
10
+ var RIG_ARTIFACTS_DIRNAME = "artifacts";
11
+ function resolveMonorepoRoot(projectRoot) {
12
+ const normalizedProjectRoot = resolve(projectRoot);
13
+ const explicit = process.env.MONOREPO_ROOT?.trim();
14
+ if (explicit) {
15
+ const explicitRoot = resolve(explicit);
16
+ const explicitParent = dirname(explicitRoot);
17
+ if (basename(explicitParent) === ".worktrees") {
18
+ const owner = dirname(explicitParent);
19
+ const ownerHasGit = existsSync(resolve(owner, ".git"));
20
+ const ownerHasTaskConfig = existsSync(resolve(owner, ".rig", "task-config.json"));
21
+ const ownerHasRigConfig = existsSync(resolve(owner, "rig.config.ts"));
22
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
23
+ return owner;
24
+ }
25
+ throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
26
+ }
27
+ if (!existsSync(resolve(explicitRoot, ".git"))) {
28
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
29
+ }
30
+ const hasTaskConfig = existsSync(resolve(explicitRoot, ".rig", "task-config.json"));
31
+ const hasRigConfig = existsSync(resolve(explicitRoot, "rig.config.ts"));
32
+ if (!hasTaskConfig && !hasRigConfig) {
33
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
34
+ }
35
+ return explicitRoot;
36
+ }
37
+ const projectParent = dirname(normalizedProjectRoot);
38
+ if (basename(projectParent) === ".worktrees") {
39
+ const worktreeOwner = dirname(projectParent);
40
+ const ownerHasGit = existsSync(resolve(worktreeOwner, ".git"));
41
+ const ownerHasTaskConfig = existsSync(resolve(worktreeOwner, ".rig", "task-config.json"));
42
+ const ownerHasRigConfig = existsSync(resolve(worktreeOwner, "rig.config.ts"));
43
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
44
+ return worktreeOwner;
45
+ }
46
+ }
47
+ return normalizedProjectRoot;
48
+ }
49
+ function resolveRuntimeWorkspaceLayout(workspaceDir) {
50
+ const root = resolve(workspaceDir);
51
+ const rigRoot = resolve(root, ".rig");
52
+ const logsDir = resolve(rigRoot, "logs");
53
+ const stateDir = resolve(rigRoot, "state");
54
+ const runtimeDir = resolve(rigRoot, "runtime");
55
+ const binDir = resolve(rigRoot, "bin");
56
+ return {
57
+ workspaceDir: root,
58
+ rigRoot,
59
+ stateDir,
60
+ logsDir,
61
+ artifactsRoot: resolve(root, RIG_ARTIFACTS_DIRNAME),
62
+ runtimeDir,
63
+ homeDir: resolve(rigRoot, "home"),
64
+ tmpDir: resolve(rigRoot, "tmp"),
65
+ cacheDir: resolve(rigRoot, "cache"),
66
+ sessionDir: resolve(rigRoot, "session"),
67
+ binDir,
68
+ distDir: resolve(rigRoot, "dist"),
69
+ pluginBinDir: resolve(binDir, "plugins"),
70
+ contextPath: resolve(rigRoot, "runtime-context.json"),
71
+ controlPlaneEventsFile: resolve(logsDir, "control-plane.events.jsonl")
72
+ };
73
+ }
74
+ function resolveActiveRuntimeWorkspaceRoot(monorepoRoot) {
75
+ const explicit = process.env.RIG_TASK_WORKSPACE?.trim();
76
+ if (!explicit) {
77
+ throw new Error("No active runtime workspace. Set RIG_TASK_WORKSPACE or provision a task runtime first.");
78
+ }
79
+ return resolve(explicit);
80
+ }
81
+ function resolveRigLayout(projectRoot) {
82
+ const monorepoRoot = resolveMonorepoRoot(projectRoot);
83
+ const definitionRoot = resolve(projectRoot, RIG_DEFINITION_DIRNAME);
84
+ const runtimeWorkspaceRoot = resolveActiveRuntimeWorkspaceRoot(monorepoRoot);
85
+ const runtimeLayout = resolveRuntimeWorkspaceLayout(runtimeWorkspaceRoot);
86
+ const policyDir = resolve(definitionRoot, "policy");
87
+ return {
88
+ projectRoot,
89
+ monorepoRoot,
90
+ definitionRoot,
91
+ runtimeWorkspaceRoot,
92
+ stateRoot: runtimeLayout.rigRoot,
93
+ artifactsRoot: runtimeLayout.artifactsRoot,
94
+ configPath: resolve(definitionRoot, "config.sh"),
95
+ taskConfigPath: resolve(runtimeWorkspaceRoot, ".rig", "task-config.json"),
96
+ policyDir,
97
+ policyFile: resolve(policyDir, "policy.json"),
98
+ pluginsDir: resolve(definitionRoot, "plugins"),
99
+ hooksDir: resolve(definitionRoot, "hooks"),
100
+ toolsDir: resolve(definitionRoot, "tools"),
101
+ templatesDir: resolve(definitionRoot, "templates"),
102
+ validationDir: resolve(definitionRoot, "validation"),
103
+ stateDir: runtimeLayout.stateDir,
104
+ logsDir: runtimeLayout.logsDir,
105
+ notificationsDir: resolve(definitionRoot, "notifications"),
106
+ runtimeDir: runtimeLayout.runtimeDir,
107
+ distDir: runtimeLayout.distDir,
108
+ binDir: runtimeLayout.binDir,
109
+ pluginBinDir: runtimeLayout.pluginBinDir,
110
+ keybindingsPath: resolve(definitionRoot, "keybindings.json"),
111
+ controlPlaneEventsFile: runtimeLayout.controlPlaneEventsFile
112
+ };
113
+ }
114
+
115
+ // packages/runtime/src/control-plane/native/task-state.ts
116
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
117
+ import { basename as basename2, resolve as resolve6 } from "path";
118
+
119
+ // packages/runtime/src/control-plane/runtime/context.ts
120
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
121
+ import { dirname as dirname2, resolve as resolve2 } from "path";
122
+ var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
123
+ var runtimeContextStringFields = [
124
+ "runtimeId",
125
+ "taskId",
126
+ "role",
127
+ "workspaceDir",
128
+ "stateDir",
129
+ "logsDir",
130
+ "sessionDir",
131
+ "sessionFile",
132
+ "policyFile",
133
+ "binDir",
134
+ "createdAt"
135
+ ];
136
+ var runtimeContextArrayFields = ["scopes", "validation"];
137
+ var runtimeContextOptionalStringFields = [
138
+ "artifactRoot",
139
+ "hostProjectRoot",
140
+ "monorepoMainRoot",
141
+ "monorepoBaseRef",
142
+ "monorepoBaseCommit"
143
+ ];
144
+ function loadRuntimeContext(path) {
145
+ const absPath = resolve2(path);
146
+ if (!existsSync2(absPath)) {
147
+ throw new Error(`RuntimeTaskContext file not found: ${absPath}`);
148
+ }
149
+ let raw;
150
+ try {
151
+ raw = JSON.parse(readFileSync(absPath, "utf8"));
152
+ } catch (err) {
153
+ throw new Error(`Failed to parse RuntimeTaskContext at ${absPath}: ${String(err)}`);
154
+ }
155
+ if (typeof raw !== "object" || raw === null) {
156
+ throw new Error(`RuntimeTaskContext at ${absPath} is not an object`);
157
+ }
158
+ const obj = raw;
159
+ for (const field of runtimeContextStringFields) {
160
+ if (typeof obj[field] !== "string" || obj[field].length === 0) {
161
+ throw new Error(`RuntimeTaskContext field "${field}" must be a non-empty string (at ${absPath})`);
162
+ }
163
+ }
164
+ for (const field of runtimeContextArrayFields) {
165
+ if (!Array.isArray(obj[field])) {
166
+ throw new Error(`RuntimeTaskContext field "${field}" must be an array (at ${absPath})`);
167
+ }
168
+ if (!obj[field].every((entry) => typeof entry === "string")) {
169
+ throw new Error(`RuntimeTaskContext field "${field}" must be a string[] (at ${absPath})`);
170
+ }
171
+ }
172
+ for (const field of runtimeContextOptionalStringFields) {
173
+ if (field in obj && obj[field] !== undefined && typeof obj[field] !== "string") {
174
+ throw new Error(`RuntimeTaskContext field "${field}" must be a string when present (at ${absPath})`);
175
+ }
176
+ }
177
+ if (obj.browser !== undefined) {
178
+ if (typeof obj.browser !== "object" || obj.browser === null || Array.isArray(obj.browser)) {
179
+ throw new Error(`RuntimeTaskContext field "browser" must be an object when present (at ${absPath})`);
180
+ }
181
+ const browser = obj.browser;
182
+ for (const field of [
183
+ "preset",
184
+ "mode",
185
+ "stateDir",
186
+ "defaultProfile",
187
+ "effectiveProfile",
188
+ "defaultAttachUrl",
189
+ "effectiveAttachUrl",
190
+ "launchHelper",
191
+ "checkHelper",
192
+ "attachInfoHelper",
193
+ "e2eHelper",
194
+ "resetProfileHelper"
195
+ ]) {
196
+ if (typeof browser[field] !== "string" || browser[field].length === 0) {
197
+ throw new Error(`RuntimeTaskContext field "browser.${field}" must be a non-empty string (at ${absPath})`);
198
+ }
199
+ }
200
+ for (const field of ["devCommand", "launchCommand", "checkCommand", "e2eCommand"]) {
201
+ if (browser[field] !== undefined && typeof browser[field] !== "string") {
202
+ throw new Error(`RuntimeTaskContext field "browser.${field}" must be a string when present (at ${absPath})`);
203
+ }
204
+ }
205
+ if (typeof browser.required !== "boolean") {
206
+ throw new Error(`RuntimeTaskContext field "browser.required" must be a boolean (at ${absPath})`);
207
+ }
208
+ }
209
+ if (obj.memory !== undefined) {
210
+ if (typeof obj.memory !== "object" || obj.memory === null || Array.isArray(obj.memory)) {
211
+ throw new Error(`RuntimeTaskContext field "memory" must be an object when present (at ${absPath})`);
212
+ }
213
+ const memory = obj.memory;
214
+ for (const field of ["canonicalPath", "canonicalRef", "canonicalBaseOid", "hydratedPath"]) {
215
+ if (typeof memory[field] !== "string" || memory[field].length === 0) {
216
+ throw new Error(`RuntimeTaskContext field "memory.${field}" must be a non-empty string (at ${absPath})`);
217
+ }
218
+ }
219
+ if (typeof memory.createdFresh !== "boolean") {
220
+ throw new Error(`RuntimeTaskContext field "memory.createdFresh" must be a boolean (at ${absPath})`);
221
+ }
222
+ if (typeof memory.retrieval !== "object" || memory.retrieval === null || Array.isArray(memory.retrieval)) {
223
+ throw new Error(`RuntimeTaskContext field "memory.retrieval" must be an object (at ${absPath})`);
224
+ }
225
+ const retrieval = memory.retrieval;
226
+ for (const field of ["topK", "lexicalWeight", "vectorWeight", "recencyWeight", "confidenceWeight"]) {
227
+ if (typeof retrieval[field] !== "number" || Number.isNaN(retrieval[field])) {
228
+ throw new Error(`RuntimeTaskContext field "memory.retrieval.${field}" must be a number (at ${absPath})`);
229
+ }
230
+ }
231
+ }
232
+ if (obj.initialDirtyFiles !== undefined) {
233
+ if (typeof obj.initialDirtyFiles !== "object" || obj.initialDirtyFiles === null || Array.isArray(obj.initialDirtyFiles)) {
234
+ throw new Error(`RuntimeTaskContext field "initialDirtyFiles" must be an object when present (at ${absPath})`);
235
+ }
236
+ const dirtyFiles = obj.initialDirtyFiles;
237
+ for (const key of ["project", "monorepo"]) {
238
+ if (dirtyFiles[key] === undefined) {
239
+ continue;
240
+ }
241
+ if (!Array.isArray(dirtyFiles[key]) || !dirtyFiles[key].every((entry) => typeof entry === "string")) {
242
+ throw new Error(`RuntimeTaskContext field "initialDirtyFiles.${key}" must be a string[] when present (at ${absPath})`);
243
+ }
244
+ }
245
+ }
246
+ if (obj.initialHeadCommits !== undefined) {
247
+ if (typeof obj.initialHeadCommits !== "object" || obj.initialHeadCommits === null || Array.isArray(obj.initialHeadCommits)) {
248
+ throw new Error(`RuntimeTaskContext field "initialHeadCommits" must be an object when present (at ${absPath})`);
249
+ }
250
+ const headCommits = obj.initialHeadCommits;
251
+ for (const key of ["project", "monorepo"]) {
252
+ if (headCommits[key] === undefined) {
253
+ continue;
254
+ }
255
+ if (typeof headCommits[key] !== "string") {
256
+ throw new Error(`RuntimeTaskContext field "initialHeadCommits.${key}" must be a string when present (at ${absPath})`);
257
+ }
258
+ }
259
+ }
260
+ return obj;
261
+ }
262
+ function loadRuntimeContextFromEnv(env = process.env) {
263
+ const contextFile = env[RUNTIME_CONTEXT_ENV];
264
+ if (contextFile) {
265
+ return loadRuntimeContext(contextFile);
266
+ }
267
+ const inferred = findRuntimeContextFile(process.cwd());
268
+ if (!inferred) {
269
+ return null;
270
+ }
271
+ return loadRuntimeContext(inferred);
272
+ }
273
+ function findRuntimeContextFile(startPath) {
274
+ let current = resolve2(startPath);
275
+ while (true) {
276
+ const candidate = resolve2(current, "runtime-context.json");
277
+ if (existsSync2(candidate) && isAgentRuntimeContextPath(candidate)) {
278
+ return candidate;
279
+ }
280
+ const parent = dirname2(current);
281
+ if (parent === current) {
282
+ return "";
283
+ }
284
+ current = parent;
285
+ }
286
+ }
287
+ function isAgentRuntimeContextPath(path) {
288
+ const normalized = path.replace(/\\/g, "/");
289
+ return /\/\.rig\/runtime-context\.json$/.test(normalized);
290
+ }
291
+
292
+ // packages/runtime/src/control-plane/state-sync/types.ts
293
+ var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
294
+ "draft",
295
+ "open",
296
+ "ready",
297
+ "queued",
298
+ "in_progress",
299
+ "under_review",
300
+ "blocked",
301
+ "completed",
302
+ "cancelled"
303
+ ]);
304
+ // packages/runtime/src/control-plane/native/git-native.ts
305
+ import { tmpdir } from "os";
306
+ import { dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
307
+ var sharedGitNativeOutputDir = resolve3(tmpdir(), "rig-native");
308
+ var sharedGitNativeOutputPath = resolve3(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
309
+
310
+ // packages/runtime/src/control-plane/native/utils.ts
311
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
312
+ import { resolve as resolve5 } from "path";
313
+
314
+ // packages/runtime/src/control-plane/native/runtime-native.ts
315
+ import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
316
+ import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync, rmSync, statSync } from "fs";
317
+ import { tmpdir as tmpdir2 } from "os";
318
+ import { dirname as dirname4, resolve as resolve4 } from "path";
319
+ var sharedNativeRuntimeOutputDir = resolve4(tmpdir2(), "rig-native");
320
+ var sharedNativeRuntimeOutputPath = resolve4(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
321
+ var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
322
+ var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
323
+ async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
324
+ if (await buildNativeRuntimeLibrary(outputPath, options)) {
325
+ return outputPath;
326
+ }
327
+ return !options.force && existsSync3(outputPath) ? outputPath : null;
328
+ }
329
+ async function loadNativeRuntimeLibrary() {
330
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
331
+ return null;
332
+ }
333
+ for (const candidate of nativeRuntimeLibraryCandidates()) {
334
+ if (!candidate || !existsSync3(candidate)) {
335
+ continue;
336
+ }
337
+ const loaded = tryDlopenNativeRuntimeLibrary(candidate);
338
+ if (loaded) {
339
+ return loaded;
340
+ }
341
+ }
342
+ const builtLibraryPath = await ensureNativeRuntimeLibraryPath(sharedNativeRuntimeOutputPath, { force: true });
343
+ if (!builtLibraryPath) {
344
+ return null;
345
+ }
346
+ return tryDlopenNativeRuntimeLibrary(builtLibraryPath);
347
+ }
348
+ function nativePackageLibraryCandidates(fromDir, names) {
349
+ const candidates = [];
350
+ let cursor = resolve4(fromDir);
351
+ for (let index = 0;index < 8; index += 1) {
352
+ for (const name of names) {
353
+ candidates.push(resolve4(cursor, "native", `${process.platform}-${process.arch}`, name), resolve4(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve4(cursor, "native", name), resolve4(cursor, "native", "lib", name));
354
+ }
355
+ const parent = dirname4(cursor);
356
+ if (parent === cursor)
357
+ break;
358
+ cursor = parent;
359
+ }
360
+ return candidates;
361
+ }
362
+ function nativeRuntimeLibraryCandidates() {
363
+ const explicit = process.env.RIG_NATIVE_RUNTIME_LIB?.trim() || "";
364
+ const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
365
+ const platformSpecific = `runtime-native-${process.platform}-${process.arch}.${suffix}`;
366
+ return [...new Set([
367
+ explicit,
368
+ ...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
369
+ execDir ? resolve4(execDir, colocatedNativeRuntimeFileName) : "",
370
+ execDir ? resolve4(execDir, platformSpecific) : "",
371
+ execDir ? resolve4(execDir, "..", colocatedNativeRuntimeFileName) : "",
372
+ execDir ? resolve4(execDir, "..", platformSpecific) : "",
373
+ execDir ? resolve4(execDir, "lib", colocatedNativeRuntimeFileName) : "",
374
+ execDir ? resolve4(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
375
+ sharedNativeRuntimeOutputPath
376
+ ].filter(Boolean))];
377
+ }
378
+ function resolveNativeRuntimeSourcePath() {
379
+ const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
380
+ if (explicit && existsSync3(explicit)) {
381
+ return explicit;
382
+ }
383
+ const bundled = resolve4(import.meta.dir, "../../../native/snapshot.zig");
384
+ return existsSync3(bundled) ? bundled : null;
385
+ }
386
+ async function buildNativeRuntimeLibrary(outputPath, options = {}) {
387
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
388
+ return false;
389
+ }
390
+ const zigBinary = Bun.which("zig");
391
+ const sourcePath = resolveNativeRuntimeSourcePath();
392
+ if (!zigBinary || !sourcePath) {
393
+ return false;
394
+ }
395
+ const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
396
+ try {
397
+ mkdirSync2(dirname4(outputPath), { recursive: true });
398
+ const needsBuild = options.force === true || !existsSync3(outputPath) || statSync(sourcePath).mtimeMs > statSync(outputPath).mtimeMs;
399
+ if (!needsBuild) {
400
+ return true;
401
+ }
402
+ const build = Bun.spawn([
403
+ zigBinary,
404
+ "build-lib",
405
+ sourcePath,
406
+ "-dynamic",
407
+ "-O",
408
+ "ReleaseFast",
409
+ `-femit-bin=${tempOutputPath}`
410
+ ], {
411
+ cwd: import.meta.dir,
412
+ stdout: "pipe",
413
+ stderr: "pipe"
414
+ });
415
+ const exitCode = await build.exited;
416
+ if (exitCode !== 0 || !existsSync3(tempOutputPath)) {
417
+ rmSync(tempOutputPath, { force: true });
418
+ return false;
419
+ }
420
+ renameSync(tempOutputPath, outputPath);
421
+ return true;
422
+ } catch {
423
+ rmSync(tempOutputPath, { force: true });
424
+ return false;
425
+ }
426
+ }
427
+ function tryDlopenNativeRuntimeLibrary(outputPath) {
428
+ try {
429
+ return dlopen(outputPath, {
430
+ rig_scope_match: {
431
+ args: ["ptr", "ptr"],
432
+ returns: "u8"
433
+ },
434
+ snapshot_capture: {
435
+ args: ["ptr", "u64", "ptr", "u64"],
436
+ returns: "ptr"
437
+ },
438
+ snapshot_delta: {
439
+ args: ["ptr", "ptr"],
440
+ returns: "ptr"
441
+ },
442
+ snapshot_store_delta: {
443
+ args: ["ptr", "ptr", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
444
+ returns: "ptr"
445
+ },
446
+ snapshot_inspect_delta: {
447
+ args: ["ptr", "u64"],
448
+ returns: "ptr"
449
+ },
450
+ snapshot_apply_delta: {
451
+ args: ["ptr", "u64", "ptr", "u64"],
452
+ returns: "ptr"
453
+ },
454
+ snapshot_release: {
455
+ args: ["ptr"],
456
+ returns: "void"
457
+ },
458
+ runtime_hash_file: {
459
+ args: ["ptr", "u64"],
460
+ returns: "ptr"
461
+ },
462
+ runtime_hash_tree: {
463
+ args: ["ptr", "u64"],
464
+ returns: "ptr"
465
+ },
466
+ runtime_prepare_paths: {
467
+ args: ["ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
468
+ returns: "ptr"
469
+ },
470
+ runtime_link_dependency_layer: {
471
+ args: ["ptr", "u64", "ptr", "u64"],
472
+ returns: "ptr"
473
+ },
474
+ runtime_scan_worktrees: {
475
+ args: ["ptr", "u64"],
476
+ returns: "ptr"
477
+ }
478
+ });
479
+ } catch {
480
+ return null;
481
+ }
482
+ }
483
+
484
+ // packages/runtime/src/control-plane/native/scope-rules.ts
485
+ var activeRules = null;
486
+ function setScopeRules(rules) {
487
+ activeRules = rules ?? null;
488
+ }
489
+
490
+ // packages/runtime/src/control-plane/native/utils.ts
491
+ function resolveMonorepoRoot2(projectRoot) {
492
+ return resolveMonorepoRoot(projectRoot);
493
+ }
494
+ var scopeRegexCache = new Map;
495
+ var DEFAULT_VALIDATION_TIMEOUT_MS = 30000;
496
+ function getValidationTimeoutMs() {
497
+ const envVal = process.env.RIG_VALIDATION_TIMEOUT_MS;
498
+ if (envVal) {
499
+ const parsed = Number(envVal);
500
+ if (Number.isFinite(parsed) && parsed > 0) {
501
+ return parsed;
502
+ }
503
+ }
504
+ return DEFAULT_VALIDATION_TIMEOUT_MS;
505
+ }
506
+ async function runCaptureAsync(command, cwd, env, timeoutMs) {
507
+ const timeout = timeoutMs ?? getValidationTimeoutMs();
508
+ const proc = Bun.spawn(command, {
509
+ cwd,
510
+ env: env ? { ...process.env, ...env } : process.env,
511
+ stdout: "pipe",
512
+ stderr: "pipe"
513
+ });
514
+ const stdoutPromise = new Response(proc.stdout).text();
515
+ const stderrPromise = new Response(proc.stderr).text();
516
+ const timeoutResult = Symbol("timeout");
517
+ const exitOrTimeout = await Promise.race([
518
+ proc.exited,
519
+ Bun.sleep(timeout).then(() => timeoutResult)
520
+ ]);
521
+ if (exitOrTimeout === timeoutResult) {
522
+ proc.kill("SIGKILL");
523
+ const [stdout2, stderr2] = await Promise.all([
524
+ Promise.race([stdoutPromise, Bun.sleep(2000).then(() => "")]),
525
+ Promise.race([stderrPromise, Bun.sleep(2000).then(() => "")])
526
+ ]);
527
+ return {
528
+ exitCode: 124,
529
+ stdout: stdout2,
530
+ stderr: `${stderr2}
531
+ [TIMEOUT] Process killed after ${timeout}ms`
532
+ };
533
+ }
534
+ const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
535
+ const exitCode = exitOrTimeout;
536
+ return { exitCode, stdout, stderr };
537
+ }
538
+ function readJsonFile(path, fallback) {
539
+ if (!existsSync4(path)) {
540
+ return fallback;
541
+ }
542
+ try {
543
+ return JSON.parse(readFileSync2(path, "utf-8"));
544
+ } catch {
545
+ return fallback;
546
+ }
547
+ }
548
+ function nowIso() {
549
+ return new Date().toISOString();
550
+ }
551
+ function resolveHarnessPaths(projectRoot) {
552
+ const hasRuntimeWorkspace = Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
553
+ const monorepoRoot = resolveMonorepoRoot2(projectRoot);
554
+ const harnessRoot = resolve5(projectRoot, "rig");
555
+ const stateRoot = resolve5(projectRoot, ".rig");
556
+ const layout = hasRuntimeWorkspace ? resolveRigLayout(projectRoot) : null;
557
+ const stateDir = layout?.stateDir ?? resolve5(stateRoot, "state");
558
+ const logsDir = layout?.logsDir ?? resolve5(stateRoot, "logs");
559
+ const artifactsDir = layout?.artifactsRoot ?? resolve5(monorepoRoot, "artifacts");
560
+ const taskConfigPath = layout?.taskConfigPath ?? resolve5(monorepoRoot, ".rig", "task-config.json");
561
+ const binDir = layout?.binDir ?? resolve5(stateRoot, "bin");
562
+ return {
563
+ harnessRoot,
564
+ stateDir: process.env.RIG_STATE_DIR || stateDir,
565
+ artifactsDir,
566
+ logsDir: process.env.RIG_LOGS_DIR || logsDir,
567
+ binDir,
568
+ hooksDir: resolve5(harnessRoot, "hooks"),
569
+ validationDir: resolve5(harnessRoot, "validation"),
570
+ taskConfigPath,
571
+ sessionPath: process.env.RIG_SESSION_FILE || resolve5(stateRoot, "session", "session.json"),
572
+ monorepoRoot,
573
+ tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve5(monorepoRoot, "TSAPITests"),
574
+ taskRepoCommitsPath: resolve5(stateDir, "task-repo-commits.json"),
575
+ baseRepoPinsPath: resolve5(stateDir, "base-repo-pins.json"),
576
+ failedApproachesPath: resolve5(stateDir, "failed_approaches.md"),
577
+ agentProfilePath: resolve5(stateDir, "agent-profile.json"),
578
+ reviewProfilePath: resolve5(stateDir, "review-profile.json")
579
+ };
580
+ }
581
+
582
+ // packages/runtime/src/control-plane/repos/registry.ts
583
+ function createRepoRegistry(entries) {
584
+ const map = new Map;
585
+ for (const e of entries) {
586
+ if (map.has(e.id))
587
+ throw new Error(`repo already registered: ${e.id}`);
588
+ map.set(e.id, { ...e });
589
+ }
590
+ const ordered = Array.from(map.values());
591
+ return {
592
+ getById: (id) => map.get(id),
593
+ list: () => ordered
594
+ };
595
+ }
596
+ var MANAGED_REPOS = new Map;
597
+ function setManagedRepos(entries) {
598
+ const next = new Map;
599
+ for (const e of entries) {
600
+ if (next.has(e.id)) {
601
+ throw new Error(`managed repo already registered: ${e.id}`);
602
+ }
603
+ next.set(e.id, e);
604
+ }
605
+ MANAGED_REPOS = next;
606
+ }
607
+ function repoRegistrationToManagedEntry(reg) {
608
+ if (!reg.defaultBranch) {
609
+ return null;
610
+ }
611
+ return {
612
+ id: reg.id,
613
+ alias: reg.defaultPath ?? reg.id,
614
+ defaultBranch: reg.defaultBranch,
615
+ defaultRemoteUrl: reg.url,
616
+ remoteEnvVar: reg.remoteEnvVar,
617
+ checkoutEnvVar: reg.checkoutEnvVar
618
+ };
619
+ }
620
+ // packages/runtime/src/control-plane/state-sync/reconcile.ts
621
+ var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
622
+ // packages/runtime/src/control-plane/native/task-state.ts
623
+ function readTaskConfig(projectRoot) {
624
+ const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
625
+ return stripTaskConfigMetadata(raw);
626
+ }
627
+ function readValidationDescriptions(projectRoot) {
628
+ const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
629
+ return readValidationDescriptionMap(raw);
630
+ }
631
+ function readValidationDescriptionMap(raw) {
632
+ return {
633
+ ...coerceValidationDescriptions(raw.validation_descriptions),
634
+ ...coerceValidationDescriptions(readValidationDescriptionsFromMeta(raw._meta))
635
+ };
636
+ }
637
+ function stripTaskConfigMetadata(raw) {
638
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
639
+ return tasks;
640
+ }
641
+ function coerceValidationDescriptions(candidate) {
642
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
643
+ return {};
644
+ }
645
+ const descriptions = {};
646
+ for (const [key, value] of Object.entries(candidate)) {
647
+ if (typeof value === "string" && value.length > 0) {
648
+ descriptions[key] = value;
649
+ }
650
+ }
651
+ return descriptions;
652
+ }
653
+ function readValidationDescriptionsFromMeta(meta) {
654
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
655
+ return;
656
+ }
657
+ return meta.validation_descriptions;
658
+ }
659
+ function resolveTaskConfigPath(projectRoot) {
660
+ const paths = resolveHarnessPaths(projectRoot);
661
+ if (existsSync5(paths.taskConfigPath)) {
662
+ return paths.taskConfigPath;
663
+ }
664
+ for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
665
+ if (existsSync5(candidate)) {
666
+ return candidate;
667
+ }
668
+ }
669
+ throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
670
+ }
671
+ function sourceTaskConfigCandidates(projectRoot) {
672
+ const runtimeContext = loadRuntimeContextFromEnv();
673
+ return [
674
+ runtimeContext?.monorepoMainRoot ? resolve6(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
675
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve6(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
676
+ resolve6(resolveMonorepoRoot2(projectRoot), ".rig", "task-config.json")
677
+ ].filter(Boolean);
678
+ }
679
+
680
+ // packages/runtime/src/control-plane/plugin-host-context.ts
681
+ import { createPluginHost } from "@rig/core";
682
+ import { loadConfig } from "@rig/core/load-config";
683
+
684
+ // packages/runtime/src/control-plane/task-source.ts
685
+ function createTaskSourceRegistry() {
686
+ const byId = new Map;
687
+ const order = [];
688
+ return {
689
+ register(s) {
690
+ if (byId.has(s.id))
691
+ throw new Error(`task source already registered: ${s.id}`);
692
+ byId.set(s.id, s);
693
+ order.push(s);
694
+ },
695
+ resolveById(id) {
696
+ const s = byId.get(id);
697
+ if (!s)
698
+ throw new Error(`task source not registered: ${id}`);
699
+ return s;
700
+ },
701
+ resolveByKind(kind) {
702
+ for (const s of order)
703
+ if (s.kind === kind)
704
+ return s;
705
+ throw new Error(`no task source registered for kind: ${kind}`);
706
+ },
707
+ list: () => order
708
+ };
709
+ }
710
+
711
+ // packages/runtime/src/control-plane/task-source-bootstrap.ts
712
+ function formatRegisteredKinds(pluginHost) {
713
+ const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
714
+ return kinds.length > 0 ? kinds.join(", ") : "none";
715
+ }
716
+ function buildTaskSourceRegistry(config, pluginHost) {
717
+ const registry = createTaskSourceRegistry();
718
+ const taskSourceConfig = config.taskSource;
719
+ const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
720
+ if (!factory) {
721
+ throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
722
+ }
723
+ registry.register(factory.factory(taskSourceConfig));
724
+ return registry;
725
+ }
726
+
727
+ // packages/runtime/src/control-plane/agent-roles.ts
728
+ function createAgentRoleRegistry(pluginRoles, configOverrides) {
729
+ const map = new Map;
730
+ for (const r of pluginRoles) {
731
+ if (map.has(r.id))
732
+ throw new Error(`agent role already registered: ${r.id}`);
733
+ const override = configOverrides?.[r.id];
734
+ const model = override?.model ?? r.defaultModel;
735
+ if (!model) {
736
+ throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
737
+ }
738
+ map.set(r.id, { ...r, model });
739
+ }
740
+ return {
741
+ resolve(id) {
742
+ const r = map.get(id);
743
+ if (!r)
744
+ throw new Error(`agent role not registered: ${id}`);
745
+ return r;
746
+ },
747
+ list: () => Array.from(map.values())
748
+ };
749
+ }
750
+
751
+ // packages/runtime/src/control-plane/task-fields.ts
752
+ function createTaskFieldRegistry(extensions) {
753
+ const byId = new Map;
754
+ for (const e of extensions) {
755
+ if (byId.has(e.id))
756
+ throw new Error(`task field extension already registered: ${e.id}`);
757
+ byId.set(e.id, e);
758
+ }
759
+ return {
760
+ get: (id) => byId.get(id),
761
+ list: () => Array.from(byId.values()),
762
+ fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
763
+ validateTaskFields(task) {
764
+ const errors = [];
765
+ for (const ext of byId.values()) {
766
+ let schema;
767
+ try {
768
+ schema = JSON.parse(ext.schemaJson);
769
+ } catch {
770
+ errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
771
+ continue;
772
+ }
773
+ const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
774
+ if (!isRequired)
775
+ continue;
776
+ const value = task[ext.fieldName];
777
+ if (value === undefined || value === null || value === "") {
778
+ errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
779
+ }
780
+ }
781
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
782
+ }
783
+ };
784
+ }
785
+
786
+ // packages/runtime/src/control-plane/validators/runtime-registration.ts
787
+ import { existsSync as existsSync6 } from "fs";
788
+ import { join } from "path";
789
+ function createValidatorRegistry() {
790
+ const map = new Map;
791
+ const order = [];
792
+ const registry = {
793
+ register(v) {
794
+ if (map.has(v.id))
795
+ throw new Error(`validator already registered: ${v.id}`);
796
+ map.set(v.id, v);
797
+ order.push(v);
798
+ },
799
+ resolve(id) {
800
+ const v = map.get(id);
801
+ if (!v)
802
+ throw new Error(`validator not registered: ${id}`);
803
+ return v;
804
+ },
805
+ list: () => order
806
+ };
807
+ registerBuiltInValidators(registry);
808
+ return registry;
809
+ }
810
+ function registerBuiltInValidators(registry) {
811
+ registry.register({
812
+ id: "std:typecheck",
813
+ category: "custom",
814
+ description: "Runs the package typecheck script when present.",
815
+ run: async (ctx) => runStdTypecheck(ctx)
816
+ });
817
+ }
818
+ async function runStdTypecheck(ctx) {
819
+ const packageJsonPath = join(ctx.workspaceRoot, "package.json");
820
+ if (!existsSync6(packageJsonPath)) {
821
+ return {
822
+ id: "std:typecheck",
823
+ passed: false,
824
+ summary: `package.json not found at ${packageJsonPath}`
825
+ };
826
+ }
827
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
828
+ cwd: ctx.workspaceRoot,
829
+ env: process.env,
830
+ stdout: "pipe",
831
+ stderr: "pipe"
832
+ });
833
+ const [exitCode, stdout, stderr] = await Promise.all([
834
+ proc.exited,
835
+ new Response(proc.stdout).text(),
836
+ new Response(proc.stderr).text()
837
+ ]);
838
+ const output = `${stdout}${stderr}`.trim();
839
+ return {
840
+ id: "std:typecheck",
841
+ passed: exitCode === 0,
842
+ summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
843
+ ...output ? { details: output.slice(0, 4000) } : {}
844
+ };
845
+ }
846
+
847
+ // packages/runtime/src/control-plane/hook-materializer.ts
848
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
849
+ import { dirname as dirname5, resolve as resolve7 } from "path";
850
+ var MARKER_PLUGIN = "_rigPlugin";
851
+ var MARKER_HOOK_ID = "_rigHookId";
852
+ function matcherToString(matcher) {
853
+ if (matcher.kind === "all")
854
+ return;
855
+ if (matcher.kind === "tool")
856
+ return matcher.name;
857
+ return matcher.pattern;
858
+ }
859
+ function isPluginOwned(cmd) {
860
+ return typeof cmd[MARKER_PLUGIN] === "string";
861
+ }
862
+ function materializeHooks(projectRoot, entries) {
863
+ const settingsPath = resolve7(projectRoot, ".claude", "settings.json");
864
+ const existing = existsSync7(settingsPath) ? safeReadJson(settingsPath) : {};
865
+ const hooks = existing.hooks ?? {};
866
+ for (const event of Object.keys(hooks)) {
867
+ const groups = hooks[event] ?? [];
868
+ const cleaned = [];
869
+ for (const group of groups) {
870
+ const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
871
+ if (operatorHooks.length > 0) {
872
+ cleaned.push({ ...group, hooks: operatorHooks });
873
+ }
874
+ }
875
+ if (cleaned.length > 0) {
876
+ hooks[event] = cleaned;
877
+ } else {
878
+ delete hooks[event];
879
+ }
880
+ }
881
+ for (const { pluginName, hook } of entries) {
882
+ if (!hook.command) {
883
+ continue;
884
+ }
885
+ const event = hook.event;
886
+ const matcherString = matcherToString(hook.matcher);
887
+ const groups = hooks[event] ??= [];
888
+ let group = groups.find((g) => g.matcher === matcherString);
889
+ if (!group) {
890
+ group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
891
+ groups.push(group);
892
+ }
893
+ group.hooks.push({
894
+ type: "command",
895
+ command: hook.command,
896
+ [MARKER_PLUGIN]: pluginName,
897
+ [MARKER_HOOK_ID]: hook.id
898
+ });
899
+ }
900
+ const next = { ...existing };
901
+ if (Object.keys(hooks).length > 0) {
902
+ next.hooks = hooks;
903
+ } else {
904
+ delete next.hooks;
905
+ }
906
+ mkdirSync3(dirname5(settingsPath), { recursive: true });
907
+ writeFileSync3(settingsPath, `${JSON.stringify(next, null, 2)}
908
+ `, "utf-8");
909
+ return settingsPath;
910
+ }
911
+ function safeReadJson(path) {
912
+ try {
913
+ return JSON.parse(readFileSync4(path, "utf-8"));
914
+ } catch {
915
+ return {};
916
+ }
917
+ }
918
+
919
+ // packages/runtime/src/control-plane/plugin-host-context.ts
920
+ async function buildPluginHostContext(projectRoot) {
921
+ let config;
922
+ try {
923
+ config = await loadConfig(projectRoot);
924
+ } catch (err) {
925
+ const msg = err instanceof Error ? err.message : String(err);
926
+ if (msg.includes("no rig.config")) {
927
+ return null;
928
+ }
929
+ throw err;
930
+ }
931
+ const pluginHost = createPluginHost(config.plugins);
932
+ setScopeRules(config.workspace.scopeNormalization);
933
+ const validatorRegistry = createValidatorRegistry();
934
+ for (const impl of pluginHost.listExecutableValidators()) {
935
+ validatorRegistry.register(impl);
936
+ }
937
+ const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost);
938
+ const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
939
+ const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
940
+ setManagedRepos(managedEntries);
941
+ const configRoleOverrides = config.runtime?.agentRoles;
942
+ const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
943
+ const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
944
+ try {
945
+ const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
946
+ pluginName: plugin.name,
947
+ hook
948
+ })));
949
+ if (hookEntries.length > 0) {
950
+ materializeHooks(projectRoot, hookEntries);
951
+ }
952
+ } catch (err) {
953
+ console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
954
+ }
955
+ return {
956
+ config,
957
+ pluginHost,
958
+ validatorRegistry,
959
+ taskSourceRegistry,
960
+ repoRegistry,
961
+ agentRoleRegistry,
962
+ taskFieldRegistry
963
+ };
964
+ }
965
+
966
+ // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
967
+ import { spawnSync } from "child_process";
968
+ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
969
+ import { basename as basename3, join as join2, resolve as resolve9 } from "path";
970
+
971
+ // packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
972
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
973
+ import { resolve as resolve8 } from "path";
974
+
975
+ // packages/runtime/src/control-plane/tasks/task-record-reader.ts
976
+ async function findTaskById(reader, id) {
977
+ const tasks = await reader.listTasks();
978
+ return tasks.find((task) => task.id === id) ?? null;
979
+ }
980
+
981
+ // packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
982
+ class LegacyTaskConfigReadError extends Error {
983
+ code = "LEGACY_TASK_CONFIG_READ_FAILED";
984
+ projectRoot;
985
+ configPath;
986
+ cause;
987
+ constructor(input) {
988
+ super(input.message, { cause: input.cause });
989
+ this.name = "LegacyTaskConfigReadError";
990
+ this.projectRoot = input.projectRoot;
991
+ this.configPath = input.configPath;
992
+ this.cause = input.cause;
993
+ }
994
+ }
995
+ function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
996
+ const configPath = options.configPath ?? resolve8(projectRoot, ".rig", "task-config.json");
997
+ const reader = {
998
+ async listTasks() {
999
+ return readLegacyTaskRecords(projectRoot, configPath);
1000
+ },
1001
+ async getTask(id) {
1002
+ return findTaskById(reader, id);
1003
+ }
1004
+ };
1005
+ return reader;
1006
+ }
1007
+ function readLegacyTaskRecords(projectRoot, configPath = resolve8(projectRoot, ".rig", "task-config.json")) {
1008
+ if (!existsSync8(configPath)) {
1009
+ return [];
1010
+ }
1011
+ const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
1012
+ return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
1013
+ }
1014
+ function readLegacyTaskConfigJson(projectRoot, configPath) {
1015
+ try {
1016
+ const parsed = JSON.parse(readFileSync5(configPath, "utf8"));
1017
+ if (isPlainRecord(parsed)) {
1018
+ return parsed;
1019
+ }
1020
+ throw new Error("task config root must be a JSON object");
1021
+ } catch (cause) {
1022
+ throw new LegacyTaskConfigReadError({
1023
+ projectRoot,
1024
+ configPath,
1025
+ message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
1026
+ cause
1027
+ });
1028
+ }
1029
+ }
1030
+ function stripLegacyTaskConfigMetadata(raw) {
1031
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
1032
+ return tasks;
1033
+ }
1034
+ function legacyTaskConfigEntryToRecord(id, entry) {
1035
+ if (!isPlainRecord(entry)) {
1036
+ return null;
1037
+ }
1038
+ const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
1039
+ const validation = readStringList(entry.validation);
1040
+ const validators = readStringList(entry.validators);
1041
+ const scope = readStringList(entry.scope);
1042
+ const status = typeof entry.status === "string" ? entry.status : "open";
1043
+ const title = typeof entry.title === "string" ? entry.title : undefined;
1044
+ const description = typeof entry.description === "string" ? entry.description : undefined;
1045
+ const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
1046
+ return {
1047
+ id,
1048
+ deps,
1049
+ status,
1050
+ source: "legacy-task-config",
1051
+ ...title ? { title } : {},
1052
+ ...description ? { description } : {},
1053
+ ...acceptanceCriteria ? { acceptanceCriteria } : {},
1054
+ ...scope.length > 0 ? { scope } : {},
1055
+ ...validation.length > 0 ? { validation } : {},
1056
+ ...validators.length > 0 ? { validators } : {},
1057
+ ...preservedLegacyFields(entry)
1058
+ };
1059
+ }
1060
+ function preservedLegacyFields(entry) {
1061
+ const preserved = {};
1062
+ for (const key of [
1063
+ "role",
1064
+ "browser",
1065
+ "repo_pins",
1066
+ "criticality",
1067
+ "queue_weight",
1068
+ "creates_repo",
1069
+ "auto_synced"
1070
+ ]) {
1071
+ if (entry[key] !== undefined) {
1072
+ preserved[key] = entry[key];
1073
+ }
1074
+ }
1075
+ return preserved;
1076
+ }
1077
+ function firstStringList(...candidates) {
1078
+ for (const candidate of candidates) {
1079
+ const list = readStringList(candidate);
1080
+ if (list.length > 0) {
1081
+ return list;
1082
+ }
1083
+ }
1084
+ return [];
1085
+ }
1086
+ function readStringList(candidate) {
1087
+ if (!Array.isArray(candidate)) {
1088
+ return [];
1089
+ }
1090
+ return candidate.filter((value) => typeof value === "string");
1091
+ }
1092
+ function isPlainRecord(candidate) {
1093
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
1094
+ }
1095
+
1096
+ // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
1097
+ var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
1098
+ var FILE_TASK_PATTERN = /\.(task\.)?json$/;
1099
+ function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
1100
+ const configPath = options.configPath ?? resolve9(projectRoot, ".rig", "task-config.json");
1101
+ const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
1102
+ const spawnFn = options.spawn ?? spawnSync;
1103
+ const ghBinary = options.ghBinary ?? "gh";
1104
+ const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
1105
+ return {
1106
+ async listTasks() {
1107
+ const rawConfig = readRawTaskConfig(configPath);
1108
+ if (!rawConfig) {
1109
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
1110
+ return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
1111
+ }
1112
+ const tasks = [];
1113
+ const legacyTasks = await legacy.listTasks();
1114
+ const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
1115
+ for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
1116
+ if (!isPlainRecord2(rawEntry)) {
1117
+ continue;
1118
+ }
1119
+ const metadata = readMaterializedTaskMetadata(rawEntry);
1120
+ if (metadata.taskSource?.kind === "github-issues") {
1121
+ tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
1122
+ continue;
1123
+ }
1124
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
1125
+ const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
1126
+ if (fileTask)
1127
+ tasks.push(fileTask);
1128
+ continue;
1129
+ }
1130
+ if (!allowLocalFallback) {
1131
+ continue;
1132
+ }
1133
+ const legacyTask = legacyById.get(id);
1134
+ if (legacyTask) {
1135
+ tasks.push(legacyTask);
1136
+ }
1137
+ }
1138
+ return tasks;
1139
+ },
1140
+ async getTask(id) {
1141
+ const rawEntry = readRawTaskEntry(configPath, id);
1142
+ if (!rawEntry) {
1143
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
1144
+ return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
1145
+ }
1146
+ const metadata = readMaterializedTaskMetadata(rawEntry);
1147
+ if (metadata.taskSource?.kind === "github-issues") {
1148
+ return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
1149
+ }
1150
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
1151
+ return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
1152
+ }
1153
+ return allowLocalFallback ? legacy.getTask(id) : null;
1154
+ }
1155
+ };
1156
+ }
1157
+ function readMaterializedTaskMetadata(entry) {
1158
+ const rawRig = entry._rig;
1159
+ if (!isPlainRecord2(rawRig)) {
1160
+ return {};
1161
+ }
1162
+ const rawSource = rawRig.taskSource;
1163
+ const metadata = {};
1164
+ if (isPlainRecord2(rawSource)) {
1165
+ const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
1166
+ if (kind.length > 0) {
1167
+ metadata.taskSource = {
1168
+ kind,
1169
+ ...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
1170
+ ...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
1171
+ ...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
1172
+ ...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
1173
+ ...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
1174
+ };
1175
+ }
1176
+ }
1177
+ if (typeof rawRig.sourceIssueId === "string") {
1178
+ metadata.sourceIssueId = rawRig.sourceIssueId;
1179
+ }
1180
+ return metadata;
1181
+ }
1182
+ function readConfiguredFilesTaskSourcePath(projectRoot) {
1183
+ const jsonPath = resolve9(projectRoot, "rig.config.json");
1184
+ if (existsSync9(jsonPath)) {
1185
+ try {
1186
+ const parsed = JSON.parse(readFileSync6(jsonPath, "utf8"));
1187
+ if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
1188
+ const source = parsed.taskSource;
1189
+ return source.kind === "files" && typeof source.path === "string" ? source.path : null;
1190
+ }
1191
+ } catch {
1192
+ return null;
1193
+ }
1194
+ }
1195
+ const tsPath = resolve9(projectRoot, "rig.config.ts");
1196
+ if (!existsSync9(tsPath)) {
1197
+ return null;
1198
+ }
1199
+ try {
1200
+ const source = readFileSync6(tsPath, "utf8");
1201
+ const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
1202
+ const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
1203
+ if (kind !== "files") {
1204
+ return null;
1205
+ }
1206
+ return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
1207
+ } catch {
1208
+ return null;
1209
+ }
1210
+ }
1211
+ function readRawTaskEntry(configPath, taskId) {
1212
+ const rawConfig = readRawTaskConfig(configPath);
1213
+ if (!rawConfig) {
1214
+ return null;
1215
+ }
1216
+ const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
1217
+ return isPlainRecord2(entry) ? entry : null;
1218
+ }
1219
+ function readRawTaskConfig(configPath) {
1220
+ if (!existsSync9(configPath)) {
1221
+ return null;
1222
+ }
1223
+ const parsed = JSON.parse(readFileSync6(configPath, "utf8"));
1224
+ return isPlainRecord2(parsed) ? parsed : null;
1225
+ }
1226
+ function stripLegacyTaskConfigMetadata2(raw) {
1227
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
1228
+ return tasks;
1229
+ }
1230
+ function listFileBackedTasks(projectRoot, sourcePath) {
1231
+ const directory = resolve9(projectRoot, sourcePath);
1232
+ if (!existsSync9(directory)) {
1233
+ return [];
1234
+ }
1235
+ const tasks = [];
1236
+ for (const name of readdirSync2(directory)) {
1237
+ if (!FILE_TASK_PATTERN.test(name))
1238
+ continue;
1239
+ const inferredId = basename3(name).replace(FILE_TASK_PATTERN, "");
1240
+ const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
1241
+ if (task)
1242
+ tasks.push(task);
1243
+ }
1244
+ return tasks;
1245
+ }
1246
+ function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
1247
+ const file = findFileBackedTaskFile(resolve9(projectRoot, sourcePath), taskId);
1248
+ if (!file) {
1249
+ return null;
1250
+ }
1251
+ const raw = JSON.parse(readFileSync6(file, "utf8"));
1252
+ if (!isPlainRecord2(raw)) {
1253
+ return null;
1254
+ }
1255
+ return {
1256
+ id: typeof raw.id === "string" ? raw.id : taskId,
1257
+ deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
1258
+ status: typeof raw.status === "string" ? raw.status : "ready",
1259
+ title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
1260
+ ...raw
1261
+ };
1262
+ }
1263
+ function findFileBackedTaskFile(directory, taskId) {
1264
+ if (!existsSync9(directory)) {
1265
+ return null;
1266
+ }
1267
+ for (const name of readdirSync2(directory)) {
1268
+ if (!FILE_TASK_PATTERN.test(name))
1269
+ continue;
1270
+ const file = join2(directory, name);
1271
+ try {
1272
+ if (!statSync3(file).isFile())
1273
+ continue;
1274
+ const raw = JSON.parse(readFileSync6(file, "utf8"));
1275
+ const inferredId = basename3(file).replace(FILE_TASK_PATTERN, "");
1276
+ const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
1277
+ if (id === taskId) {
1278
+ return file;
1279
+ }
1280
+ } catch {}
1281
+ }
1282
+ return null;
1283
+ }
1284
+ function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
1285
+ const source = requireGithubIssueSource(metadata, id);
1286
+ const issue = runGh(bin, [
1287
+ "issue",
1288
+ "view",
1289
+ String(id),
1290
+ "--repo",
1291
+ `${source.owner}/${source.repo}`,
1292
+ "--json",
1293
+ "number,title,body,labels,state,url,assignees"
1294
+ ], spawnFn);
1295
+ return githubIssueToTask(issue, source, rawEntry);
1296
+ }
1297
+ function requireGithubIssueSource(metadata, id) {
1298
+ const source = metadata.taskSource;
1299
+ if (source?.kind === "github-issues" && source.owner && source.repo) {
1300
+ return { owner: source.owner, repo: source.repo };
1301
+ }
1302
+ const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
1303
+ if (parsed && parsed[3] === id) {
1304
+ return { owner: parsed[1], repo: parsed[2] };
1305
+ }
1306
+ throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
1307
+ }
1308
+ function githubIssueToTask(issue, source, rawEntry) {
1309
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
1310
+ const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
1311
+ const roleLabel = labelNames.find((label) => label.startsWith("role:"));
1312
+ const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
1313
+ const body = issue.body ?? "";
1314
+ const repo = `${source.owner}/${source.repo}`;
1315
+ return {
1316
+ id: String(issue.number),
1317
+ deps: parseDeps(body),
1318
+ status: githubStatusFor(issue),
1319
+ title: issue.title,
1320
+ body,
1321
+ ...scope.length > 0 ? { scope } : {},
1322
+ ...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
1323
+ ...validators.length > 0 ? { validators } : {},
1324
+ ...issue.url ? { url: issue.url } : {},
1325
+ issueType: issueTypeFor(labelNames),
1326
+ sourceIssueId: `${repo}#${issue.number}`,
1327
+ parentChildDeps: parseParents(body),
1328
+ labels: labelNames,
1329
+ raw: issue,
1330
+ source: "github-issues",
1331
+ _rig: {
1332
+ taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
1333
+ sourceIssueId: `${repo}#${issue.number}`
1334
+ }
1335
+ };
1336
+ }
1337
+ function githubStatusFor(issue) {
1338
+ const state = (issue.state ?? "").toUpperCase();
1339
+ if (state === "CLOSED")
1340
+ return "closed";
1341
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
1342
+ if (labelNames.includes("in-progress"))
1343
+ return "in_progress";
1344
+ if (labelNames.includes("blocked"))
1345
+ return "blocked";
1346
+ if (labelNames.includes("ready"))
1347
+ return "ready";
1348
+ if (labelNames.includes("under-review"))
1349
+ return "under_review";
1350
+ if (labelNames.includes("failed"))
1351
+ return "failed";
1352
+ if (labelNames.includes("cancelled"))
1353
+ return "cancelled";
1354
+ return "open";
1355
+ }
1356
+ function selectedGitHubEnv() {
1357
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1358
+ return { GH_TOKEN: token, GITHUB_TOKEN: token };
1359
+ }
1360
+ function ghSpawnOptions() {
1361
+ return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
1362
+ }
1363
+ function runGh(bin, args, spawnFn) {
1364
+ const res = spawnFn(bin, [...args], ghSpawnOptions());
1365
+ assertGhSuccess(args, res);
1366
+ if (!res.stdout || res.stdout.trim() === "") {
1367
+ throw new Error(`gh ${args.join(" ")} returned empty stdout`);
1368
+ }
1369
+ return JSON.parse(res.stdout);
1370
+ }
1371
+ function assertGhSuccess(args, res) {
1372
+ if (res.error) {
1373
+ const msg = res.error.message ?? String(res.error);
1374
+ throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
1375
+ }
1376
+ if (res.status !== 0) {
1377
+ throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
1378
+ }
1379
+ }
1380
+ function parseDeps(body) {
1381
+ return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
1382
+ }
1383
+ function parseParents(body) {
1384
+ return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
1385
+ }
1386
+ function parseIssueRefs(body, pattern) {
1387
+ const match = body.match(pattern);
1388
+ if (!match)
1389
+ return [];
1390
+ return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
1391
+ }
1392
+ function issueTypeFor(labels) {
1393
+ const typed = labels.find((label) => label.startsWith("type:"));
1394
+ if (typed)
1395
+ return typed.slice("type:".length);
1396
+ if (labels.includes("epic"))
1397
+ return "epic";
1398
+ return "task";
1399
+ }
1400
+ function isPlainRecord2(candidate) {
1401
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
1402
+ }
1403
+
1404
+ // packages/runtime/src/control-plane/tasks/source-lifecycle.ts
1405
+ function hasRunnableTaskSource(source) {
1406
+ return Boolean(source && typeof source === "object" && !Array.isArray(source));
1407
+ }
1408
+ async function getPluginTask(projectRoot, taskId) {
1409
+ const ctx = await buildPluginHostContext(projectRoot);
1410
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
1411
+ if (!hasRunnableTaskSource(source)) {
1412
+ return ctx ? { configured: false, sourceKind: null, task: null } : null;
1413
+ }
1414
+ const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
1415
+ return {
1416
+ configured: true,
1417
+ sourceKind: source.kind,
1418
+ task
1419
+ };
1420
+ }
1421
+ async function readConfiguredTaskSourceTask(projectRoot, taskId) {
1422
+ const pluginResult = await getPluginTask(projectRoot, taskId);
1423
+ if (pluginResult)
1424
+ return pluginResult;
1425
+ const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
1426
+ return {
1427
+ configured: false,
1428
+ sourceKind: null,
1429
+ task
1430
+ };
1431
+ }
1432
+
1433
+ // packages/runtime/src/control-plane/native/validator-binaries.ts
1434
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, rmSync as rmSync3, statSync as statSync4 } from "fs";
1435
+ import { dirname as dirname7, resolve as resolve13 } from "path";
1436
+
1437
+ // packages/runtime/src/binary-run.ts
1438
+ import { chmodSync, cpSync, existsSync as existsSync10, mkdirSync as mkdirSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1439
+ import { basename as basename4, dirname as dirname6, resolve as resolve10 } from "path";
1440
+ import { fileURLToPath } from "url";
1441
+ import { drainMicrotasks, gcAndSweep } from "bun:jsc";
1442
+ var runtimeBinaryBuildQueue = Promise.resolve();
1443
+ async function buildRuntimeBinary(options) {
1444
+ return runSerializedRuntimeBinaryBuild(async () => {
1445
+ const resolved = resolveRuntimeBinaryBuildOptions(options);
1446
+ runBestEffortBuildGc();
1447
+ const manifestPath = runtimeBinaryCacheManifestPath(resolved.outputPath);
1448
+ const buildKey = createRuntimeBinaryBuildKey({
1449
+ entrypoint: resolved.entrypoint,
1450
+ define: resolved.define,
1451
+ env: resolved.env
1452
+ });
1453
+ if (await isRuntimeBinaryBuildFresh({ outputPath: resolved.outputPath, manifestPath, buildKey })) {
1454
+ return;
1455
+ }
1456
+ if (shouldUseRuntimeBinaryBuildWorker()) {
1457
+ await buildRuntimeBinaryViaWorker(resolved);
1458
+ } else {
1459
+ await buildRuntimeBinaryInProcess(resolved, { manifestPath, buildKey });
1460
+ }
1461
+ runBestEffortBuildGc();
1462
+ });
1463
+ }
1464
+ async function buildRuntimeBinaryInProcess(options, manifest) {
1465
+ const tempBuildDir = resolve10(dirname6(options.outputPath), `.bun-build-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
1466
+ const tempOutputPath = resolve10(tempBuildDir, basename4(options.outputPath));
1467
+ mkdirSync4(tempBuildDir, { recursive: true });
1468
+ await withTemporaryEnv({
1469
+ ...options.env,
1470
+ ...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
1471
+ }, async () => withTemporaryCwd(tempBuildDir, async () => {
1472
+ const buildResult = await Bun.build({
1473
+ entrypoints: [options.entrypoint],
1474
+ compile: {
1475
+ target: currentCompileTarget(),
1476
+ outfile: tempOutputPath
1477
+ },
1478
+ target: "bun",
1479
+ format: "esm",
1480
+ minify: true,
1481
+ bytecode: true,
1482
+ metafile: true,
1483
+ define: options.define ? {
1484
+ __RIG_BUILD_CONFIG__: JSON.stringify(options.define)
1485
+ } : undefined
1486
+ });
1487
+ if (!buildResult.success) {
1488
+ const details = buildResult.logs.map((log) => [log.message, log.position?.file ? `${log.position.file}:${log.position.line}:${log.position.column}` : ""].filter(Boolean).join(" ")).filter(Boolean).join(`
1489
+ `);
1490
+ throw new Error(`Failed to build ${options.entrypoint}: ${details || "Bun.build() returned errors"}`);
1491
+ }
1492
+ if (!existsSync10(tempOutputPath)) {
1493
+ const emitted = buildResult.outputs.map((output) => output.path).join(", ") || "(none)";
1494
+ throw new Error(`Failed to build ${options.entrypoint}: Bun.build() did not emit ${tempOutputPath}. Emitted: ${emitted}`);
1495
+ }
1496
+ renameSync2(tempOutputPath, options.outputPath);
1497
+ chmodSync(options.outputPath, 493);
1498
+ if (manifest) {
1499
+ await writeRuntimeBinaryCacheManifest({
1500
+ manifestPath: manifest.manifestPath,
1501
+ buildKey: manifest.buildKey,
1502
+ cwd: tempBuildDir,
1503
+ metafile: buildResult.metafile
1504
+ });
1505
+ }
1506
+ })).finally(() => {
1507
+ rmSync2(tempBuildDir, { recursive: true, force: true });
1508
+ });
1509
+ }
1510
+ function runBestEffortBuildGc() {
1511
+ try {
1512
+ drainMicrotasks();
1513
+ } catch {}
1514
+ try {
1515
+ gcAndSweep();
1516
+ } catch {}
1517
+ }
1518
+ function runtimeBinaryCacheManifestPath(outputPath) {
1519
+ return `${outputPath}.build-manifest.json`;
1520
+ }
1521
+ function resolveRuntimeBinaryBuildOptions(options) {
1522
+ return {
1523
+ ...options,
1524
+ entrypoint: resolve10(options.cwd, options.sourcePath),
1525
+ outputPath: resolve10(options.outputPath)
1526
+ };
1527
+ }
1528
+ function shouldUseRuntimeBinaryBuildWorker() {
1529
+ if (process.env.RIG_RUNTIME_BUILD_WORKER === "1") {
1530
+ return false;
1531
+ }
1532
+ if (process.env.RIG_RUNTIME_BUILD_IN_PROCESS === "1") {
1533
+ return false;
1534
+ }
1535
+ return true;
1536
+ }
1537
+ async function buildRuntimeBinaryViaWorker(options) {
1538
+ const workerSourcePath = resolveRuntimeBinaryBuildWorkerSourcePath(options);
1539
+ if (!workerSourcePath || !existsSync10(workerSourcePath)) {
1540
+ await buildRuntimeBinaryInProcess(options, {
1541
+ manifestPath: runtimeBinaryCacheManifestPath(options.outputPath),
1542
+ buildKey: createRuntimeBinaryBuildKey({
1543
+ entrypoint: options.entrypoint,
1544
+ define: options.define,
1545
+ env: options.env
1546
+ })
1547
+ });
1548
+ return;
1549
+ }
1550
+ const payloadPath = createRuntimeBinaryBuildWorkerPayloadPath(options.outputPath);
1551
+ const bunCli = resolveRuntimeBinaryBuildWorkerInvocation();
1552
+ await Bun.write(payloadPath, `${JSON.stringify(options)}
1553
+ `);
1554
+ const build = Bun.spawn([bunCli.command, workerSourcePath, payloadPath], {
1555
+ cwd: options.cwd,
1556
+ stdout: "pipe",
1557
+ stderr: "pipe",
1558
+ env: {
1559
+ ...process.env,
1560
+ ...options.env,
1561
+ ...bunCli.env,
1562
+ RIG_RUNTIME_BUILD_WORKER: "1"
1563
+ }
1564
+ });
1565
+ const [exitCode, stdout, stderr] = await Promise.all([
1566
+ build.exited,
1567
+ new Response(build.stdout).text(),
1568
+ new Response(build.stderr).text()
1569
+ ]);
1570
+ rmSync2(payloadPath, { force: true });
1571
+ if (exitCode !== 0) {
1572
+ throw new Error(`Failed to build ${options.entrypoint}: ${(stderr || stdout || `worker exited ${exitCode}`).trim()}`);
1573
+ }
1574
+ }
1575
+ function createRuntimeBinaryBuildWorkerPayloadPath(outputPath) {
1576
+ return resolve10(dirname6(outputPath), `.bun-build-worker-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
1577
+ }
1578
+ function resolveRuntimeBinaryBuildWorkerSourcePath(options) {
1579
+ const envRoots = [
1580
+ options.cwd?.trim(),
1581
+ process.env.RIG_HOST_PROJECT_ROOT?.trim(),
1582
+ process.env.PROJECT_RIG_ROOT?.trim()
1583
+ ].filter(Boolean);
1584
+ for (const root of envRoots) {
1585
+ const candidate = resolve10(root, "packages/runtime/src/binary-build-worker.ts");
1586
+ if (existsSync10(candidate)) {
1587
+ return candidate;
1588
+ }
1589
+ }
1590
+ const localCandidate = resolve10(import.meta.dir, "binary-build-worker.ts");
1591
+ return existsSync10(localCandidate) ? localCandidate : null;
1592
+ }
1593
+ function resolveRuntimeBinaryBuildWorkerInvocation() {
1594
+ const bunPath = Bun.which("bun");
1595
+ if (bunPath) {
1596
+ return { command: bunPath, env: {} };
1597
+ }
1598
+ if (process.execPath?.trim()) {
1599
+ return {
1600
+ command: process.execPath,
1601
+ env: { BUN_BE_BUN: "1" }
1602
+ };
1603
+ }
1604
+ throw new Error("bun is required to run the runtime binary build worker.");
1605
+ }
1606
+ function currentCompileTarget() {
1607
+ if (process.platform === "darwin") {
1608
+ return process.arch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
1609
+ }
1610
+ if (process.platform === "linux") {
1611
+ return process.arch === "arm64" ? "bun-linux-arm64" : "bun-linux-x64";
1612
+ }
1613
+ return "bun-windows-x64";
1614
+ }
1615
+ function createRuntimeBinaryBuildKey(input) {
1616
+ return JSON.stringify({
1617
+ version: 1,
1618
+ bunVersion: Bun.version,
1619
+ platform: process.platform,
1620
+ arch: process.arch,
1621
+ entrypoint: input.entrypoint,
1622
+ define: sortRecord(input.define),
1623
+ env: sortRecord(input.env)
1624
+ });
1625
+ }
1626
+ async function isRuntimeBinaryBuildFresh(input) {
1627
+ if (!existsSync10(input.outputPath) || !existsSync10(input.manifestPath)) {
1628
+ return false;
1629
+ }
1630
+ let manifest = null;
1631
+ try {
1632
+ manifest = await Bun.file(input.manifestPath).json();
1633
+ } catch {
1634
+ return false;
1635
+ }
1636
+ if (!manifest || manifest.version !== 1 || manifest.buildKey !== input.buildKey) {
1637
+ return false;
1638
+ }
1639
+ for (const [filePath, expectedDigest] of Object.entries(manifest.inputs || {})) {
1640
+ if (!existsSync10(filePath)) {
1641
+ return false;
1642
+ }
1643
+ if (await sha256File(filePath) !== expectedDigest) {
1644
+ return false;
1645
+ }
1646
+ }
1647
+ return true;
1648
+ }
1649
+ async function writeRuntimeBinaryCacheManifest(input) {
1650
+ const inputs = {};
1651
+ for (const inputPath of Object.keys(input.metafile?.inputs || {}).sort()) {
1652
+ const normalized = normalizeBuildInputPath(input.cwd, inputPath);
1653
+ if (!normalized || !existsSync10(normalized)) {
1654
+ continue;
1655
+ }
1656
+ inputs[normalized] = await sha256File(normalized);
1657
+ }
1658
+ const manifest = {
1659
+ version: 1,
1660
+ buildKey: input.buildKey,
1661
+ inputs
1662
+ };
1663
+ await Bun.write(input.manifestPath, `${JSON.stringify(manifest, null, 2)}
1664
+ `);
1665
+ }
1666
+ function normalizeBuildInputPath(cwd, inputPath) {
1667
+ if (!inputPath) {
1668
+ return null;
1669
+ }
1670
+ if (inputPath.startsWith("file://")) {
1671
+ return fileURLToPath(inputPath);
1672
+ }
1673
+ if (inputPath.startsWith("<")) {
1674
+ return null;
1675
+ }
1676
+ return resolve10(cwd, inputPath);
1677
+ }
1678
+ async function sha256File(path) {
1679
+ const hasher = new Bun.CryptoHasher("sha256");
1680
+ hasher.update(await Bun.file(path).arrayBuffer());
1681
+ return hasher.digest("hex");
1682
+ }
1683
+ function sortRecord(value) {
1684
+ if (!value) {
1685
+ return;
1686
+ }
1687
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
1688
+ }
1689
+ async function runSerializedRuntimeBinaryBuild(action) {
1690
+ const previous = runtimeBinaryBuildQueue;
1691
+ let release;
1692
+ runtimeBinaryBuildQueue = new Promise((resolve11) => {
1693
+ release = resolve11;
1694
+ });
1695
+ await previous;
1696
+ try {
1697
+ return await action();
1698
+ } finally {
1699
+ release();
1700
+ }
1701
+ }
1702
+ async function withTemporaryEnv(env, action) {
1703
+ if (!env) {
1704
+ return action();
1705
+ }
1706
+ const previousValues = new Map;
1707
+ for (const [key, value] of Object.entries(env)) {
1708
+ previousValues.set(key, process.env[key]);
1709
+ if (value === undefined) {
1710
+ delete process.env[key];
1711
+ } else {
1712
+ process.env[key] = value;
1713
+ }
1714
+ }
1715
+ try {
1716
+ return await action();
1717
+ } finally {
1718
+ for (const [key, value] of previousValues.entries()) {
1719
+ if (value === undefined) {
1720
+ delete process.env[key];
1721
+ } else {
1722
+ process.env[key] = value;
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ async function withTemporaryCwd(cwd, action) {
1728
+ const previousCwd = process.cwd();
1729
+ process.chdir(cwd);
1730
+ try {
1731
+ return await action();
1732
+ } finally {
1733
+ process.chdir(previousCwd);
1734
+ }
1735
+ }
1736
+
1737
+ // packages/runtime/src/control-plane/runtime/provisioning-env.ts
1738
+ import { delimiter, resolve as resolve12 } from "path";
1739
+
1740
+ // packages/runtime/src/control-plane/runtime/runtime-paths.ts
1741
+ import { existsSync as existsSync11, readdirSync as readdirSync3, realpathSync } from "fs";
1742
+ import { resolve as resolve11 } from "path";
1743
+
1744
+ // packages/runtime/src/control-plane/runtime/sandbox/utils.ts
1745
+ function uniq(values) {
1746
+ return [...new Set(values)];
1747
+ }
1748
+ // packages/runtime/src/control-plane/runtime/runtime-paths.ts
1749
+ function resolveBunBinaryPath() {
1750
+ const explicit = normalizeExecutablePath(process.env.RIG_BUN_PATH?.trim());
1751
+ if (explicit) {
1752
+ return explicit;
1753
+ }
1754
+ const pathBun = normalizeExecutablePath(Bun.which("bun")?.trim());
1755
+ if (pathBun && !looksLikeRuntimeGateway(pathBun)) {
1756
+ return pathBun;
1757
+ }
1758
+ const home = process.env.HOME?.trim();
1759
+ const fallbackCandidates = [
1760
+ home ? resolve11(home, ".bun/bin/bun") : "",
1761
+ "/opt/homebrew/bin/bun",
1762
+ "/usr/local/bin/bun",
1763
+ "/usr/bin/bun"
1764
+ ];
1765
+ for (const candidate of fallbackCandidates) {
1766
+ const normalized = normalizeExecutablePath(candidate);
1767
+ if (normalized) {
1768
+ return normalized;
1769
+ }
1770
+ }
1771
+ const execPath = normalizeExecutablePath(process.execPath?.trim());
1772
+ if (execPath && !looksLikeRuntimeGateway(execPath)) {
1773
+ return execPath;
1774
+ }
1775
+ throw new Error("bun not found in PATH");
1776
+ }
1777
+ function resolveClaudeBinaryPath() {
1778
+ const explicit = normalizeExecutablePath(process.env.RIG_CLAUDE_PATH?.trim());
1779
+ if (explicit) {
1780
+ return explicit;
1781
+ }
1782
+ const pathClaude = normalizeExecutablePath(Bun.which("claude")?.trim());
1783
+ if (pathClaude && !looksLikeRuntimeGateway(pathClaude)) {
1784
+ return pathClaude;
1785
+ }
1786
+ const home = process.env.HOME?.trim();
1787
+ const fallbackCandidates = [
1788
+ home ? resolve11(home, ".local/bin/claude") : "",
1789
+ home ? resolve11(home, ".local/share/claude/local/claude") : "",
1790
+ "/opt/homebrew/bin/claude",
1791
+ "/usr/local/bin/claude",
1792
+ "/usr/bin/claude"
1793
+ ];
1794
+ for (const candidate of fallbackCandidates) {
1795
+ const normalized = normalizeExecutablePath(candidate);
1796
+ if (normalized) {
1797
+ return normalized;
1798
+ }
1799
+ }
1800
+ throw new Error("claude not found in PATH");
1801
+ }
1802
+ function resolveBunInstallDir(bunBinaryPath = resolveBunBinaryPath()) {
1803
+ return resolve11(bunBinaryPath, "../..");
1804
+ }
1805
+ function resolveClaudeInstallDir() {
1806
+ const realPath = resolveClaudeBinaryPath();
1807
+ return resolve11(realPath, "..");
1808
+ }
1809
+ function resolveNodeInstallDir() {
1810
+ const preferredNode = resolvePreferredNodeBinary();
1811
+ if (!preferredNode)
1812
+ return null;
1813
+ const explicitNode = process.env.RIG_NODE_BIN?.trim();
1814
+ if (explicitNode && resolve11(explicitNode) === resolve11(preferredNode)) {
1815
+ return preferredNode.endsWith("/bin/node") ? resolve11(preferredNode, "../..") : resolve11(preferredNode, "..");
1816
+ }
1817
+ try {
1818
+ const realPath = realpathSync(preferredNode);
1819
+ if (realPath.endsWith("/bin/node")) {
1820
+ return resolve11(realPath, "../..");
1821
+ }
1822
+ return resolve11(realPath, "..");
1823
+ } catch {
1824
+ return resolve11(preferredNode, "..");
1825
+ }
1826
+ }
1827
+ function resolvePreferredNodeBinary() {
1828
+ const candidates = [];
1829
+ const envNode = process.env.RIG_NODE_BIN?.trim();
1830
+ if (envNode) {
1831
+ const explicit = resolve11(envNode);
1832
+ if (existsSync11(explicit)) {
1833
+ return explicit;
1834
+ }
1835
+ }
1836
+ const nvmBin = process.env.NVM_BIN?.trim();
1837
+ if (nvmBin) {
1838
+ candidates.push(resolve11(nvmBin, "node"));
1839
+ }
1840
+ const home = process.env.HOME?.trim();
1841
+ if (home) {
1842
+ const nvmVersionsDir = resolve11(home, ".nvm/versions/node");
1843
+ if (existsSync11(nvmVersionsDir)) {
1844
+ try {
1845
+ const versionDirs = readdirSync3(nvmVersionsDir).map((entry) => entry.trim()).filter((entry) => /^v\d+\.\d+\.\d+$/.test(entry)).sort((a, b) => Bun.semver.order(b.replace(/^v/, ""), a.replace(/^v/, "")));
1846
+ for (const versionDir of versionDirs) {
1847
+ candidates.push(resolve11(nvmVersionsDir, versionDir, "bin/node"));
1848
+ }
1849
+ } catch {}
1850
+ }
1851
+ }
1852
+ const whichNode = Bun.which("node");
1853
+ if (whichNode) {
1854
+ candidates.push(whichNode);
1855
+ }
1856
+ const deduped = uniq(candidates.map((candidate) => resolve11(candidate)));
1857
+ const existing = deduped.filter((candidate) => existsSync11(candidate));
1858
+ if (existing.length === 0) {
1859
+ return null;
1860
+ }
1861
+ const stable = existing.find((candidate) => {
1862
+ const major = inferNodeMajor(candidate);
1863
+ return typeof major === "number" && major >= 18 && major <= 24;
1864
+ });
1865
+ if (stable) {
1866
+ return stable;
1867
+ }
1868
+ return existing[0] ?? null;
1869
+ }
1870
+ function inferNodeMajor(nodeBinaryPath) {
1871
+ const normalized = resolve11(nodeBinaryPath).replace(/\\/g, "/");
1872
+ const match = normalized.match(/(?:^|\/)(?:node-)?v?(\d+)\.\d+\.\d+(?:\/|$)/);
1873
+ if (!match) {
1874
+ return null;
1875
+ }
1876
+ const major = Number.parseInt(match[1], 10);
1877
+ return Number.isFinite(major) ? major : null;
1878
+ }
1879
+ function normalizeExecutablePath(candidate) {
1880
+ if (!candidate) {
1881
+ return "";
1882
+ }
1883
+ const normalized = resolve11(candidate);
1884
+ if (!existsSync11(normalized)) {
1885
+ return "";
1886
+ }
1887
+ try {
1888
+ return realpathSync(normalized);
1889
+ } catch {
1890
+ return normalized;
1891
+ }
1892
+ }
1893
+ function looksLikeRuntimeGateway(candidate) {
1894
+ const normalized = resolve11(candidate).replace(/\\/g, "/");
1895
+ return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
1896
+ }
1897
+
1898
+ // packages/runtime/src/control-plane/runtime/provisioning-env.ts
1899
+ function runtimeProvisioningEnv(baseEnv = process.env) {
1900
+ const env = { ...baseEnv };
1901
+ const realBash = baseEnv.RIG_REAL_BASH?.trim() || "/bin/bash";
1902
+ const bunBinary = baseEnv.RIG_BUN_PATH?.trim() || resolveBunBinaryPath();
1903
+ const bunDir = resolveBunInstallDir(bunBinary);
1904
+ const claudeBinary = baseEnv.RIG_CLAUDE_PATH?.trim() || (() => {
1905
+ try {
1906
+ return resolveClaudeBinaryPath();
1907
+ } catch {
1908
+ return "";
1909
+ }
1910
+ })();
1911
+ const claudeDir = claudeBinary ? (() => {
1912
+ try {
1913
+ return resolveClaudeInstallDir();
1914
+ } catch {
1915
+ return resolve12(claudeBinary, "..");
1916
+ }
1917
+ })() : "";
1918
+ const nodeDir = resolveNodeInstallDir();
1919
+ const realHome = baseEnv.HOME?.trim();
1920
+ const inheritedPath = (baseEnv.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean).filter((entry) => !entry.endsWith("/.rig/bin") && !entry.endsWith("/rig/tools"));
1921
+ const pathEntries = [
1922
+ `${bunDir}/bin`,
1923
+ claudeDir,
1924
+ nodeDir ? `${nodeDir}/bin` : "",
1925
+ realHome ? resolve12(realHome, ".local/bin") : "",
1926
+ realHome ? resolve12(realHome, ".cargo/bin") : "",
1927
+ ...inheritedPath,
1928
+ "/usr/local/bin",
1929
+ "/usr/local/sbin",
1930
+ "/opt/homebrew/bin",
1931
+ "/opt/homebrew/sbin",
1932
+ "/usr/bin",
1933
+ "/bin",
1934
+ "/usr/sbin",
1935
+ "/sbin"
1936
+ ].filter(Boolean);
1937
+ env.BASH = realBash;
1938
+ env.SHELL = baseEnv.SHELL?.trim() || realBash;
1939
+ env.PATH = [...new Set(pathEntries)].join(delimiter);
1940
+ env.RIG_BUN_PATH = bunBinary;
1941
+ if (claudeBinary) {
1942
+ env.RIG_CLAUDE_PATH = claudeBinary;
1943
+ }
1944
+ env.PYTHON = env.PYTHON?.trim() || "python3";
1945
+ const nodeGypPath = Bun.which("node-gyp");
1946
+ if (nodeGypPath) {
1947
+ env.npm_config_node_gyp = nodeGypPath;
1948
+ }
1949
+ delete env.RIG_BASH_ACTIVE;
1950
+ delete env.RIG_BASH_MODE;
1951
+ return env;
1952
+ }
1953
+
1954
+ // packages/runtime/src/control-plane/native/validator-binaries.ts
1955
+ function resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext) {
1956
+ if (runtimeContext) {
1957
+ return resolve13(runtimeContext.binDir, "validators", binaryName);
1958
+ }
1959
+ return resolve13(resolveHarnessPaths(projectRoot).binDir, "validators", binaryName);
1960
+ }
1961
+ async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
1962
+ const match = checkId.match(/^([a-z][\w-]*):([a-z][\w-]*)$/);
1963
+ if (!match) {
1964
+ return null;
1965
+ }
1966
+ const category = match[1];
1967
+ const check = match[2];
1968
+ if (!category || !check) {
1969
+ return null;
1970
+ }
1971
+ const binaryName = `${category}-${check}`;
1972
+ const binaryPath = resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
1973
+ const hostProjectRoot = runtimeContext?.hostProjectRoot?.trim() || projectRoot;
1974
+ const sourcePath = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators", category, `${check}.ts`);
1975
+ if (!existsSync12(sourcePath)) {
1976
+ return null;
1977
+ }
1978
+ const sourceMtime = statSync4(sourcePath).mtimeMs;
1979
+ const binaryExists = existsSync12(binaryPath);
1980
+ const binaryMtime = binaryExists ? statSync4(binaryPath).mtimeMs : 0;
1981
+ if (!binaryExists || sourceMtime > binaryMtime) {
1982
+ if (binaryExists) {
1983
+ rmSync3(binaryPath, { force: true });
1984
+ rmSync3(`${binaryPath}.build-manifest.json`, { force: true });
1985
+ }
1986
+ mkdirSync5(dirname7(binaryPath), { recursive: true });
1987
+ await buildRuntimeBinary({
1988
+ sourcePath: `packages/runtime/src/control-plane/validators/${category}/${check}.ts`,
1989
+ outputPath: binaryPath,
1990
+ cwd: hostProjectRoot,
1991
+ define: { AGENT_BUN_PATH: resolveBunBinaryPath() },
1992
+ env: runtimeProvisioningEnv()
1993
+ });
1994
+ }
1995
+ return existsSync12(binaryPath) ? binaryPath : null;
1996
+ }
1997
+
1998
+ // packages/runtime/src/control-plane/native/validator.ts
1999
+ function isCheckId(entry) {
2000
+ return /^[a-z][\w-]*:[a-z][\w-]*$/.test(entry);
2001
+ }
2002
+ function stringArray(candidate) {
2003
+ return Array.isArray(candidate) ? candidate.filter((entry) => typeof entry === "string") : [];
2004
+ }
2005
+ function safeReadTaskConfig(projectRoot) {
2006
+ try {
2007
+ return readTaskConfig(projectRoot);
2008
+ } catch {
2009
+ return {};
2010
+ }
2011
+ }
2012
+ async function readTaskSourceValidation(projectRoot, taskId) {
2013
+ const sourceTask = await readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task).catch(() => null);
2014
+ if (!sourceTask) {
2015
+ return { validation: [], scope: [], taskConfig: undefined };
2016
+ }
2017
+ const record = sourceTask;
2018
+ const validation = stringArray(record.validation).length > 0 ? stringArray(record.validation) : stringArray(record.validators);
2019
+ return {
2020
+ validation,
2021
+ scope: stringArray(record.scope),
2022
+ taskConfig: {
2023
+ ...typeof record.role === "string" ? { role: record.role } : {},
2024
+ ...stringArray(record.scope).length > 0 ? { scope: stringArray(record.scope) } : {},
2025
+ ...validation.length > 0 ? { validation } : {}
2026
+ }
2027
+ };
2028
+ }
2029
+ function resolveValidationPaths(projectRoot, taskId, runtimeContext) {
2030
+ if (runtimeContext) {
2031
+ return {
2032
+ taskLogDir: resolve14(runtimeContext.logsDir, taskId),
2033
+ artifactDir: resolve14(runtimeContext.workspaceDir, "artifacts", taskId)
2034
+ };
2035
+ }
2036
+ const paths = resolveHarnessPaths(projectRoot);
2037
+ return {
2038
+ taskLogDir: resolve14(paths.logsDir, taskId),
2039
+ artifactDir: resolve14(paths.artifactsDir, taskId)
2040
+ };
2041
+ }
2042
+ async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext) {
2043
+ const binaryName = checkId.replace(":", "-");
2044
+ const binaryPath = await ensureValidatorBinary(projectRoot, checkId, runtimeContext) ?? resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
2045
+ if (!existsSync13(binaryPath)) {
2046
+ return {
2047
+ result: {
2048
+ id: checkId,
2049
+ passed: false,
2050
+ summary: `Validator binary not found: ${binaryPath}`
2051
+ },
2052
+ exitCode: 2
2053
+ };
2054
+ }
2055
+ const validatorCwd = runtimeContext?.workspaceDir || resolveMonorepoRoot(projectRoot);
2056
+ const runtimeShellPath = runtimeContext ? resolve14(runtimeContext.binDir, "rig-shell") : "";
2057
+ const monorepoMainRoot = runtimeContext?.monorepoMainRoot || process.env.MONOREPO_MAIN_ROOT?.trim() || resolveMonorepoRoot(projectRoot);
2058
+ const validatorEnv = {
2059
+ PROJECT_RIG_ROOT: runtimeContext?.hostProjectRoot || projectRoot,
2060
+ RIG_HOST_PROJECT_ROOT: projectRoot,
2061
+ RIG_TASK_WORKSPACE: validatorCwd,
2062
+ MONOREPO_ROOT: validatorCwd,
2063
+ MONOREPO_MAIN_ROOT: monorepoMainRoot,
2064
+ RIG_TASK_ID: taskId
2065
+ };
2066
+ if (runtimeContext) {
2067
+ validatorEnv.RIG_TASK_RUNTIME_ID = runtimeContext.runtimeId;
2068
+ validatorEnv.RIG_LOGS_DIR = runtimeContext.logsDir;
2069
+ validatorEnv.RIG_RUNTIME_BIN_DIR = runtimeContext.binDir;
2070
+ }
2071
+ const { exitCode, stdout, stderr } = await runCaptureAsync(runtimeShellPath && existsSync13(runtimeShellPath) ? [runtimeShellPath, "run-binary", binaryPath] : [binaryPath], validatorCwd, validatorEnv);
2072
+ try {
2073
+ const result = JSON.parse(stdout.trim());
2074
+ return { result, exitCode };
2075
+ } catch {
2076
+ return {
2077
+ result: {
2078
+ id: checkId,
2079
+ passed: false,
2080
+ summary: `Failed to parse validator output: ${stderr || stdout}`.slice(0, 200)
2081
+ },
2082
+ exitCode: exitCode || 2
2083
+ };
2084
+ }
2085
+ }
2086
+ async function dispatchValidator(checkId, registry, ctx, subprocessFallback) {
2087
+ let registered;
2088
+ try {
2089
+ registered = registry.resolve(checkId);
2090
+ } catch {
2091
+ return subprocessFallback(checkId);
2092
+ }
2093
+ const validatorResult = await registered.run(ctx);
2094
+ return {
2095
+ result: {
2096
+ id: validatorResult.id,
2097
+ passed: validatorResult.passed,
2098
+ summary: validatorResult.summary,
2099
+ details: validatorResult.details
2100
+ },
2101
+ exitCode: validatorResult.passed ? 0 : 1
2102
+ };
2103
+ }
2104
+ async function validateTask(projectRoot, taskId, runtimeContext, registry, options = {}) {
2105
+ const resolvedContext = runtimeContext ?? null;
2106
+ const taskConfig = resolvedContext ? {} : options.taskConfig ?? safeReadTaskConfig(projectRoot);
2107
+ const sourceValidation = !resolvedContext ? await readTaskSourceValidation(projectRoot, taskId) : { validation: [], scope: [], taskConfig: undefined };
2108
+ const configuredValidation = stringArray(taskConfig[taskId]?.validation);
2109
+ const commands = resolvedContext?.validation?.length ? resolvedContext.validation : configuredValidation.length > 0 ? configuredValidation : sourceValidation.validation;
2110
+ const { taskLogDir, artifactDir } = resolveValidationPaths(projectRoot, taskId, resolvedContext);
2111
+ mkdirSync6(taskLogDir, { recursive: true });
2112
+ mkdirSync6(artifactDir, { recursive: true });
2113
+ if (commands.length === 0) {
2114
+ const skipped = {
2115
+ status: "skipped",
2116
+ total: 0,
2117
+ passed: 0,
2118
+ failed: 0,
2119
+ categories: []
2120
+ };
2121
+ writeFileSync6(resolve14(artifactDir, "validation-summary.json"), `${JSON.stringify(skipped, null, 2)}
2122
+ `, "utf-8");
2123
+ return skipped;
2124
+ }
2125
+ const effectiveRegistry = registry ?? createValidatorRegistry();
2126
+ const workspaceRoot = resolvedContext?.workspaceDir ?? resolveMonorepoRoot(projectRoot);
2127
+ const monorepoRoot = resolvedContext?.monorepoMainRoot ?? process.env.MONOREPO_MAIN_ROOT?.trim() ?? resolveMonorepoRoot(projectRoot);
2128
+ const validatorCtx = {
2129
+ taskId,
2130
+ workspaceRoot,
2131
+ scope: resolvedContext?.scopes ?? (stringArray(taskConfig[taskId]?.scope).length > 0 ? stringArray(taskConfig[taskId]?.scope) : sourceValidation.scope),
2132
+ monorepoRoot,
2133
+ artifactsDir: artifactDir,
2134
+ taskConfig: sourceValidation.taskConfig ?? taskConfig[taskId] ?? undefined
2135
+ };
2136
+ const valDescriptions = resolvedContext ? {} : options.validationDescriptions ?? (() => {
2137
+ try {
2138
+ return readValidationDescriptions(projectRoot);
2139
+ } catch {
2140
+ return {};
2141
+ }
2142
+ })();
2143
+ const categories = [];
2144
+ let passed = 0;
2145
+ let failed = 0;
2146
+ for (const cmd of commands) {
2147
+ const startedAt = Date.now();
2148
+ if (!isCheckId(cmd)) {
2149
+ failed += 1;
2150
+ categories.push({
2151
+ category: cmd,
2152
+ status: "fail",
2153
+ exit_code: 2,
2154
+ duration_seconds: 0
2155
+ });
2156
+ const logFile2 = resolve14(taskLogDir, `invalid-entry-validation.log`);
2157
+ mkdirSync6(taskLogDir, { recursive: true });
2158
+ writeFileSync6(logFile2, `=== ${nowIso()} :: ${cmd} ===
2159
+ Invalid validation entry: not a check-ID. All entries must use format "category:check-name".
2160
+ `, "utf-8");
2161
+ continue;
2162
+ }
2163
+ const { result, exitCode } = await dispatchValidator(cmd, effectiveRegistry, validatorCtx, (id) => runValidatorBinary(projectRoot, taskId, id, resolvedContext));
2164
+ const durationSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
2165
+ const logFile = resolve14(taskLogDir, `${cmd.replace(":", "-")}-validation.log`);
2166
+ mkdirSync6(taskLogDir, { recursive: true });
2167
+ writeFileSync6(logFile, `=== ${nowIso()} :: ${cmd} ===
2168
+ ${JSON.stringify(result, null, 2)}
2169
+ `, "utf-8");
2170
+ if (result.passed) {
2171
+ passed += 1;
2172
+ categories.push({ category: cmd, status: "pass", duration_seconds: durationSeconds });
2173
+ } else {
2174
+ failed += 1;
2175
+ categories.push({ category: cmd, status: "fail", exit_code: exitCode, duration_seconds: durationSeconds });
2176
+ const desc = valDescriptions[cmd];
2177
+ if (desc) {
2178
+ console.log(` What this checks (${cmd}): ${desc}`);
2179
+ }
2180
+ }
2181
+ }
2182
+ const summary = {
2183
+ status: failed === 0 ? "pass" : "fail",
2184
+ total: commands.length,
2185
+ passed,
2186
+ failed,
2187
+ categories
2188
+ };
2189
+ mkdirSync6(artifactDir, { recursive: true });
2190
+ writeFileSync6(resolve14(artifactDir, "validation-summary.json"), `${JSON.stringify(summary, null, 2)}
2191
+ `, "utf-8");
2192
+ return summary;
2193
+ }
2194
+ export {
2195
+ validateTask,
2196
+ dispatchValidator
2197
+ };