@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,1093 @@
1
+ // @bun
2
+ // packages/runtime/src/control-plane/plugin-host-context.ts
3
+ import { createPluginHost } from "@rig/core";
4
+ import { loadConfig } from "@rig/core/load-config";
5
+
6
+ // packages/runtime/src/control-plane/task-source.ts
7
+ function createTaskSourceRegistry() {
8
+ const byId = new Map;
9
+ const order = [];
10
+ return {
11
+ register(s) {
12
+ if (byId.has(s.id))
13
+ throw new Error(`task source already registered: ${s.id}`);
14
+ byId.set(s.id, s);
15
+ order.push(s);
16
+ },
17
+ resolveById(id) {
18
+ const s = byId.get(id);
19
+ if (!s)
20
+ throw new Error(`task source not registered: ${id}`);
21
+ return s;
22
+ },
23
+ resolveByKind(kind) {
24
+ for (const s of order)
25
+ if (s.kind === kind)
26
+ return s;
27
+ throw new Error(`no task source registered for kind: ${kind}`);
28
+ },
29
+ list: () => order
30
+ };
31
+ }
32
+
33
+ // packages/runtime/src/control-plane/task-source-bootstrap.ts
34
+ function formatRegisteredKinds(pluginHost) {
35
+ const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
36
+ return kinds.length > 0 ? kinds.join(", ") : "none";
37
+ }
38
+ function buildTaskSourceRegistry(config, pluginHost) {
39
+ const registry = createTaskSourceRegistry();
40
+ const taskSourceConfig = config.taskSource;
41
+ const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
42
+ if (!factory) {
43
+ 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.");
44
+ }
45
+ registry.register(factory.factory(taskSourceConfig));
46
+ return registry;
47
+ }
48
+
49
+ // packages/runtime/src/control-plane/repos/registry.ts
50
+ function createRepoRegistry(entries) {
51
+ const map = new Map;
52
+ for (const e of entries) {
53
+ if (map.has(e.id))
54
+ throw new Error(`repo already registered: ${e.id}`);
55
+ map.set(e.id, { ...e });
56
+ }
57
+ const ordered = Array.from(map.values());
58
+ return {
59
+ getById: (id) => map.get(id),
60
+ list: () => ordered
61
+ };
62
+ }
63
+ var MANAGED_REPOS = new Map;
64
+ function setManagedRepos(entries) {
65
+ const next = new Map;
66
+ for (const e of entries) {
67
+ if (next.has(e.id)) {
68
+ throw new Error(`managed repo already registered: ${e.id}`);
69
+ }
70
+ next.set(e.id, e);
71
+ }
72
+ MANAGED_REPOS = next;
73
+ }
74
+ function repoRegistrationToManagedEntry(reg) {
75
+ if (!reg.defaultBranch) {
76
+ return null;
77
+ }
78
+ return {
79
+ id: reg.id,
80
+ alias: reg.defaultPath ?? reg.id,
81
+ defaultBranch: reg.defaultBranch,
82
+ defaultRemoteUrl: reg.url,
83
+ remoteEnvVar: reg.remoteEnvVar,
84
+ checkoutEnvVar: reg.checkoutEnvVar
85
+ };
86
+ }
87
+
88
+ // packages/runtime/src/control-plane/agent-roles.ts
89
+ function createAgentRoleRegistry(pluginRoles, configOverrides) {
90
+ const map = new Map;
91
+ for (const r of pluginRoles) {
92
+ if (map.has(r.id))
93
+ throw new Error(`agent role already registered: ${r.id}`);
94
+ const override = configOverrides?.[r.id];
95
+ const model = override?.model ?? r.defaultModel;
96
+ if (!model) {
97
+ throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
98
+ }
99
+ map.set(r.id, { ...r, model });
100
+ }
101
+ return {
102
+ resolve(id) {
103
+ const r = map.get(id);
104
+ if (!r)
105
+ throw new Error(`agent role not registered: ${id}`);
106
+ return r;
107
+ },
108
+ list: () => Array.from(map.values())
109
+ };
110
+ }
111
+
112
+ // packages/runtime/src/control-plane/task-fields.ts
113
+ function createTaskFieldRegistry(extensions) {
114
+ const byId = new Map;
115
+ for (const e of extensions) {
116
+ if (byId.has(e.id))
117
+ throw new Error(`task field extension already registered: ${e.id}`);
118
+ byId.set(e.id, e);
119
+ }
120
+ return {
121
+ get: (id) => byId.get(id),
122
+ list: () => Array.from(byId.values()),
123
+ fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
124
+ validateTaskFields(task) {
125
+ const errors = [];
126
+ for (const ext of byId.values()) {
127
+ let schema;
128
+ try {
129
+ schema = JSON.parse(ext.schemaJson);
130
+ } catch {
131
+ errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
132
+ continue;
133
+ }
134
+ const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
135
+ if (!isRequired)
136
+ continue;
137
+ const value = task[ext.fieldName];
138
+ if (value === undefined || value === null || value === "") {
139
+ errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
140
+ }
141
+ }
142
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
143
+ }
144
+ };
145
+ }
146
+
147
+ // packages/runtime/src/control-plane/validators/runtime-registration.ts
148
+ import { existsSync } from "fs";
149
+ import { join } from "path";
150
+ function createValidatorRegistry() {
151
+ const map = new Map;
152
+ const order = [];
153
+ const registry = {
154
+ register(v) {
155
+ if (map.has(v.id))
156
+ throw new Error(`validator already registered: ${v.id}`);
157
+ map.set(v.id, v);
158
+ order.push(v);
159
+ },
160
+ resolve(id) {
161
+ const v = map.get(id);
162
+ if (!v)
163
+ throw new Error(`validator not registered: ${id}`);
164
+ return v;
165
+ },
166
+ list: () => order
167
+ };
168
+ registerBuiltInValidators(registry);
169
+ return registry;
170
+ }
171
+ function registerBuiltInValidators(registry) {
172
+ registry.register({
173
+ id: "std:typecheck",
174
+ category: "custom",
175
+ description: "Runs the package typecheck script when present.",
176
+ run: async (ctx) => runStdTypecheck(ctx)
177
+ });
178
+ }
179
+ async function runStdTypecheck(ctx) {
180
+ const packageJsonPath = join(ctx.workspaceRoot, "package.json");
181
+ if (!existsSync(packageJsonPath)) {
182
+ return {
183
+ id: "std:typecheck",
184
+ passed: false,
185
+ summary: `package.json not found at ${packageJsonPath}`
186
+ };
187
+ }
188
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
189
+ cwd: ctx.workspaceRoot,
190
+ env: process.env,
191
+ stdout: "pipe",
192
+ stderr: "pipe"
193
+ });
194
+ const [exitCode, stdout, stderr] = await Promise.all([
195
+ proc.exited,
196
+ new Response(proc.stdout).text(),
197
+ new Response(proc.stderr).text()
198
+ ]);
199
+ const output = `${stdout}${stderr}`.trim();
200
+ return {
201
+ id: "std:typecheck",
202
+ passed: exitCode === 0,
203
+ summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
204
+ ...output ? { details: output.slice(0, 4000) } : {}
205
+ };
206
+ }
207
+
208
+ // packages/runtime/src/control-plane/native/scope-rules.ts
209
+ var activeRules = null;
210
+ function setScopeRules(rules) {
211
+ activeRules = rules ?? null;
212
+ }
213
+
214
+ // packages/runtime/src/control-plane/hook-materializer.ts
215
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
216
+ import { dirname, resolve } from "path";
217
+ var MARKER_PLUGIN = "_rigPlugin";
218
+ var MARKER_HOOK_ID = "_rigHookId";
219
+ function matcherToString(matcher) {
220
+ if (matcher.kind === "all")
221
+ return;
222
+ if (matcher.kind === "tool")
223
+ return matcher.name;
224
+ return matcher.pattern;
225
+ }
226
+ function isPluginOwned(cmd) {
227
+ return typeof cmd[MARKER_PLUGIN] === "string";
228
+ }
229
+ function materializeHooks(projectRoot, entries) {
230
+ const settingsPath = resolve(projectRoot, ".claude", "settings.json");
231
+ const existing = existsSync2(settingsPath) ? safeReadJson(settingsPath) : {};
232
+ const hooks = existing.hooks ?? {};
233
+ for (const event of Object.keys(hooks)) {
234
+ const groups = hooks[event] ?? [];
235
+ const cleaned = [];
236
+ for (const group of groups) {
237
+ const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
238
+ if (operatorHooks.length > 0) {
239
+ cleaned.push({ ...group, hooks: operatorHooks });
240
+ }
241
+ }
242
+ if (cleaned.length > 0) {
243
+ hooks[event] = cleaned;
244
+ } else {
245
+ delete hooks[event];
246
+ }
247
+ }
248
+ for (const { pluginName, hook } of entries) {
249
+ if (!hook.command) {
250
+ continue;
251
+ }
252
+ const event = hook.event;
253
+ const matcherString = matcherToString(hook.matcher);
254
+ const groups = hooks[event] ??= [];
255
+ let group = groups.find((g) => g.matcher === matcherString);
256
+ if (!group) {
257
+ group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
258
+ groups.push(group);
259
+ }
260
+ group.hooks.push({
261
+ type: "command",
262
+ command: hook.command,
263
+ [MARKER_PLUGIN]: pluginName,
264
+ [MARKER_HOOK_ID]: hook.id
265
+ });
266
+ }
267
+ const next = { ...existing };
268
+ if (Object.keys(hooks).length > 0) {
269
+ next.hooks = hooks;
270
+ } else {
271
+ delete next.hooks;
272
+ }
273
+ mkdirSync(dirname(settingsPath), { recursive: true });
274
+ writeFileSync(settingsPath, `${JSON.stringify(next, null, 2)}
275
+ `, "utf-8");
276
+ return settingsPath;
277
+ }
278
+ function safeReadJson(path) {
279
+ try {
280
+ return JSON.parse(readFileSync(path, "utf-8"));
281
+ } catch {
282
+ return {};
283
+ }
284
+ }
285
+
286
+ // packages/runtime/src/control-plane/plugin-host-context.ts
287
+ async function buildPluginHostContext(projectRoot) {
288
+ let config;
289
+ try {
290
+ config = await loadConfig(projectRoot);
291
+ } catch (err) {
292
+ const msg = err instanceof Error ? err.message : String(err);
293
+ if (msg.includes("no rig.config")) {
294
+ return null;
295
+ }
296
+ throw err;
297
+ }
298
+ const pluginHost = createPluginHost(config.plugins);
299
+ setScopeRules(config.workspace.scopeNormalization);
300
+ const validatorRegistry = createValidatorRegistry();
301
+ for (const impl of pluginHost.listExecutableValidators()) {
302
+ validatorRegistry.register(impl);
303
+ }
304
+ const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost);
305
+ const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
306
+ const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
307
+ setManagedRepos(managedEntries);
308
+ const configRoleOverrides = config.runtime?.agentRoles;
309
+ const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
310
+ const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
311
+ try {
312
+ const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
313
+ pluginName: plugin.name,
314
+ hook
315
+ })));
316
+ if (hookEntries.length > 0) {
317
+ materializeHooks(projectRoot, hookEntries);
318
+ }
319
+ } catch (err) {
320
+ console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
321
+ }
322
+ return {
323
+ config,
324
+ pluginHost,
325
+ validatorRegistry,
326
+ taskSourceRegistry,
327
+ repoRegistry,
328
+ agentRoleRegistry,
329
+ taskFieldRegistry
330
+ };
331
+ }
332
+
333
+ // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
334
+ import { spawnSync } from "child_process";
335
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
336
+ import { basename, join as join2, resolve as resolve3 } from "path";
337
+
338
+ // packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
339
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
340
+ import { resolve as resolve2 } from "path";
341
+
342
+ // packages/runtime/src/control-plane/tasks/task-record-reader.ts
343
+ async function findTaskById(reader, id) {
344
+ const tasks = await reader.listTasks();
345
+ return tasks.find((task) => task.id === id) ?? null;
346
+ }
347
+
348
+ // packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
349
+ class LegacyTaskConfigReadError extends Error {
350
+ code = "LEGACY_TASK_CONFIG_READ_FAILED";
351
+ projectRoot;
352
+ configPath;
353
+ cause;
354
+ constructor(input) {
355
+ super(input.message, { cause: input.cause });
356
+ this.name = "LegacyTaskConfigReadError";
357
+ this.projectRoot = input.projectRoot;
358
+ this.configPath = input.configPath;
359
+ this.cause = input.cause;
360
+ }
361
+ }
362
+ function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
363
+ const configPath = options.configPath ?? resolve2(projectRoot, ".rig", "task-config.json");
364
+ const reader = {
365
+ async listTasks() {
366
+ return readLegacyTaskRecords(projectRoot, configPath);
367
+ },
368
+ async getTask(id) {
369
+ return findTaskById(reader, id);
370
+ }
371
+ };
372
+ return reader;
373
+ }
374
+ function readLegacyTaskRecords(projectRoot, configPath = resolve2(projectRoot, ".rig", "task-config.json")) {
375
+ if (!existsSync3(configPath)) {
376
+ return [];
377
+ }
378
+ const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
379
+ return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
380
+ }
381
+ function readLegacyTaskConfigJson(projectRoot, configPath) {
382
+ try {
383
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
384
+ if (isPlainRecord(parsed)) {
385
+ return parsed;
386
+ }
387
+ throw new Error("task config root must be a JSON object");
388
+ } catch (cause) {
389
+ throw new LegacyTaskConfigReadError({
390
+ projectRoot,
391
+ configPath,
392
+ message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
393
+ cause
394
+ });
395
+ }
396
+ }
397
+ function stripLegacyTaskConfigMetadata(raw) {
398
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
399
+ return tasks;
400
+ }
401
+ function legacyTaskConfigEntryToRecord(id, entry) {
402
+ if (!isPlainRecord(entry)) {
403
+ return null;
404
+ }
405
+ const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
406
+ const validation = readStringList(entry.validation);
407
+ const validators = readStringList(entry.validators);
408
+ const scope = readStringList(entry.scope);
409
+ const status = typeof entry.status === "string" ? entry.status : "open";
410
+ const title = typeof entry.title === "string" ? entry.title : undefined;
411
+ const description = typeof entry.description === "string" ? entry.description : undefined;
412
+ const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
413
+ return {
414
+ id,
415
+ deps,
416
+ status,
417
+ source: "legacy-task-config",
418
+ ...title ? { title } : {},
419
+ ...description ? { description } : {},
420
+ ...acceptanceCriteria ? { acceptanceCriteria } : {},
421
+ ...scope.length > 0 ? { scope } : {},
422
+ ...validation.length > 0 ? { validation } : {},
423
+ ...validators.length > 0 ? { validators } : {},
424
+ ...preservedLegacyFields(entry)
425
+ };
426
+ }
427
+ function preservedLegacyFields(entry) {
428
+ const preserved = {};
429
+ for (const key of [
430
+ "role",
431
+ "browser",
432
+ "repo_pins",
433
+ "criticality",
434
+ "queue_weight",
435
+ "creates_repo",
436
+ "auto_synced"
437
+ ]) {
438
+ if (entry[key] !== undefined) {
439
+ preserved[key] = entry[key];
440
+ }
441
+ }
442
+ return preserved;
443
+ }
444
+ function firstStringList(...candidates) {
445
+ for (const candidate of candidates) {
446
+ const list = readStringList(candidate);
447
+ if (list.length > 0) {
448
+ return list;
449
+ }
450
+ }
451
+ return [];
452
+ }
453
+ function readStringList(candidate) {
454
+ if (!Array.isArray(candidate)) {
455
+ return [];
456
+ }
457
+ return candidate.filter((value) => typeof value === "string");
458
+ }
459
+ function isPlainRecord(candidate) {
460
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
461
+ }
462
+
463
+ // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
464
+ var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
465
+ var FILE_TASK_PATTERN = /\.(task\.)?json$/;
466
+ function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
467
+ const configPath = options.configPath ?? resolve3(projectRoot, ".rig", "task-config.json");
468
+ const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
469
+ const spawnFn = options.spawn ?? spawnSync;
470
+ const ghBinary = options.ghBinary ?? "gh";
471
+ const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
472
+ return {
473
+ async listTasks() {
474
+ const rawConfig = readRawTaskConfig(configPath);
475
+ if (!rawConfig) {
476
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
477
+ return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
478
+ }
479
+ const tasks = [];
480
+ const legacyTasks = await legacy.listTasks();
481
+ const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
482
+ for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
483
+ if (!isPlainRecord2(rawEntry)) {
484
+ continue;
485
+ }
486
+ const metadata = readMaterializedTaskMetadata(rawEntry);
487
+ if (metadata.taskSource?.kind === "github-issues") {
488
+ tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
489
+ continue;
490
+ }
491
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
492
+ const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
493
+ if (fileTask)
494
+ tasks.push(fileTask);
495
+ continue;
496
+ }
497
+ if (!allowLocalFallback) {
498
+ continue;
499
+ }
500
+ const legacyTask = legacyById.get(id);
501
+ if (legacyTask) {
502
+ tasks.push(legacyTask);
503
+ }
504
+ }
505
+ return tasks;
506
+ },
507
+ async getTask(id) {
508
+ const rawEntry = readRawTaskEntry(configPath, id);
509
+ if (!rawEntry) {
510
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
511
+ return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
512
+ }
513
+ const metadata = readMaterializedTaskMetadata(rawEntry);
514
+ if (metadata.taskSource?.kind === "github-issues") {
515
+ return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
516
+ }
517
+ if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
518
+ return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
519
+ }
520
+ return allowLocalFallback ? legacy.getTask(id) : null;
521
+ }
522
+ };
523
+ }
524
+ async function readSourceAwareTaskStatus(projectRoot, taskId, options = {}) {
525
+ try {
526
+ const task = await createSourceAwareTaskConfigRecordReader(projectRoot, options).getTask(taskId);
527
+ return task?.status ?? null;
528
+ } catch {
529
+ return null;
530
+ }
531
+ }
532
+ function updateSourceAwareTaskConfigTask(projectRoot, taskId, update, options = {}) {
533
+ const configPath = options.configPath ?? resolve3(projectRoot, ".rig", "task-config.json");
534
+ const rawEntry = readRawTaskEntry(configPath, taskId);
535
+ if (!rawEntry) {
536
+ const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
537
+ return configuredFilesPath ? updateFileBackedTask(projectRoot, configuredFilesPath, taskId, update) : false;
538
+ }
539
+ const metadata = readMaterializedTaskMetadata(rawEntry);
540
+ const source = metadata.taskSource;
541
+ if (source?.kind === "github-issues") {
542
+ applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync, taskId, metadata, update);
543
+ return true;
544
+ }
545
+ if (source?.kind === "files" && source.path) {
546
+ return updateFileBackedTask(projectRoot, source.path, taskId, update);
547
+ }
548
+ if (source?.kind && source.kind !== "files" && source.kind !== "legacy-task-config") {
549
+ return false;
550
+ }
551
+ if (!source && options.allowLocalTaskConfigStatusFallback === false) {
552
+ return false;
553
+ }
554
+ if (typeof update.status !== "string" || update.status.trim().length === 0) {
555
+ return false;
556
+ }
557
+ writeLegacyTaskStatus(configPath, taskId, update.status);
558
+ return true;
559
+ }
560
+ function readMaterializedTaskMetadata(entry) {
561
+ const rawRig = entry._rig;
562
+ if (!isPlainRecord2(rawRig)) {
563
+ return {};
564
+ }
565
+ const rawSource = rawRig.taskSource;
566
+ const metadata = {};
567
+ if (isPlainRecord2(rawSource)) {
568
+ const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
569
+ if (kind.length > 0) {
570
+ metadata.taskSource = {
571
+ kind,
572
+ ...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
573
+ ...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
574
+ ...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
575
+ ...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
576
+ ...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
577
+ };
578
+ }
579
+ }
580
+ if (typeof rawRig.sourceIssueId === "string") {
581
+ metadata.sourceIssueId = rawRig.sourceIssueId;
582
+ }
583
+ return metadata;
584
+ }
585
+ function readConfiguredFilesTaskSourcePath(projectRoot) {
586
+ const jsonPath = resolve3(projectRoot, "rig.config.json");
587
+ if (existsSync4(jsonPath)) {
588
+ try {
589
+ const parsed = JSON.parse(readFileSync3(jsonPath, "utf8"));
590
+ if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
591
+ const source = parsed.taskSource;
592
+ return source.kind === "files" && typeof source.path === "string" ? source.path : null;
593
+ }
594
+ } catch {
595
+ return null;
596
+ }
597
+ }
598
+ const tsPath = resolve3(projectRoot, "rig.config.ts");
599
+ if (!existsSync4(tsPath)) {
600
+ return null;
601
+ }
602
+ try {
603
+ const source = readFileSync3(tsPath, "utf8");
604
+ const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
605
+ const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
606
+ if (kind !== "files") {
607
+ return null;
608
+ }
609
+ return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
610
+ } catch {
611
+ return null;
612
+ }
613
+ }
614
+ function readRawTaskEntry(configPath, taskId) {
615
+ const rawConfig = readRawTaskConfig(configPath);
616
+ if (!rawConfig) {
617
+ return null;
618
+ }
619
+ const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
620
+ return isPlainRecord2(entry) ? entry : null;
621
+ }
622
+ function readRawTaskConfig(configPath) {
623
+ if (!existsSync4(configPath)) {
624
+ return null;
625
+ }
626
+ const parsed = JSON.parse(readFileSync3(configPath, "utf8"));
627
+ return isPlainRecord2(parsed) ? parsed : null;
628
+ }
629
+ function stripLegacyTaskConfigMetadata2(raw) {
630
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
631
+ return tasks;
632
+ }
633
+ function writeLegacyTaskStatus(configPath, taskId, status) {
634
+ const rawConfig = readRawTaskConfig(configPath);
635
+ if (!rawConfig) {
636
+ return;
637
+ }
638
+ const entry = rawConfig[taskId];
639
+ if (!isPlainRecord2(entry)) {
640
+ return;
641
+ }
642
+ entry.status = status;
643
+ writeFileSync2(configPath, `${JSON.stringify(rawConfig, null, 2)}
644
+ `, "utf8");
645
+ }
646
+ function updateFileBackedTask(projectRoot, sourcePath, taskId, update) {
647
+ const directory = resolve3(projectRoot, sourcePath);
648
+ const file = findFileBackedTaskFile(directory, taskId);
649
+ if (!file) {
650
+ return false;
651
+ }
652
+ const raw = JSON.parse(readFileSync3(file, "utf8"));
653
+ if (!isPlainRecord2(raw)) {
654
+ return false;
655
+ }
656
+ if (update.status)
657
+ raw.status = update.status;
658
+ if (update.title !== undefined)
659
+ raw.title = update.title;
660
+ if (update.body !== undefined)
661
+ raw.body = update.body;
662
+ if (update.comment?.trim()) {
663
+ const existing = Array.isArray(raw.comments) ? raw.comments : [];
664
+ raw.comments = [
665
+ ...existing,
666
+ { body: update.comment, createdAt: new Date().toISOString(), source: "rig" }
667
+ ];
668
+ }
669
+ writeFileSync2(file, `${JSON.stringify(raw, null, 2)}
670
+ `, "utf8");
671
+ return true;
672
+ }
673
+ function listFileBackedTasks(projectRoot, sourcePath) {
674
+ const directory = resolve3(projectRoot, sourcePath);
675
+ if (!existsSync4(directory)) {
676
+ return [];
677
+ }
678
+ const tasks = [];
679
+ for (const name of readdirSync(directory)) {
680
+ if (!FILE_TASK_PATTERN.test(name))
681
+ continue;
682
+ const inferredId = basename(name).replace(FILE_TASK_PATTERN, "");
683
+ const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
684
+ if (task)
685
+ tasks.push(task);
686
+ }
687
+ return tasks;
688
+ }
689
+ function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
690
+ const file = findFileBackedTaskFile(resolve3(projectRoot, sourcePath), taskId);
691
+ if (!file) {
692
+ return null;
693
+ }
694
+ const raw = JSON.parse(readFileSync3(file, "utf8"));
695
+ if (!isPlainRecord2(raw)) {
696
+ return null;
697
+ }
698
+ return {
699
+ id: typeof raw.id === "string" ? raw.id : taskId,
700
+ deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
701
+ status: typeof raw.status === "string" ? raw.status : "ready",
702
+ title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
703
+ ...raw
704
+ };
705
+ }
706
+ function findFileBackedTaskFile(directory, taskId) {
707
+ if (!existsSync4(directory)) {
708
+ return null;
709
+ }
710
+ for (const name of readdirSync(directory)) {
711
+ if (!FILE_TASK_PATTERN.test(name))
712
+ continue;
713
+ const file = join2(directory, name);
714
+ try {
715
+ if (!statSync(file).isFile())
716
+ continue;
717
+ const raw = JSON.parse(readFileSync3(file, "utf8"));
718
+ const inferredId = basename(file).replace(FILE_TASK_PATTERN, "");
719
+ const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
720
+ if (id === taskId) {
721
+ return file;
722
+ }
723
+ } catch {}
724
+ }
725
+ return null;
726
+ }
727
+ function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
728
+ const source = requireGithubIssueSource(metadata, id);
729
+ const issue = runGh(bin, [
730
+ "issue",
731
+ "view",
732
+ String(id),
733
+ "--repo",
734
+ `${source.owner}/${source.repo}`,
735
+ "--json",
736
+ "number,title,body,labels,state,url,assignees"
737
+ ], spawnFn);
738
+ return githubIssueToTask(issue, source, rawEntry);
739
+ }
740
+ function applyGithubIssueUpdate(bin, spawnFn, id, metadata, update) {
741
+ const source = requireGithubIssueSource(metadata, id);
742
+ const repo = `${source.owner}/${source.repo}`;
743
+ if (update.status) {
744
+ applyGithubIssueStatus(bin, repo, spawnFn, id, update.status);
745
+ }
746
+ if (typeof update.comment === "string" && update.comment.trim().length > 0) {
747
+ runGhVoid(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn);
748
+ }
749
+ const editArgs = ["issue", "edit", String(id), "--repo", repo];
750
+ if (typeof update.title === "string" && update.title.trim().length > 0) {
751
+ editArgs.push("--title", update.title.trim());
752
+ }
753
+ if (typeof update.body === "string") {
754
+ editArgs.push("--body", update.body);
755
+ }
756
+ if (editArgs.length > 5) {
757
+ runGhVoid(bin, editArgs, spawnFn);
758
+ }
759
+ }
760
+ function requireGithubIssueSource(metadata, id) {
761
+ const source = metadata.taskSource;
762
+ if (source?.kind === "github-issues" && source.owner && source.repo) {
763
+ return { owner: source.owner, repo: source.repo };
764
+ }
765
+ const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
766
+ if (parsed && parsed[3] === id) {
767
+ return { owner: parsed[1], repo: parsed[2] };
768
+ }
769
+ throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
770
+ }
771
+ function githubIssueToTask(issue, source, rawEntry) {
772
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
773
+ const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
774
+ const roleLabel = labelNames.find((label) => label.startsWith("role:"));
775
+ const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
776
+ const body = issue.body ?? "";
777
+ const repo = `${source.owner}/${source.repo}`;
778
+ return {
779
+ id: String(issue.number),
780
+ deps: parseDeps(body),
781
+ status: githubStatusFor(issue),
782
+ title: issue.title,
783
+ body,
784
+ ...scope.length > 0 ? { scope } : {},
785
+ ...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
786
+ ...validators.length > 0 ? { validators } : {},
787
+ ...issue.url ? { url: issue.url } : {},
788
+ issueType: issueTypeFor(labelNames),
789
+ sourceIssueId: `${repo}#${issue.number}`,
790
+ parentChildDeps: parseParents(body),
791
+ labels: labelNames,
792
+ raw: issue,
793
+ source: "github-issues",
794
+ _rig: {
795
+ taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
796
+ sourceIssueId: `${repo}#${issue.number}`
797
+ }
798
+ };
799
+ }
800
+ function githubStatusFor(issue) {
801
+ const state = (issue.state ?? "").toUpperCase();
802
+ if (state === "CLOSED")
803
+ return "closed";
804
+ const labelNames = (issue.labels ?? []).map((label) => label.name);
805
+ if (labelNames.includes("in-progress"))
806
+ return "in_progress";
807
+ if (labelNames.includes("blocked"))
808
+ return "blocked";
809
+ if (labelNames.includes("ready"))
810
+ return "ready";
811
+ if (labelNames.includes("under-review"))
812
+ return "under_review";
813
+ if (labelNames.includes("failed"))
814
+ return "failed";
815
+ if (labelNames.includes("cancelled"))
816
+ return "cancelled";
817
+ return "open";
818
+ }
819
+ function applyGithubIssueStatus(bin, repo, spawnFn, id, status) {
820
+ if (status === "closed") {
821
+ runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn);
822
+ return;
823
+ }
824
+ const targetLabel = statusLabelFor(status);
825
+ for (const label of STATUS_LABELS) {
826
+ if (targetLabel !== null && label === targetLabel) {
827
+ continue;
828
+ }
829
+ try {
830
+ runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", label], spawnFn);
831
+ } catch {}
832
+ }
833
+ if (targetLabel !== null) {
834
+ try {
835
+ runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
836
+ } catch (error) {
837
+ const message = error instanceof Error ? error.message : String(error);
838
+ if (!/not found/i.test(message)) {
839
+ throw error;
840
+ }
841
+ ensureStatusLabel(bin, repo, spawnFn, targetLabel);
842
+ runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
843
+ }
844
+ }
845
+ }
846
+ function statusLabelFor(status) {
847
+ switch (status) {
848
+ case "in_progress":
849
+ return "in-progress";
850
+ case "blocked":
851
+ return "blocked";
852
+ case "ready":
853
+ return "ready";
854
+ case "under_review":
855
+ return "under-review";
856
+ case "failed":
857
+ return "failed";
858
+ case "cancelled":
859
+ return "cancelled";
860
+ case "open":
861
+ return null;
862
+ default:
863
+ throw new Error(`unsupported status: ${status}`);
864
+ }
865
+ }
866
+ function ensureStatusLabel(bin, repo, spawnFn, label) {
867
+ try {
868
+ runGhVoid(bin, [
869
+ "label",
870
+ "create",
871
+ label,
872
+ "--repo",
873
+ repo,
874
+ "--color",
875
+ "6f42c1",
876
+ "--description",
877
+ "Task status managed by Rig"
878
+ ], spawnFn);
879
+ } catch (error) {
880
+ const message = error instanceof Error ? error.message : String(error);
881
+ if (!/already exists/i.test(message)) {
882
+ throw error;
883
+ }
884
+ }
885
+ }
886
+ function selectedGitHubEnv() {
887
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
888
+ return { GH_TOKEN: token, GITHUB_TOKEN: token };
889
+ }
890
+ function ghSpawnOptions() {
891
+ return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
892
+ }
893
+ function runGh(bin, args, spawnFn) {
894
+ const res = spawnFn(bin, [...args], ghSpawnOptions());
895
+ assertGhSuccess(args, res);
896
+ if (!res.stdout || res.stdout.trim() === "") {
897
+ throw new Error(`gh ${args.join(" ")} returned empty stdout`);
898
+ }
899
+ return JSON.parse(res.stdout);
900
+ }
901
+ function runGhVoid(bin, args, spawnFn) {
902
+ const res = spawnFn(bin, [...args], ghSpawnOptions());
903
+ assertGhSuccess(args, res);
904
+ }
905
+ function assertGhSuccess(args, res) {
906
+ if (res.error) {
907
+ const msg = res.error.message ?? String(res.error);
908
+ throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
909
+ }
910
+ if (res.status !== 0) {
911
+ throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
912
+ }
913
+ }
914
+ function parseDeps(body) {
915
+ return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
916
+ }
917
+ function parseParents(body) {
918
+ return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
919
+ }
920
+ function parseIssueRefs(body, pattern) {
921
+ const match = body.match(pattern);
922
+ if (!match)
923
+ return [];
924
+ return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
925
+ }
926
+ function issueTypeFor(labels) {
927
+ const typed = labels.find((label) => label.startsWith("type:"));
928
+ if (typed)
929
+ return typed.slice("type:".length);
930
+ if (labels.includes("epic"))
931
+ return "epic";
932
+ return "task";
933
+ }
934
+ function isPlainRecord2(candidate) {
935
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
936
+ }
937
+
938
+ // packages/runtime/src/control-plane/tasks/source-lifecycle.ts
939
+ function hasRunnableTaskSource(source) {
940
+ return Boolean(source && typeof source === "object" && !Array.isArray(source));
941
+ }
942
+ function cleanString(value) {
943
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
944
+ }
945
+ function taskIdFromSourceIssueId(value) {
946
+ const raw = cleanString(value);
947
+ if (!raw)
948
+ return null;
949
+ const issueNumber = raw.match(/#([^#\s]+)$/)?.[1];
950
+ return issueNumber ?? raw;
951
+ }
952
+ function resolveSourceTaskId(taskId, sourceTask) {
953
+ return cleanString(sourceTask?.id) ?? taskIdFromSourceIssueId(sourceTask?.sourceIssueId) ?? taskIdFromSourceIssueId(sourceTask?.source_issue_id) ?? taskId;
954
+ }
955
+ async function listPluginTasks(projectRoot) {
956
+ const ctx = await buildPluginHostContext(projectRoot);
957
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
958
+ if (!hasRunnableTaskSource(source)) {
959
+ return ctx ? { configured: false, sourceKind: null, tasks: [] } : null;
960
+ }
961
+ return {
962
+ configured: true,
963
+ sourceKind: source.kind,
964
+ tasks: await source.list()
965
+ };
966
+ }
967
+ async function getPluginTask(projectRoot, taskId) {
968
+ const ctx = await buildPluginHostContext(projectRoot);
969
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
970
+ if (!hasRunnableTaskSource(source)) {
971
+ return ctx ? { configured: false, sourceKind: null, task: null } : null;
972
+ }
973
+ const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
974
+ return {
975
+ configured: true,
976
+ sourceKind: source.kind,
977
+ task
978
+ };
979
+ }
980
+ async function listConfiguredTaskSourceTasks(projectRoot) {
981
+ const pluginResult = await listPluginTasks(projectRoot);
982
+ if (pluginResult)
983
+ return pluginResult;
984
+ const tasks = await createSourceAwareTaskConfigRecordReader(projectRoot).listTasks();
985
+ return {
986
+ configured: false,
987
+ sourceKind: null,
988
+ tasks
989
+ };
990
+ }
991
+ async function readConfiguredTaskSourceTask(projectRoot, taskId) {
992
+ const pluginResult = await getPluginTask(projectRoot, taskId);
993
+ if (pluginResult)
994
+ return pluginResult;
995
+ const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
996
+ return {
997
+ configured: false,
998
+ sourceKind: null,
999
+ task
1000
+ };
1001
+ }
1002
+ async function updatePluginTaskSourceTask(projectRoot, taskId, update) {
1003
+ const ctx = await buildPluginHostContext(projectRoot);
1004
+ const [source] = ctx?.taskSourceRegistry.list() ?? [];
1005
+ if (!hasRunnableTaskSource(source)) {
1006
+ return ctx ? { taskId, updated: false, source: "none", sourceKind: null, status: null } : null;
1007
+ }
1008
+ if (source.updateTask) {
1009
+ await source.updateTask(taskId, update);
1010
+ } else if (update.status && source.updateStatus) {
1011
+ await source.updateStatus(taskId, update.status);
1012
+ } else {
1013
+ return {
1014
+ taskId,
1015
+ updated: false,
1016
+ source: "plugin",
1017
+ sourceKind: source.kind,
1018
+ status: null
1019
+ };
1020
+ }
1021
+ const status = source.get ? (await source.get(taskId))?.status ?? update.status ?? null : update.status ?? null;
1022
+ return {
1023
+ taskId,
1024
+ updated: true,
1025
+ source: "plugin",
1026
+ sourceKind: source.kind,
1027
+ status
1028
+ };
1029
+ }
1030
+ async function updateConfiguredTaskSourceTask(projectRoot, input) {
1031
+ const taskId = resolveSourceTaskId(input.taskId, input.sourceTask);
1032
+ let pluginResult = null;
1033
+ try {
1034
+ pluginResult = await updatePluginTaskSourceTask(projectRoot, taskId, input.update);
1035
+ } catch (error) {
1036
+ const fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update, {
1037
+ allowLocalTaskConfigStatusFallback: false
1038
+ });
1039
+ if (!fallbackUpdated) {
1040
+ throw error;
1041
+ }
1042
+ return {
1043
+ taskId,
1044
+ updated: true,
1045
+ source: "compat",
1046
+ sourceKind: null,
1047
+ status: await readSourceAwareTaskStatus(projectRoot, taskId)
1048
+ };
1049
+ }
1050
+ if (pluginResult) {
1051
+ return pluginResult;
1052
+ }
1053
+ const updated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update);
1054
+ return {
1055
+ taskId,
1056
+ updated,
1057
+ source: updated ? "compat" : "none",
1058
+ sourceKind: null,
1059
+ status: await readSourceAwareTaskStatus(projectRoot, taskId)
1060
+ };
1061
+ }
1062
+ function buildTaskRunLifecycleComment(input) {
1063
+ const lines = [
1064
+ "<!-- rig:status-comment -->",
1065
+ `### Rig status: ${input.status}`,
1066
+ "",
1067
+ input.summary,
1068
+ "",
1069
+ `- Run: ${input.runId}`,
1070
+ `- Status: ${input.status}`
1071
+ ];
1072
+ if (input.errorText?.trim()) {
1073
+ lines.push(`- Error: ${input.errorText.trim()}`);
1074
+ }
1075
+ if (input.runtimeWorkspace?.trim()) {
1076
+ lines.push(`- Runtime workspace: ${input.runtimeWorkspace.trim()}`);
1077
+ }
1078
+ if (input.logsDir?.trim()) {
1079
+ lines.push(`- Logs: ${input.logsDir.trim()}`);
1080
+ }
1081
+ if (input.sessionDir?.trim()) {
1082
+ lines.push(`- Session: ${input.sessionDir.trim()}`);
1083
+ }
1084
+ return lines.join(`
1085
+ `);
1086
+ }
1087
+ export {
1088
+ updatePluginTaskSourceTask,
1089
+ updateConfiguredTaskSourceTask,
1090
+ readConfiguredTaskSourceTask,
1091
+ listConfiguredTaskSourceTasks,
1092
+ buildTaskRunLifecycleComment
1093
+ };