@h-rig/bundle-default-lifecycle 0.0.6-alpha.157 → 0.0.6-alpha.159

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 (54) hide show
  1. package/dist/src/cli.d.ts +1 -7
  2. package/dist/src/cli.js +5 -2
  3. package/dist/src/control-plane/completion-verification.js +1591 -118
  4. package/dist/src/control-plane/hooks/inject-context.d.ts +2 -0
  5. package/dist/src/control-plane/hooks/inject-context.js +175 -0
  6. package/dist/src/control-plane/hooks/shared.d.ts +11 -0
  7. package/dist/src/control-plane/hooks/shared.js +44 -0
  8. package/dist/src/control-plane/hooks/submodule-branch.d.ts +2 -0
  9. package/dist/src/control-plane/hooks/submodule-branch.js +432 -0
  10. package/dist/src/control-plane/hooks/task-runtime-start.d.ts +2 -0
  11. package/dist/src/control-plane/hooks/task-runtime-start.js +429 -0
  12. package/dist/src/control-plane/materialize-task-config.d.ts +29 -0
  13. package/dist/src/control-plane/materialize-task-config.js +95 -0
  14. package/dist/src/control-plane/native/git-ops.d.ts +67 -0
  15. package/dist/src/control-plane/native/git-ops.js +1390 -0
  16. package/dist/src/control-plane/policy.d.ts +3 -0
  17. package/dist/src/control-plane/policy.js +226 -0
  18. package/dist/src/control-plane/pr-automation.d.ts +2 -0
  19. package/dist/src/control-plane/pr-automation.js +26 -16
  20. package/dist/src/control-plane/pr-merge-gate-cap.d.ts +10 -0
  21. package/dist/src/control-plane/pr-merge-gate-cap.js +13 -0
  22. package/dist/src/control-plane/task-data.d.ts +13 -0
  23. package/dist/src/control-plane/task-data.js +12 -0
  24. package/dist/src/control-plane/task-verify.js +131 -59
  25. package/dist/src/control-plane/verifier.d.ts +1 -3
  26. package/dist/src/control-plane/verifier.js +133 -57
  27. package/dist/src/defaultPipeline.d.ts +1 -1
  28. package/dist/src/defaultPipeline.js +5 -2
  29. package/dist/src/index.d.ts +0 -2
  30. package/dist/src/index.js +1908 -290
  31. package/dist/src/native/closeout-runners.js +22 -2
  32. package/dist/src/native/github-auth-env.d.ts +2 -0
  33. package/dist/src/native/github-auth-env.js +25 -0
  34. package/dist/src/native/host-git.d.ts +6 -0
  35. package/dist/src/native/host-git.js +62 -0
  36. package/dist/src/native/in-process-closeout.d.ts +1 -3
  37. package/dist/src/native/in-process-closeout.js +0 -794
  38. package/dist/src/pipelineCloseout.js +1905 -185
  39. package/dist/src/plugin.js +2843 -145
  40. package/dist/src/stages/auto-merge.js +28 -16
  41. package/dist/src/stages/commit.js +28 -16
  42. package/dist/src/stages/isolation.d.ts +1 -1
  43. package/dist/src/stages/isolation.js +5 -3
  44. package/dist/src/stages/merge-gate.js +35 -3
  45. package/dist/src/stages/open-pr.js +28 -16
  46. package/dist/src/stages/push.js +28 -16
  47. package/dist/src/stages/source-closeout.js +28 -16
  48. package/package.json +29 -16
  49. package/dist/src/branch-naming.d.ts +0 -15
  50. package/dist/src/branch-naming.js +0 -33
  51. package/dist/src/closeoutEquivalence.d.ts +0 -37
  52. package/dist/src/closeoutEquivalence.js +0 -78
  53. package/dist/src/closeoutShadowHarness.d.ts +0 -27
  54. package/dist/src/closeoutShadowHarness.js +0 -29
@@ -0,0 +1,429 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/bundle-default-lifecycle/src/control-plane/hooks/task-runtime-start.ts
5
+ import { existsSync as existsSync4 } from "fs";
6
+ import { resolve as resolve2 } from "path";
7
+ import { resolveProjectRoot } from "@rig/hook-kit";
8
+ import { resolveRuntimeWorkspaceLayout } from "@rig/core/layout";
9
+
10
+ // packages/bundle-default-lifecycle/src/control-plane/native/git-ops.ts
11
+ import { existsSync as existsSync3, lstatSync, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
12
+ import { dirname, isAbsolute, resolve } from "path";
13
+ import { loadDotEnvSecrets, resolveRuntimeSecrets } from "@rig/core/baked-secrets";
14
+ import { loadRuntimeContext, loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
15
+
16
+ // packages/bundle-default-lifecycle/src/control-plane/task-data.ts
17
+ import { TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
18
+ import { defineCapability } from "@rig/core/capability";
19
+ import { requireInstalledCapability } from "@rig/core/capability-loaders";
20
+ var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
21
+ function taskData() {
22
+ return requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before running the lifecycle.");
23
+ }
24
+
25
+ // packages/bundle-default-lifecycle/src/control-plane/native/git-ops.ts
26
+ import { nowIso, runCapture as baseRunCapture } from "@rig/core/exec";
27
+ import { resolveCheckoutRoot as resolveMonorepoRoot } from "@rig/core/checkout-root";
28
+ import { getScopeRules } from "@rig/core/scope-rules";
29
+
30
+ // packages/bundle-default-lifecycle/src/native/github-auth-env.ts
31
+ import { existsSync, readFileSync } from "fs";
32
+ function cleanToken(value) {
33
+ const trimmed = value?.trim() ?? "";
34
+ return trimmed.length > 0 ? trimmed : null;
35
+ }
36
+ function authStateToken(env = process.env) {
37
+ const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
38
+ if (!file || !existsSync(file))
39
+ return null;
40
+ try {
41
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
42
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ // packages/bundle-default-lifecycle/src/control-plane/native/git-ops.ts
49
+ import { safePathSegment } from "@rig/core/safe-identifiers";
50
+
51
+ // packages/bundle-default-lifecycle/src/native/host-git.ts
52
+ import { existsSync as existsSync2 } from "fs";
53
+ function isRuntimeGatewayGitPath(candidate) {
54
+ return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
55
+ }
56
+ function resolveHostGitBinary() {
57
+ const candidates = [
58
+ process.env.RIG_GIT_BIN?.trim() || "",
59
+ "/usr/bin/git",
60
+ "/opt/homebrew/bin/git",
61
+ "/usr/local/bin/git"
62
+ ];
63
+ const bunResolved = Bun.which("git");
64
+ if (bunResolved && !isRuntimeGatewayGitPath(bunResolved)) {
65
+ candidates.push(bunResolved);
66
+ }
67
+ for (const candidate of candidates) {
68
+ if (!candidate || isRuntimeGatewayGitPath(candidate)) {
69
+ continue;
70
+ }
71
+ if (existsSync2(candidate)) {
72
+ return candidate;
73
+ }
74
+ }
75
+ return "git";
76
+ }
77
+
78
+ // packages/bundle-default-lifecycle/src/control-plane/native/git-ops.ts
79
+ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
80
+ "changed-files.txt",
81
+ "contract-changes.md",
82
+ "decision-log.md",
83
+ "git-state.txt",
84
+ "next-actions.md",
85
+ "pr-state.json",
86
+ "task-result.json",
87
+ "validation-summary.json"
88
+ ]);
89
+ function resolveOptionalMonorepoRoot(projectRoot) {
90
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
91
+ if (runtimeWorkspace && existsSync3(resolve(runtimeWorkspace, ".git"))) {
92
+ return resolve(runtimeWorkspace);
93
+ }
94
+ try {
95
+ return resolveMonorepoRoot(projectRoot);
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ function safeCurrentTaskId(projectRoot) {
101
+ try {
102
+ const taskId = taskData().currentTaskId(projectRoot);
103
+ return /^bd-[a-z0-9-]+$/.test(taskId) ? taskId : "";
104
+ } catch {
105
+ return "";
106
+ }
107
+ }
108
+ function gitCmd(projectRoot, repoRoot, ...args) {
109
+ return [resolveHostGitBinary(), "-C", repoRoot, ...args];
110
+ }
111
+ function gitSyncBranch(projectRoot, taskId, targetRepo = "monorepo") {
112
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
113
+ if (!resolvedTask) {
114
+ throw new Error("No task specified and no active task in session.");
115
+ }
116
+ const repoRoot = targetRepo === "monorepo" ? resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot(projectRoot) : projectRoot;
117
+ const repoLabel = targetRepo === "monorepo" ? "Monorepo" : "Project";
118
+ if (!existsSync3(resolve(repoRoot, ".git"))) {
119
+ throw new Error(`${repoLabel} repo not found at ${repoRoot}`);
120
+ }
121
+ const branchId = resolveTaskBranchId(projectRoot, resolvedTask);
122
+ const branchTarget = `rig/${branchId}`;
123
+ const current = branchName(projectRoot, repoRoot);
124
+ if (current === branchTarget) {
125
+ console.log(`${repoLabel} branch: already on ${branchTarget}`);
126
+ return;
127
+ }
128
+ const hasBranch = runCapture(gitCmd(projectRoot, repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branchTarget}`), projectRoot).exitCode === 0;
129
+ const cmd = hasBranch && current === "HEAD" ? gitCmd(projectRoot, repoRoot, "checkout", "-B", branchTarget) : hasBranch ? gitCmd(projectRoot, repoRoot, "checkout", branchTarget) : gitCmd(projectRoot, repoRoot, "checkout", "-b", branchTarget);
130
+ const checkout = runCapture(cmd, projectRoot);
131
+ if (checkout.exitCode !== 0) {
132
+ throw new Error(`Failed to sync ${repoLabel.toLowerCase()} branch: ${checkout.stderr || checkout.stdout}`);
133
+ }
134
+ const action = hasBranch && current === "HEAD" ? "reset" : hasBranch ? "checked out" : "created";
135
+ console.log(`${repoLabel} branch: ${action} ${branchTarget}`);
136
+ }
137
+ function resolveTaskBranchId(projectRoot, taskId) {
138
+ if (/^bd-[a-z0-9-]+$/.test(taskId)) {
139
+ return taskId;
140
+ }
141
+ const normalizedTaskId = taskData().lookupTask(projectRoot, taskId);
142
+ if (normalizedTaskId) {
143
+ return normalizedTaskId;
144
+ }
145
+ const currentTask = taskData().currentTaskId(projectRoot);
146
+ if (currentTask && currentTask === taskId) {
147
+ return currentTask;
148
+ }
149
+ const runtimeIdFromEnv = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
150
+ if (runtimeIdFromEnv.startsWith("task-") && runtimeIdFromEnv.length > "task-".length) {
151
+ return runtimeIdFromEnv.slice("task-".length);
152
+ }
153
+ try {
154
+ const runtimeIdFromContext = loadRuntimeContextFromEnv()?.runtimeId || "";
155
+ if (runtimeIdFromContext.startsWith("task-") && runtimeIdFromContext.length > "task-".length) {
156
+ return runtimeIdFromContext.slice("task-".length);
157
+ }
158
+ } catch {}
159
+ const artifactDir = taskData().artifactDirForId(projectRoot, taskId);
160
+ if (existsSync3(artifactDir)) {
161
+ return taskId;
162
+ }
163
+ throw new Error(`Unknown task id: ${taskId}`);
164
+ }
165
+ function branchName(projectRoot, repo) {
166
+ return runCapture(gitCmd(projectRoot, repo, "rev-parse", "--abbrev-ref", "HEAD"), projectRoot).stdout.trim();
167
+ }
168
+ function runCapture(command, cwd, projectRoot = cwd) {
169
+ return baseRunCapture(command, cwd, runtimeGitEnv(projectRoot));
170
+ }
171
+ function runtimeGitEnv(projectRoot) {
172
+ const { ctx, runtimeRoot } = resolveRuntimeMetadata(projectRoot);
173
+ const runtimeHome = runtimeRoot ? resolve(runtimeRoot, "home") : "";
174
+ const runtimeTmp = runtimeRoot ? resolve(runtimeRoot, "tmp") : "";
175
+ const runtimeCache = runtimeRoot ? resolve(runtimeRoot, "cache") : "";
176
+ const runtimeKnownHosts = runtimeHome ? resolve(runtimeHome, ".ssh", "known_hosts") : "";
177
+ const runtimeKey = runtimeHome ? resolve(runtimeHome, ".ssh", "rig-agent-key") : "";
178
+ const env = {};
179
+ if (ctx?.workspaceDir) {
180
+ env.PROJECT_RIG_ROOT = projectRoot;
181
+ env.RIG_TASK_WORKSPACE = ctx.workspaceDir;
182
+ env.MONOREPO_ROOT = ctx.workspaceDir;
183
+ env.MONOREPO_MAIN_ROOT = resolveMonorepoRoot(projectRoot);
184
+ } else if (projectRoot) {
185
+ env.PROJECT_RIG_ROOT = projectRoot;
186
+ }
187
+ if (runtimeRoot) {
188
+ env.RIG_RUNTIME_HOME = runtimeRoot;
189
+ }
190
+ if (runtimeHome && existsSync3(runtimeHome)) {
191
+ env.HOME = runtimeHome;
192
+ env.OPENSSL_CONF = ensureRuntimeOpenSslConfig(runtimeHome);
193
+ }
194
+ if (runtimeTmp && existsSync3(runtimeTmp)) {
195
+ env.TMPDIR = runtimeTmp;
196
+ }
197
+ if (runtimeCache && existsSync3(runtimeCache)) {
198
+ env.XDG_CACHE_HOME = runtimeCache;
199
+ }
200
+ const workspaceSecrets = loadDotEnvSecrets(ctx?.workspaceDir || projectRoot, process.env);
201
+ for (const [key, value] of Object.entries(resolveRuntimeSecrets(process.env, workspaceSecrets))) {
202
+ if (key === "GITHUB_SSH_KEY" || !value) {
203
+ continue;
204
+ }
205
+ env[key] = value;
206
+ }
207
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || authStateToken(process.env) || "";
208
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
209
+ env.GITHUB_TOKEN = rigGithubToken;
210
+ }
211
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
212
+ env.GITHUB_TOKEN = env.GH_TOKEN;
213
+ }
214
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
215
+ env.GH_TOKEN = env.GITHUB_TOKEN;
216
+ }
217
+ if (!env.GREPTILE_GITHUB_TOKEN && env.GITHUB_TOKEN) {
218
+ env.GREPTILE_GITHUB_TOKEN = env.GITHUB_TOKEN;
219
+ }
220
+ const persistedSecrets = loadPersistedRuntimeSecrets(runtimeRoot);
221
+ for (const [key, value] of Object.entries(persistedSecrets)) {
222
+ if (!value)
223
+ continue;
224
+ if (!env[key]) {
225
+ env[key] = value;
226
+ }
227
+ }
228
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
229
+ env.GITHUB_TOKEN = env.GH_TOKEN;
230
+ }
231
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
232
+ env.GH_TOKEN = env.GITHUB_TOKEN;
233
+ }
234
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
235
+ if (gitHubToken) {
236
+ env.RIG_GITHUB_TOKEN = gitHubToken;
237
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
238
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
239
+ applyGitHubCredentialHelperEnv(env);
240
+ }
241
+ if (runtimeKnownHosts && existsSync3(runtimeKnownHosts)) {
242
+ const sshParts = [
243
+ "ssh",
244
+ `-o UserKnownHostsFile="${runtimeKnownHosts}"`,
245
+ "-o StrictHostKeyChecking=yes",
246
+ "-F /dev/null"
247
+ ];
248
+ if (runtimeKey && existsSync3(runtimeKey)) {
249
+ sshParts.splice(1, 0, `-i "${runtimeKey}"`, "-o IdentitiesOnly=yes");
250
+ }
251
+ env.GIT_SSH_COMMAND = sshParts.join(" ");
252
+ } else if (process.env.GIT_SSH_COMMAND?.trim()) {
253
+ env.GIT_SSH_COMMAND = process.env.GIT_SSH_COMMAND;
254
+ }
255
+ return Object.keys(env).length > 0 ? env : undefined;
256
+ }
257
+ function applyGitHubCredentialHelperEnv(env) {
258
+ env.GIT_TERMINAL_PROMPT = "0";
259
+ env.GIT_CONFIG_COUNT = "2";
260
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
261
+ env.GIT_CONFIG_VALUE_0 = "";
262
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
263
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
264
+ }
265
+ function loadPersistedRuntimeSecrets(runtimeRoot) {
266
+ if (!runtimeRoot) {
267
+ return {};
268
+ }
269
+ const path = resolve(runtimeRoot, "runtime-secrets.json");
270
+ if (!existsSync3(path)) {
271
+ return {};
272
+ }
273
+ try {
274
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
275
+ const allowed = new Set(["GITHUB_TOKEN", "GH_TOKEN", "RIG_GITHUB_TOKEN"]);
276
+ const entries = Object.entries(parsed).filter((entry) => typeof entry[1] === "string" && allowed.has(entry[0]));
277
+ return Object.fromEntries(entries);
278
+ } catch {
279
+ return {};
280
+ }
281
+ }
282
+ function ensureRuntimeOpenSslConfig(runtimeHome) {
283
+ const sslDir = resolve(runtimeHome, ".ssl");
284
+ const sslConfig = resolve(sslDir, "openssl.cnf");
285
+ if (!existsSync3(sslDir)) {
286
+ mkdirSync(sslDir, { recursive: true });
287
+ }
288
+ if (!existsSync3(sslConfig)) {
289
+ writeFileSync(sslConfig, `# Rig runtime OpenSSL config placeholder
290
+ `);
291
+ }
292
+ return sslConfig;
293
+ }
294
+ function resolveRuntimeMetadata(projectRoot) {
295
+ const contextFile = process.env.RIG_RUNTIME_CONTEXT_FILE?.trim();
296
+ const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
297
+ let ctx = loadRuntimeContextFromEnv();
298
+ if (runtimeHome) {
299
+ return {
300
+ ctx,
301
+ runtimeRoot: runtimeHome
302
+ };
303
+ }
304
+ if (contextFile) {
305
+ return {
306
+ ctx,
307
+ runtimeRoot: dirname(resolve(contextFile))
308
+ };
309
+ }
310
+ const inferredContextFile = findRuntimeContextFile(projectRoot);
311
+ if (existsSync3(inferredContextFile)) {
312
+ try {
313
+ ctx = loadRuntimeContext(inferredContextFile);
314
+ } catch {}
315
+ return {
316
+ ctx,
317
+ runtimeRoot: dirname(inferredContextFile)
318
+ };
319
+ }
320
+ return { ctx, runtimeRoot: "" };
321
+ }
322
+ function findRuntimeContextFile(startPath) {
323
+ let current = resolve(startPath);
324
+ while (true) {
325
+ const candidate = resolve(current, "runtime-context.json");
326
+ if (existsSync3(candidate)) {
327
+ return candidate;
328
+ }
329
+ const parent = dirname(current);
330
+ if (parent === current) {
331
+ return "";
332
+ }
333
+ current = parent;
334
+ }
335
+ }
336
+
337
+ // packages/bundle-default-lifecycle/src/control-plane/hooks/task-runtime-start.ts
338
+ import { taskRuntimeId } from "@rig/core/safe-identifiers";
339
+ import { ISOLATION_BACKEND, REPO_OPERATIONS_CAPABILITY } from "@rig/contracts";
340
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
341
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
342
+ import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/core/runtime-context";
343
+ var RepoOperationsCap = defineCapability2(REPO_OPERATIONS_CAPABILITY);
344
+ function resolveTaskRuntimeMode(raw) {
345
+ if (raw === "off" || raw === "worktree") {
346
+ return raw;
347
+ }
348
+ return "worktree";
349
+ }
350
+ function resolveActiveRuntimeId(taskId) {
351
+ const runtimeIdFromEnv = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
352
+ if (runtimeIdFromEnv) {
353
+ return runtimeIdFromEnv;
354
+ }
355
+ try {
356
+ const runtimeIdFromContext = loadRuntimeContextFromEnv2()?.runtimeId?.trim();
357
+ if (runtimeIdFromContext) {
358
+ return runtimeIdFromContext;
359
+ }
360
+ } catch {}
361
+ return taskRuntimeId(taskId);
362
+ }
363
+ function resolveActiveWorkspaceDir() {
364
+ const workspaceDirFromEnv = (process.env.RIG_TASK_WORKSPACE || "").trim();
365
+ if (workspaceDirFromEnv) {
366
+ return workspaceDirFromEnv;
367
+ }
368
+ try {
369
+ const workspaceDirFromContext = loadRuntimeContextFromEnv2()?.workspaceDir?.trim();
370
+ if (workspaceDirFromContext) {
371
+ return workspaceDirFromContext;
372
+ }
373
+ } catch {}
374
+ return "";
375
+ }
376
+ function isProvisionedRuntimeWorkspace(workspaceDir) {
377
+ if (!workspaceDir) {
378
+ return false;
379
+ }
380
+ const layout = resolveRuntimeWorkspaceLayout(workspaceDir);
381
+ return existsSync4(resolve2(workspaceDir, ".git")) && existsSync4(resolve2(layout.binDir, "rig-agent")) && existsSync4(resolve2(layout.binDir, "hooks", "scope-guard")) && existsSync4(resolve2(layout.sessionDir, "session.json"));
382
+ }
383
+ async function main() {
384
+ const projectRoot = resolveProjectRoot();
385
+ const taskId = taskData().currentTaskId(projectRoot);
386
+ if (!taskId) {
387
+ return;
388
+ }
389
+ const runtimeId = resolveActiveRuntimeId(taskId);
390
+ const canonicalRuntimeId = taskRuntimeId(taskId);
391
+ const workspaceDir = resolveActiveWorkspaceDir();
392
+ if (runtimeId === canonicalRuntimeId) {
393
+ if (isProvisionedRuntimeWorkspace(workspaceDir)) {
394
+ console.log(`Task runtime already provisioned (${runtimeId}); skipping in-session reprovision.`);
395
+ console.log(`Task runtime ready (worktree): ${workspaceDir}`);
396
+ return;
397
+ }
398
+ gitSyncBranch(projectRoot, taskId);
399
+ const repoOperations = await requireCapabilityForRoot(projectRoot, RepoOperationsCap, `No REPO_OPERATIONS capability is registered for project root "${projectRoot}". ` + "Install @rig/repos-plugin (it ships in the default bundle) so task repo pins can be refreshed.");
400
+ repoOperations.repoEnsure(projectRoot, taskId);
401
+ } else {
402
+ console.log(`Task runtime already provisioned (${runtimeId}); skipping canonical branch sync.`);
403
+ console.log(`Task runtime already provisioned (${runtimeId}); skipping repo pin refresh.`);
404
+ if (isProvisionedRuntimeWorkspace(workspaceDir)) {
405
+ console.log(`Task runtime ready (worktree): ${workspaceDir}`);
406
+ return;
407
+ }
408
+ }
409
+ const mode = resolveTaskRuntimeMode(process.env.RIG_TASK_RUNTIME_MODE);
410
+ if (mode !== "off") {
411
+ const isolationBackend = await requireCapabilityForRoot(projectRoot, defineCapability2(ISOLATION_BACKEND), `No ISOLATION_BACKEND capability is registered for project root "${projectRoot}". ` + "Install @rig/isolation-plugin (it ships in the default bundle) so runtime provisioning can resolve a backend.");
412
+ const runtime = await isolationBackend.ensureAgentRuntime({
413
+ projectRoot,
414
+ id: runtimeId,
415
+ taskId,
416
+ mode
417
+ });
418
+ console.log(`Task runtime ready (${mode}): ${runtime.workspaceDir}`);
419
+ }
420
+ }
421
+ if (import.meta.main || process.env.RIG_HOOK_ROLE === "task-runtime-start") {
422
+ main().catch((error) => {
423
+ console.error(error);
424
+ process.exit(1);
425
+ });
426
+ }
427
+ export {
428
+ main
429
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * materialize-task-config.ts
3
+ *
4
+ * Compatibility/export helper from the declarative plugin-host (rig.config →
5
+ * task source) to the legacy disk-backed `.rig/task-config.json` format.
6
+ *
7
+ * Normal runtime dispatch/provision reads TaskRecord values directly from the
8
+ * configured task source and must not call this helper. Keep it available for
9
+ * old migration/export flows that intentionally need a legacy task-config file.
10
+ *
11
+ * Operator-authored entries (auto_synced !== true) are preserved as-is so
12
+ * hand-edits on disk win over source-derived defaults.
13
+ */
14
+ export interface MaterializeResult {
15
+ /** Absolute path to .rig/task-config.json that was written. */
16
+ configPath: string;
17
+ /** Number of source-derived entries merged in (existing or new). */
18
+ syncedCount: number;
19
+ }
20
+ /**
21
+ * Materialize plugin-host tasks into `.rig/task-config.json` for explicit
22
+ * compatibility/export use.
23
+ *
24
+ * @returns The result on success, or `null` when no rig.config is present, no
25
+ * task source is registered, or the source returned no tasks. Callers
26
+ * must treat `null` as "nothing exported" rather than as part of the
27
+ * normal dispatch path.
28
+ */
29
+ export declare function materializePluginHostTaskConfig(projectRoot: string): Promise<MaterializeResult | null>;
@@ -0,0 +1,95 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // packages/bundle-default-lifecycle/src/control-plane/materialize-task-config.ts
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { dirname, resolve } from "path";
7
+ async function materializePluginHostTaskConfig(projectRoot) {
8
+ const hasConfig = existsSync(resolve(projectRoot, "rig.config.ts")) || existsSync(resolve(projectRoot, "rig.config.json"));
9
+ if (!hasConfig)
10
+ return null;
11
+ const { buildPluginHostContext } = await import("@rig/core/plugin-host-context");
12
+ let ctx;
13
+ try {
14
+ ctx = await buildPluginHostContext(projectRoot);
15
+ } catch (err) {
16
+ console.warn(`[materialize-task-config] plugin host build failed (treating as legacy path): ${err instanceof Error ? err.message : String(err)}`);
17
+ return null;
18
+ }
19
+ if (!ctx)
20
+ return null;
21
+ const sources = ctx.taskSourceRegistry.list();
22
+ if (sources.length === 0)
23
+ return null;
24
+ const source = sources[0];
25
+ const tasks = await source.list();
26
+ if (tasks.length === 0)
27
+ return null;
28
+ const configPath = resolve(projectRoot, ".rig", "task-config.json");
29
+ const existing = existsSync(configPath) ? safeJsonRead(configPath) : {};
30
+ const merged = { ...existing };
31
+ let syncedCount = 0;
32
+ for (const task of tasks) {
33
+ const t = task;
34
+ const id = typeof t.id === "string" ? t.id.trim() : "";
35
+ if (!id)
36
+ continue;
37
+ const existingEntry = merged[id];
38
+ const existingIsOperatorEdit = existingEntry !== undefined && typeof existingEntry === "object" && existingEntry !== null && existingEntry.auto_synced !== true;
39
+ if (existingIsOperatorEdit) {
40
+ continue;
41
+ }
42
+ merged[id] = synthesizeEntry(t, ctx.config.taskSource);
43
+ syncedCount += 1;
44
+ }
45
+ mkdirSync(dirname(configPath), { recursive: true });
46
+ writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
47
+ `, "utf-8");
48
+ return { configPath, syncedCount };
49
+ }
50
+ function synthesizeEntry(t, taskSource) {
51
+ const role = typeof t.role === "string" ? t.role : undefined;
52
+ const scope = Array.isArray(t.scope) ? t.scope.filter((s) => typeof s === "string") : [];
53
+ const validation = Array.isArray(t.validators) ? t.validators.filter((v) => typeof v === "string") : Array.isArray(t.validation) ? t.validation.filter((v) => typeof v === "string") : [];
54
+ const description = typeof t.description === "string" ? t.description : typeof t.body === "string" ? t.body : undefined;
55
+ const acceptance = typeof t.acceptanceCriteria === "string" ? t.acceptanceCriteria : undefined;
56
+ const title = typeof t.title === "string" ? t.title : undefined;
57
+ const status = typeof t.status === "string" ? t.status : undefined;
58
+ const sourceIssueId = typeof t.sourceIssueId === "string" ? t.sourceIssueId : taskSource.kind === "github-issues" && taskSource.owner && taskSource.repo ? `${taskSource.owner}/${taskSource.repo}#${String(t.id)}` : undefined;
59
+ return {
60
+ auto_synced: true,
61
+ _rig: {
62
+ taskSource: materializedTaskSource(taskSource),
63
+ ...sourceIssueId ? { sourceIssueId } : {}
64
+ },
65
+ ...title ? { title } : {},
66
+ ...status ? { status } : {},
67
+ ...sourceIssueId ? { sourceIssueId } : {},
68
+ ...role ? { role } : {},
69
+ scope,
70
+ validation,
71
+ ...description ? { description } : {},
72
+ ...acceptance ? { acceptance_criteria: acceptance } : {}
73
+ };
74
+ }
75
+ function materializedTaskSource(taskSource) {
76
+ return {
77
+ kind: taskSource.kind,
78
+ ...taskSource.path ? { path: taskSource.path } : {},
79
+ ...taskSource.owner ? { owner: taskSource.owner } : {},
80
+ ...taskSource.repo ? { repo: taskSource.repo } : {},
81
+ ...taskSource.labels ? { labels: taskSource.labels } : {},
82
+ ...taskSource.state ? { state: taskSource.state } : {}
83
+ };
84
+ }
85
+ function safeJsonRead(path) {
86
+ try {
87
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
88
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
89
+ } catch {
90
+ return {};
91
+ }
92
+ }
93
+ export {
94
+ materializePluginHostTaskConfig
95
+ };
@@ -0,0 +1,67 @@
1
+ export declare function shouldScopeGitCommit(args: string[], hasTaskContext: boolean): boolean;
2
+ export declare function gitStatus(projectRoot: string, taskId?: string): void;
3
+ export declare function gitChanged(projectRoot: string, taskId: string | undefined, scoped: boolean): string[];
4
+ export declare function gitPreflight(projectRoot: string, taskId: string | undefined, strict: boolean): boolean;
5
+ export declare function gitSyncBranch(projectRoot: string, taskId?: string, targetRepo?: "monorepo" | "project"): void;
6
+ export declare function gitCommit(options: {
7
+ projectRoot: string;
8
+ taskId?: string;
9
+ target: "monorepo" | "project" | "both";
10
+ message?: string;
11
+ allowEmpty: boolean;
12
+ scoped?: boolean;
13
+ }): void;
14
+ export declare function gitSnapshot(projectRoot: string, taskId?: string, outputPath?: string): string;
15
+ type ReviewerSource = "flag" | "task-config" | "changed-files" | "env";
16
+ export type GitOpenPrOptions = {
17
+ projectRoot: string;
18
+ taskId?: string;
19
+ target?: "monorepo" | "project";
20
+ reviewer?: string;
21
+ base?: string;
22
+ title?: string;
23
+ body?: string;
24
+ draft?: boolean;
25
+ };
26
+ export type GitOpenPrResult = {
27
+ url: string;
28
+ reviewer?: string;
29
+ reviewerSource?: ReviewerSource;
30
+ target: "monorepo" | "project";
31
+ repoLabel: string;
32
+ branch: string;
33
+ base: string;
34
+ };
35
+ export type GitMergePrOptions = {
36
+ projectRoot: string;
37
+ pr: GitOpenPrResult;
38
+ method?: "squash" | "merge" | "rebase";
39
+ deleteBranch?: boolean;
40
+ /** Raw 40-char head SHA the merge must match (derived upstream by the lifecycle gate). */
41
+ matchHeadCommit: string;
42
+ };
43
+ export type GitMergePrResult = {
44
+ status: "already-merged" | "merged";
45
+ url: string;
46
+ };
47
+ export declare function gitOpenPr(options: GitOpenPrOptions): GitOpenPrResult;
48
+ export declare function resolveTaskBranchRef(projectRoot: string, taskId: string): string;
49
+ export declare function gitMergePr(options: GitMergePrOptions): GitMergePrResult;
50
+ export declare function readPrMetadata(projectRoot: string, taskId: string): GitOpenPrResult[];
51
+ declare function readChangedFilesManifest(projectRoot: string, taskId: string): string[];
52
+ declare function refreshChangedFilesManifest(projectRoot: string, taskId: string): string;
53
+ declare function buildStageAddArgs(repoRoot: string, files: string[], scoped: boolean): string[] | null;
54
+ declare function resolveScopedStageFilesForRepo(projectRoot: string, repoRoot: string, taskId: string | undefined, files: string[]): string[];
55
+ declare function resolveScopedFilesForRepo(projectRoot: string, repoRoot: string, files: string[]): string[];
56
+ declare function resolveChangedTaskArtifactFiles(projectRoot: string, repoRoot: string, taskId: string): string[];
57
+ declare function stageExcludePathspecs(repoRoot: string): string[];
58
+ export declare const __testOnly: {
59
+ buildStageAddArgs: typeof buildStageAddArgs;
60
+ refreshChangedFilesManifest: typeof refreshChangedFilesManifest;
61
+ readChangedFilesManifest: typeof readChangedFilesManifest;
62
+ resolveChangedTaskArtifactFiles: typeof resolveChangedTaskArtifactFiles;
63
+ resolveScopedStageFilesForRepo: typeof resolveScopedStageFilesForRepo;
64
+ resolveScopedFilesForRepo: typeof resolveScopedFilesForRepo;
65
+ stageExcludePathspecs: typeof stageExcludePathspecs;
66
+ };
67
+ export {};