@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,2860 @@
1
+ // @bun
2
+ // packages/runtime/src/control-plane/native/git-ops.ts
3
+ import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
4
+ import { dirname as dirname7, isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ // packages/runtime/src/control-plane/runtime/baked-secrets.ts
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { resolve } from "path";
10
+ var BAKED_RUNTIME_SECRETS = {
11
+ ANTHROPIC_API_KEY: typeof RIG_BAKED_ANTHROPIC_API_KEY !== "undefined" ? RIG_BAKED_ANTHROPIC_API_KEY : "",
12
+ OPENAI_API_KEY: typeof RIG_BAKED_OPENAI_API_KEY !== "undefined" ? RIG_BAKED_OPENAI_API_KEY : "",
13
+ OPENROUTER_API_KEY: typeof RIG_BAKED_OPENROUTER_API_KEY !== "undefined" ? RIG_BAKED_OPENROUTER_API_KEY : "",
14
+ AI_REVIEW_MODE: typeof RIG_BAKED_AI_REVIEW_MODE !== "undefined" ? RIG_BAKED_AI_REVIEW_MODE : "",
15
+ AI_REVIEW_PROVIDER: typeof RIG_BAKED_AI_REVIEW_PROVIDER !== "undefined" ? RIG_BAKED_AI_REVIEW_PROVIDER : "",
16
+ GREPTILE_API_BASE: typeof RIG_BAKED_GREPTILE_API_BASE !== "undefined" ? RIG_BAKED_GREPTILE_API_BASE : "",
17
+ GREPTILE_REMOTE: typeof RIG_BAKED_GREPTILE_REMOTE !== "undefined" ? RIG_BAKED_GREPTILE_REMOTE : "",
18
+ GREPTILE_REPOSITORY: typeof RIG_BAKED_GREPTILE_REPOSITORY !== "undefined" ? RIG_BAKED_GREPTILE_REPOSITORY : "",
19
+ GREPTILE_CONTEXT_BRANCH: typeof RIG_BAKED_GREPTILE_CONTEXT_BRANCH !== "undefined" ? RIG_BAKED_GREPTILE_CONTEXT_BRANCH : "",
20
+ GREPTILE_DEFAULT_BRANCH: typeof RIG_BAKED_GREPTILE_DEFAULT_BRANCH !== "undefined" ? RIG_BAKED_GREPTILE_DEFAULT_BRANCH : "",
21
+ GREPTILE_API_KEY: typeof RIG_BAKED_GREPTILE_API_KEY !== "undefined" ? RIG_BAKED_GREPTILE_API_KEY : "",
22
+ GREPTILE_GITHUB_TOKEN: typeof RIG_BAKED_GREPTILE_GITHUB_TOKEN !== "undefined" ? RIG_BAKED_GREPTILE_GITHUB_TOKEN : "",
23
+ GREPTILE_POLL_ATTEMPTS: typeof RIG_BAKED_GREPTILE_POLL_ATTEMPTS !== "undefined" ? RIG_BAKED_GREPTILE_POLL_ATTEMPTS : "",
24
+ GREPTILE_POLL_INTERVAL_MS: typeof RIG_BAKED_GREPTILE_POLL_INTERVAL_MS !== "undefined" ? RIG_BAKED_GREPTILE_POLL_INTERVAL_MS : "",
25
+ GH_TOKEN: typeof RIG_BAKED_GITHUB_TOKEN !== "undefined" ? RIG_BAKED_GITHUB_TOKEN : "",
26
+ GITHUB_TOKEN: typeof RIG_BAKED_GITHUB_TOKEN !== "undefined" ? RIG_BAKED_GITHUB_TOKEN : "",
27
+ GITHUB_SSH_KEY: typeof RIG_BAKED_GITHUB_SSH_KEY !== "undefined" ? RIG_BAKED_GITHUB_SSH_KEY : "",
28
+ AWS_ACCESS_KEY_ID: typeof RIG_BAKED_AWS_ACCESS_KEY_ID !== "undefined" ? RIG_BAKED_AWS_ACCESS_KEY_ID : "",
29
+ AWS_SECRET_ACCESS_KEY: typeof RIG_BAKED_AWS_SECRET_ACCESS_KEY !== "undefined" ? RIG_BAKED_AWS_SECRET_ACCESS_KEY : "",
30
+ AWS_REGION: typeof RIG_BAKED_AWS_REGION !== "undefined" ? RIG_BAKED_AWS_REGION : "",
31
+ LINEAR_API_KEY: typeof RIG_BAKED_LINEAR_API_KEY !== "undefined" ? RIG_BAKED_LINEAR_API_KEY : "",
32
+ LINEAR_WEBHOOK_SECRET: typeof RIG_BAKED_LINEAR_WEBHOOK_SECRET !== "undefined" ? RIG_BAKED_LINEAR_WEBHOOK_SECRET : ""
33
+ };
34
+ function resolveRuntimeSecrets(env, baked = BAKED_RUNTIME_SECRETS) {
35
+ const resolved = {};
36
+ const keys = new Set([
37
+ ...Object.keys(BAKED_RUNTIME_SECRETS),
38
+ ...Object.keys(baked)
39
+ ]);
40
+ for (const key of keys) {
41
+ const envValue = env[key]?.trim();
42
+ const bakedValue = baked[key]?.trim();
43
+ if (envValue) {
44
+ resolved[key] = envValue;
45
+ } else if (bakedValue) {
46
+ resolved[key] = bakedValue;
47
+ }
48
+ }
49
+ return resolved;
50
+ }
51
+ function loadDotEnvSecrets(projectRoot, env = process.env) {
52
+ const dotenvPath = resolve(projectRoot, ".env");
53
+ if (!existsSync(dotenvPath)) {
54
+ return {};
55
+ }
56
+ const parsed = {};
57
+ const lines = readFileSync(dotenvPath, "utf-8").split(/\r?\n/);
58
+ for (const rawLine of lines) {
59
+ const line = rawLine.trim();
60
+ if (!line || line.startsWith("#")) {
61
+ continue;
62
+ }
63
+ const exportMatch = line.match(/^(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)$/);
64
+ if (!exportMatch) {
65
+ continue;
66
+ }
67
+ const key = exportMatch[1];
68
+ if (!(key in BAKED_RUNTIME_SECRETS)) {
69
+ continue;
70
+ }
71
+ const value = expandShellValue(exportMatch[2] ?? "", { ...env, ...parsed });
72
+ if (value) {
73
+ parsed[key] = value;
74
+ }
75
+ }
76
+ return parsed;
77
+ }
78
+ function expandShellValue(rawValue, env) {
79
+ let value = rawValue.trim();
80
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
81
+ value = value.slice(1, -1);
82
+ }
83
+ return value.replace(/\$\{([A-Z0-9_]+)(:-([^}]*))?\}/g, (_match, name, _defaultGroup, fallback) => {
84
+ const envValue = env[name]?.trim();
85
+ if (envValue) {
86
+ return envValue;
87
+ }
88
+ return fallback ?? "";
89
+ });
90
+ }
91
+
92
+ // packages/runtime/src/control-plane/runtime/context.ts
93
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
94
+ import { dirname, resolve as resolve2 } from "path";
95
+ var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
96
+ var runtimeContextStringFields = [
97
+ "runtimeId",
98
+ "taskId",
99
+ "role",
100
+ "workspaceDir",
101
+ "stateDir",
102
+ "logsDir",
103
+ "sessionDir",
104
+ "sessionFile",
105
+ "policyFile",
106
+ "binDir",
107
+ "createdAt"
108
+ ];
109
+ var runtimeContextArrayFields = ["scopes", "validation"];
110
+ var runtimeContextOptionalStringFields = [
111
+ "artifactRoot",
112
+ "hostProjectRoot",
113
+ "monorepoMainRoot",
114
+ "monorepoBaseRef",
115
+ "monorepoBaseCommit"
116
+ ];
117
+ function loadRuntimeContext(path) {
118
+ const absPath = resolve2(path);
119
+ if (!existsSync2(absPath)) {
120
+ throw new Error(`RuntimeTaskContext file not found: ${absPath}`);
121
+ }
122
+ let raw;
123
+ try {
124
+ raw = JSON.parse(readFileSync2(absPath, "utf8"));
125
+ } catch (err) {
126
+ throw new Error(`Failed to parse RuntimeTaskContext at ${absPath}: ${String(err)}`);
127
+ }
128
+ if (typeof raw !== "object" || raw === null) {
129
+ throw new Error(`RuntimeTaskContext at ${absPath} is not an object`);
130
+ }
131
+ const obj = raw;
132
+ for (const field of runtimeContextStringFields) {
133
+ if (typeof obj[field] !== "string" || obj[field].length === 0) {
134
+ throw new Error(`RuntimeTaskContext field "${field}" must be a non-empty string (at ${absPath})`);
135
+ }
136
+ }
137
+ for (const field of runtimeContextArrayFields) {
138
+ if (!Array.isArray(obj[field])) {
139
+ throw new Error(`RuntimeTaskContext field "${field}" must be an array (at ${absPath})`);
140
+ }
141
+ if (!obj[field].every((entry) => typeof entry === "string")) {
142
+ throw new Error(`RuntimeTaskContext field "${field}" must be a string[] (at ${absPath})`);
143
+ }
144
+ }
145
+ for (const field of runtimeContextOptionalStringFields) {
146
+ if (field in obj && obj[field] !== undefined && typeof obj[field] !== "string") {
147
+ throw new Error(`RuntimeTaskContext field "${field}" must be a string when present (at ${absPath})`);
148
+ }
149
+ }
150
+ if (obj.browser !== undefined) {
151
+ if (typeof obj.browser !== "object" || obj.browser === null || Array.isArray(obj.browser)) {
152
+ throw new Error(`RuntimeTaskContext field "browser" must be an object when present (at ${absPath})`);
153
+ }
154
+ const browser = obj.browser;
155
+ for (const field of [
156
+ "preset",
157
+ "mode",
158
+ "stateDir",
159
+ "defaultProfile",
160
+ "effectiveProfile",
161
+ "defaultAttachUrl",
162
+ "effectiveAttachUrl",
163
+ "launchHelper",
164
+ "checkHelper",
165
+ "attachInfoHelper",
166
+ "e2eHelper",
167
+ "resetProfileHelper"
168
+ ]) {
169
+ if (typeof browser[field] !== "string" || browser[field].length === 0) {
170
+ throw new Error(`RuntimeTaskContext field "browser.${field}" must be a non-empty string (at ${absPath})`);
171
+ }
172
+ }
173
+ for (const field of ["devCommand", "launchCommand", "checkCommand", "e2eCommand"]) {
174
+ if (browser[field] !== undefined && typeof browser[field] !== "string") {
175
+ throw new Error(`RuntimeTaskContext field "browser.${field}" must be a string when present (at ${absPath})`);
176
+ }
177
+ }
178
+ if (typeof browser.required !== "boolean") {
179
+ throw new Error(`RuntimeTaskContext field "browser.required" must be a boolean (at ${absPath})`);
180
+ }
181
+ }
182
+ if (obj.memory !== undefined) {
183
+ if (typeof obj.memory !== "object" || obj.memory === null || Array.isArray(obj.memory)) {
184
+ throw new Error(`RuntimeTaskContext field "memory" must be an object when present (at ${absPath})`);
185
+ }
186
+ const memory = obj.memory;
187
+ for (const field of ["canonicalPath", "canonicalRef", "canonicalBaseOid", "hydratedPath"]) {
188
+ if (typeof memory[field] !== "string" || memory[field].length === 0) {
189
+ throw new Error(`RuntimeTaskContext field "memory.${field}" must be a non-empty string (at ${absPath})`);
190
+ }
191
+ }
192
+ if (typeof memory.createdFresh !== "boolean") {
193
+ throw new Error(`RuntimeTaskContext field "memory.createdFresh" must be a boolean (at ${absPath})`);
194
+ }
195
+ if (typeof memory.retrieval !== "object" || memory.retrieval === null || Array.isArray(memory.retrieval)) {
196
+ throw new Error(`RuntimeTaskContext field "memory.retrieval" must be an object (at ${absPath})`);
197
+ }
198
+ const retrieval = memory.retrieval;
199
+ for (const field of ["topK", "lexicalWeight", "vectorWeight", "recencyWeight", "confidenceWeight"]) {
200
+ if (typeof retrieval[field] !== "number" || Number.isNaN(retrieval[field])) {
201
+ throw new Error(`RuntimeTaskContext field "memory.retrieval.${field}" must be a number (at ${absPath})`);
202
+ }
203
+ }
204
+ }
205
+ if (obj.initialDirtyFiles !== undefined) {
206
+ if (typeof obj.initialDirtyFiles !== "object" || obj.initialDirtyFiles === null || Array.isArray(obj.initialDirtyFiles)) {
207
+ throw new Error(`RuntimeTaskContext field "initialDirtyFiles" must be an object when present (at ${absPath})`);
208
+ }
209
+ const dirtyFiles = obj.initialDirtyFiles;
210
+ for (const key of ["project", "monorepo"]) {
211
+ if (dirtyFiles[key] === undefined) {
212
+ continue;
213
+ }
214
+ if (!Array.isArray(dirtyFiles[key]) || !dirtyFiles[key].every((entry) => typeof entry === "string")) {
215
+ throw new Error(`RuntimeTaskContext field "initialDirtyFiles.${key}" must be a string[] when present (at ${absPath})`);
216
+ }
217
+ }
218
+ }
219
+ if (obj.initialHeadCommits !== undefined) {
220
+ if (typeof obj.initialHeadCommits !== "object" || obj.initialHeadCommits === null || Array.isArray(obj.initialHeadCommits)) {
221
+ throw new Error(`RuntimeTaskContext field "initialHeadCommits" must be an object when present (at ${absPath})`);
222
+ }
223
+ const headCommits = obj.initialHeadCommits;
224
+ for (const key of ["project", "monorepo"]) {
225
+ if (headCommits[key] === undefined) {
226
+ continue;
227
+ }
228
+ if (typeof headCommits[key] !== "string") {
229
+ throw new Error(`RuntimeTaskContext field "initialHeadCommits.${key}" must be a string when present (at ${absPath})`);
230
+ }
231
+ }
232
+ }
233
+ return obj;
234
+ }
235
+ function loadRuntimeContextFromEnv(env = process.env) {
236
+ const contextFile = env[RUNTIME_CONTEXT_ENV];
237
+ if (contextFile) {
238
+ return loadRuntimeContext(contextFile);
239
+ }
240
+ const inferred = findRuntimeContextFile(process.cwd());
241
+ if (!inferred) {
242
+ return null;
243
+ }
244
+ return loadRuntimeContext(inferred);
245
+ }
246
+ function findRuntimeContextFile(startPath) {
247
+ let current = resolve2(startPath);
248
+ while (true) {
249
+ const candidate = resolve2(current, "runtime-context.json");
250
+ if (existsSync2(candidate) && isAgentRuntimeContextPath(candidate)) {
251
+ return candidate;
252
+ }
253
+ const parent = dirname(current);
254
+ if (parent === current) {
255
+ return "";
256
+ }
257
+ current = parent;
258
+ }
259
+ }
260
+ function isAgentRuntimeContextPath(path) {
261
+ const normalized = path.replace(/\\/g, "/");
262
+ return /\/\.rig\/runtime-context\.json$/.test(normalized);
263
+ }
264
+
265
+ // packages/runtime/src/control-plane/native/task-ops.ts
266
+ import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
267
+ import { resolve as resolve10 } from "path";
268
+
269
+ // packages/runtime/src/build-time-config.ts
270
+ function normalizeBuildConfig(value) {
271
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
272
+ return {};
273
+ }
274
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
275
+ }
276
+ function readBuildConfig() {
277
+ if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
278
+ return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
279
+ }
280
+ const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
281
+ if (!raw) {
282
+ return {};
283
+ }
284
+ try {
285
+ return normalizeBuildConfig(JSON.parse(raw));
286
+ } catch {
287
+ return {};
288
+ }
289
+ }
290
+
291
+ // packages/runtime/src/control-plane/runtime/tooling/shell.ts
292
+ import { tmpdir } from "os";
293
+ import { basename, dirname as dirname2, resolve as resolve3 } from "path";
294
+ var sharedNativeShellOutputDir = resolve3(tmpdir(), "rig-native");
295
+ var sharedNativeShellOutputPath = resolve3(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
296
+ // packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
297
+ import { tmpdir as tmpdir2 } from "os";
298
+ import { basename as basename2, dirname as dirname3, resolve as resolve4 } from "path";
299
+ var sharedNativeToolsOutputDir = resolve4(tmpdir2(), "rig-native");
300
+ var sharedNativeToolsOutputPath = resolve4(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
301
+ // packages/runtime/src/control-plane/plugin-host-context.ts
302
+ import { createPluginHost } from "@rig/core";
303
+ import { loadConfig } from "@rig/core/load-config";
304
+
305
+ // packages/runtime/src/control-plane/repos/registry.ts
306
+ var MANAGED_REPOS = new Map;
307
+
308
+ // packages/runtime/src/control-plane/native/scope-rules.ts
309
+ var activeRules = null;
310
+ function getScopeRules() {
311
+ return activeRules;
312
+ }
313
+
314
+ // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
315
+ var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
316
+
317
+ // packages/runtime/src/control-plane/native/task-state.ts
318
+ import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
319
+ import { basename as basename4, resolve as resolve9 } from "path";
320
+
321
+ // packages/runtime/src/control-plane/state-sync/types.ts
322
+ var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
323
+ "draft",
324
+ "open",
325
+ "ready",
326
+ "queued",
327
+ "in_progress",
328
+ "under_review",
329
+ "blocked",
330
+ "completed",
331
+ "cancelled"
332
+ ]);
333
+ // packages/runtime/src/control-plane/native/git-native.ts
334
+ import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
335
+ import { tmpdir as tmpdir3 } from "os";
336
+ import { dirname as dirname4, isAbsolute, resolve as resolve5 } from "path";
337
+ import { createHash } from "crypto";
338
+ var sharedGitNativeOutputDir = resolve5(tmpdir3(), "rig-native");
339
+ var sharedGitNativeOutputPath = resolve5(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
340
+ var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
341
+ function temporaryGitBinaryOutputPath(outputPath) {
342
+ const suffix = process.platform === "win32" ? ".exe" : "";
343
+ return resolve5(dirname4(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
344
+ }
345
+ function publishGitBinary(tempOutputPath, outputPath) {
346
+ try {
347
+ renameSync(tempOutputPath, outputPath);
348
+ } catch (error) {
349
+ if (process.platform === "win32" && existsSync3(outputPath)) {
350
+ rmSync(outputPath, { force: true });
351
+ renameSync(tempOutputPath, outputPath);
352
+ return;
353
+ }
354
+ throw error;
355
+ }
356
+ }
357
+ function runtimeRigGitFileName() {
358
+ return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
359
+ }
360
+ function rigGitSourceCandidates() {
361
+ const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
362
+ const cwd = process.cwd()?.trim() || "";
363
+ const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
364
+ const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
365
+ const moduleRelativeSource = resolve5(import.meta.dir, "../../../native/rig-git.zig");
366
+ return [...new Set([
367
+ process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
368
+ moduleRelativeSource,
369
+ projectRoot ? resolve5(projectRoot, "packages/runtime/native/rig-git.zig") : "",
370
+ hostProjectRoot ? resolve5(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
371
+ cwd ? resolve5(cwd, "packages/runtime/native/rig-git.zig") : "",
372
+ execDir ? resolve5(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
373
+ execDir ? resolve5(execDir, "..", "native", "rig-git.zig") : ""
374
+ ].filter(Boolean))];
375
+ }
376
+ function nativePackageBinaryCandidates(fromDir, fileName) {
377
+ const candidates = [];
378
+ let cursor = resolve5(fromDir);
379
+ for (let index = 0;index < 8; index += 1) {
380
+ candidates.push(resolve5(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve5(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve5(cursor, "native", fileName), resolve5(cursor, "native", "bin", fileName));
381
+ const parent = dirname4(cursor);
382
+ if (parent === cursor)
383
+ break;
384
+ cursor = parent;
385
+ }
386
+ return candidates;
387
+ }
388
+ function rigGitBinaryCandidates() {
389
+ const execDir = process.execPath?.trim() ? dirname4(process.execPath.trim()) : "";
390
+ const fileName = runtimeRigGitFileName();
391
+ const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
392
+ return [...new Set([
393
+ explicit,
394
+ ...nativePackageBinaryCandidates(import.meta.dir, fileName),
395
+ execDir ? resolve5(execDir, fileName) : "",
396
+ execDir ? resolve5(execDir, "..", fileName) : "",
397
+ execDir ? resolve5(execDir, "..", "bin", fileName) : "",
398
+ sharedGitNativeOutputPath
399
+ ].filter(Boolean))];
400
+ }
401
+ function resolveGitSourcePath() {
402
+ for (const candidate of rigGitSourceCandidates()) {
403
+ if (candidate && existsSync3(candidate)) {
404
+ return candidate;
405
+ }
406
+ }
407
+ return null;
408
+ }
409
+ function resolveGitBinaryPath() {
410
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
411
+ return null;
412
+ }
413
+ for (const candidate of rigGitBinaryCandidates()) {
414
+ if (candidate && existsSync3(candidate)) {
415
+ return candidate;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ function preferredGitBinaryOutputPath() {
421
+ const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
422
+ return explicit || sharedGitNativeOutputPath;
423
+ }
424
+ function binarySupportsTrackerCommandsSync(binaryPath) {
425
+ try {
426
+ const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
427
+ stdout: "pipe",
428
+ stderr: "pipe"
429
+ });
430
+ const stdout = probe.stdout.toString().trim();
431
+ const stderr = probe.stderr.toString().trim();
432
+ if (stdout.includes('"error":"unknown command"')) {
433
+ return false;
434
+ }
435
+ return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
436
+ } catch {
437
+ return false;
438
+ }
439
+ }
440
+ function nativeBuildManifestPath(outputPath) {
441
+ return `${outputPath}.build-manifest.json`;
442
+ }
443
+ function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
444
+ if (!existsSync3(manifestPath)) {
445
+ return false;
446
+ }
447
+ try {
448
+ const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
449
+ return manifest.version === 1 && manifest.buildKey === buildKey;
450
+ } catch {
451
+ return false;
452
+ }
453
+ }
454
+ function sha256FileSync(path) {
455
+ return createHash("sha256").update(readFileSync3(path)).digest("hex");
456
+ }
457
+ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
458
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
459
+ throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
460
+ }
461
+ const sourcePath = resolveGitSourcePath();
462
+ if (!sourcePath) {
463
+ const binaryPath = resolveGitBinaryPath();
464
+ if (binaryPath) {
465
+ return binaryPath;
466
+ }
467
+ throw new Error("rig-git.zig source file not found.");
468
+ }
469
+ const zigBinary = Bun.which("zig");
470
+ if (!zigBinary) {
471
+ throw new Error("zig is required to build native Rig git tools.");
472
+ }
473
+ mkdirSync2(dirname4(outputPath), { recursive: true });
474
+ const sourceDigest = sha256FileSync(sourcePath);
475
+ const buildKey = JSON.stringify({
476
+ version: 1,
477
+ zigBinary,
478
+ platform: process.platform,
479
+ arch: process.arch,
480
+ sourcePath,
481
+ sourceDigest
482
+ });
483
+ const manifestPath = nativeBuildManifestPath(outputPath);
484
+ const needsBuild = !existsSync3(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
485
+ if (!needsBuild) {
486
+ chmodSync(outputPath, 493);
487
+ return outputPath;
488
+ }
489
+ const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
490
+ const build = Bun.spawnSync([
491
+ zigBinary,
492
+ "build-exe",
493
+ sourcePath,
494
+ "-O",
495
+ "ReleaseFast",
496
+ `-femit-bin=${tempOutputPath}`
497
+ ], {
498
+ cwd: dirname4(sourcePath),
499
+ stdout: "pipe",
500
+ stderr: "pipe"
501
+ });
502
+ if (build.exitCode !== 0 || !existsSync3(tempOutputPath)) {
503
+ const stderr = build.stderr.toString().trim();
504
+ const stdout = build.stdout.toString().trim();
505
+ const details = [stderr, stdout].filter(Boolean).join(`
506
+ `);
507
+ throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
508
+ }
509
+ chmodSync(tempOutputPath, 493);
510
+ if (existsSync3(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
511
+ rmSync(tempOutputPath, { force: true });
512
+ chmodSync(outputPath, 493);
513
+ return outputPath;
514
+ }
515
+ publishGitBinary(tempOutputPath, outputPath);
516
+ if (!binarySupportsTrackerCommandsSync(outputPath)) {
517
+ rmSync(outputPath, { force: true });
518
+ throw new Error("Failed to build native Rig git tools: tracker command probe failed");
519
+ }
520
+ writeFileSync2(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
521
+ `, "utf8");
522
+ return outputPath;
523
+ }
524
+ function runGitNative(command, args) {
525
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
526
+ return { ok: false, error: "rig-git native disabled" };
527
+ }
528
+ const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
529
+ let binaryPath = null;
530
+ if (trackerCommand) {
531
+ try {
532
+ binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
533
+ } catch (error) {
534
+ const message = error instanceof Error ? error.message : String(error);
535
+ if (message.includes("rig-git.zig source file not found")) {
536
+ return { ok: false, error: "rig-git binary not found" };
537
+ }
538
+ return { ok: false, error: message };
539
+ }
540
+ } else {
541
+ const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
542
+ binaryPath = explicitBinaryPath && existsSync3(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
543
+ if (!binaryPath) {
544
+ try {
545
+ binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
546
+ } catch (error) {
547
+ const message = error instanceof Error ? error.message : String(error);
548
+ if (message.includes("rig-git.zig source file not found")) {
549
+ return { ok: false, error: "rig-git binary not found" };
550
+ }
551
+ return { ok: false, error: message };
552
+ }
553
+ }
554
+ }
555
+ try {
556
+ const proc = Bun.spawnSync([binaryPath, command, ...args], {
557
+ stdout: "pipe",
558
+ stderr: "pipe",
559
+ env: process.env
560
+ });
561
+ if (proc.exitCode !== 0) {
562
+ const stdoutText = proc.stdout.toString().trim();
563
+ if (stdoutText) {
564
+ try {
565
+ const parsed = JSON.parse(stdoutText);
566
+ if (!parsed.ok) {
567
+ return parsed;
568
+ }
569
+ } catch {}
570
+ }
571
+ const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
572
+ return { ok: false, error: errText };
573
+ }
574
+ const output = proc.stdout.toString().trim();
575
+ return JSON.parse(output);
576
+ } catch (err) {
577
+ return { ok: false, error: String(err) };
578
+ }
579
+ }
580
+ function nativeBranchName(repoPath) {
581
+ const result = runGitNative("branch-name", [repoPath]);
582
+ if (!result.ok)
583
+ return null;
584
+ if ("value" in result && typeof result.value === "string")
585
+ return result.value;
586
+ return null;
587
+ }
588
+ function nativeHeadOid(repoPath) {
589
+ const result = runGitNative("head-oid", [repoPath]);
590
+ if (!result.ok)
591
+ return null;
592
+ if ("value" in result && typeof result.value === "string")
593
+ return result.value;
594
+ return null;
595
+ }
596
+ function nativeChangeCount(repoPath) {
597
+ const result = runGitNative("change-count", [repoPath]);
598
+ if (!result.ok)
599
+ return null;
600
+ if ("count" in result && typeof result.count === "number")
601
+ return result.count;
602
+ return null;
603
+ }
604
+ function nativePendingFiles(repoPath) {
605
+ const result = runGitNative("pending-files", [repoPath]);
606
+ if (!result.ok)
607
+ return null;
608
+ if ("files" in result && Array.isArray(result.files)) {
609
+ return result.files.map((f) => ({ path: f.path, status: f.status }));
610
+ }
611
+ return null;
612
+ }
613
+ function nativeFileHasChanges(repoPath, filePath) {
614
+ const result = runGitNative("file-has-changes", [repoPath, filePath]);
615
+ if (!result.ok)
616
+ return null;
617
+ if ("has_changes" in result && typeof result.has_changes === "boolean")
618
+ return result.has_changes;
619
+ return null;
620
+ }
621
+
622
+ // packages/runtime/src/control-plane/native/utils.ts
623
+ import { ptr as ptr2 } from "bun:ffi";
624
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
625
+ import { resolve as resolve8 } from "path";
626
+
627
+ // packages/runtime/src/layout.ts
628
+ import { existsSync as existsSync4 } from "fs";
629
+ import { basename as basename3, dirname as dirname5, resolve as resolve6 } from "path";
630
+ var RIG_DEFINITION_DIRNAME = "rig";
631
+ var RIG_ARTIFACTS_DIRNAME = "artifacts";
632
+ function resolveMonorepoRoot(projectRoot) {
633
+ const normalizedProjectRoot = resolve6(projectRoot);
634
+ const explicit = process.env.MONOREPO_ROOT?.trim();
635
+ if (explicit) {
636
+ const explicitRoot = resolve6(explicit);
637
+ const explicitParent = dirname5(explicitRoot);
638
+ if (basename3(explicitParent) === ".worktrees") {
639
+ const owner = dirname5(explicitParent);
640
+ const ownerHasGit = existsSync4(resolve6(owner, ".git"));
641
+ const ownerHasTaskConfig = existsSync4(resolve6(owner, ".rig", "task-config.json"));
642
+ const ownerHasRigConfig = existsSync4(resolve6(owner, "rig.config.ts"));
643
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
644
+ return owner;
645
+ }
646
+ throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
647
+ }
648
+ if (!existsSync4(resolve6(explicitRoot, ".git"))) {
649
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
650
+ }
651
+ const hasTaskConfig = existsSync4(resolve6(explicitRoot, ".rig", "task-config.json"));
652
+ const hasRigConfig = existsSync4(resolve6(explicitRoot, "rig.config.ts"));
653
+ if (!hasTaskConfig && !hasRigConfig) {
654
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
655
+ }
656
+ return explicitRoot;
657
+ }
658
+ const projectParent = dirname5(normalizedProjectRoot);
659
+ if (basename3(projectParent) === ".worktrees") {
660
+ const worktreeOwner = dirname5(projectParent);
661
+ const ownerHasGit = existsSync4(resolve6(worktreeOwner, ".git"));
662
+ const ownerHasTaskConfig = existsSync4(resolve6(worktreeOwner, ".rig", "task-config.json"));
663
+ const ownerHasRigConfig = existsSync4(resolve6(worktreeOwner, "rig.config.ts"));
664
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
665
+ return worktreeOwner;
666
+ }
667
+ }
668
+ return normalizedProjectRoot;
669
+ }
670
+ function resolveRuntimeWorkspaceLayout(workspaceDir) {
671
+ const root = resolve6(workspaceDir);
672
+ const rigRoot = resolve6(root, ".rig");
673
+ const logsDir = resolve6(rigRoot, "logs");
674
+ const stateDir = resolve6(rigRoot, "state");
675
+ const runtimeDir = resolve6(rigRoot, "runtime");
676
+ const binDir = resolve6(rigRoot, "bin");
677
+ return {
678
+ workspaceDir: root,
679
+ rigRoot,
680
+ stateDir,
681
+ logsDir,
682
+ artifactsRoot: resolve6(root, RIG_ARTIFACTS_DIRNAME),
683
+ runtimeDir,
684
+ homeDir: resolve6(rigRoot, "home"),
685
+ tmpDir: resolve6(rigRoot, "tmp"),
686
+ cacheDir: resolve6(rigRoot, "cache"),
687
+ sessionDir: resolve6(rigRoot, "session"),
688
+ binDir,
689
+ distDir: resolve6(rigRoot, "dist"),
690
+ pluginBinDir: resolve6(binDir, "plugins"),
691
+ contextPath: resolve6(rigRoot, "runtime-context.json"),
692
+ controlPlaneEventsFile: resolve6(logsDir, "control-plane.events.jsonl")
693
+ };
694
+ }
695
+ function resolveActiveRuntimeWorkspaceRoot(monorepoRoot) {
696
+ const explicit = process.env.RIG_TASK_WORKSPACE?.trim();
697
+ if (!explicit) {
698
+ throw new Error("No active runtime workspace. Set RIG_TASK_WORKSPACE or provision a task runtime first.");
699
+ }
700
+ return resolve6(explicit);
701
+ }
702
+ function resolveRigLayout(projectRoot) {
703
+ const monorepoRoot = resolveMonorepoRoot(projectRoot);
704
+ const definitionRoot = resolve6(projectRoot, RIG_DEFINITION_DIRNAME);
705
+ const runtimeWorkspaceRoot = resolveActiveRuntimeWorkspaceRoot(monorepoRoot);
706
+ const runtimeLayout = resolveRuntimeWorkspaceLayout(runtimeWorkspaceRoot);
707
+ const policyDir = resolve6(definitionRoot, "policy");
708
+ return {
709
+ projectRoot,
710
+ monorepoRoot,
711
+ definitionRoot,
712
+ runtimeWorkspaceRoot,
713
+ stateRoot: runtimeLayout.rigRoot,
714
+ artifactsRoot: runtimeLayout.artifactsRoot,
715
+ configPath: resolve6(definitionRoot, "config.sh"),
716
+ taskConfigPath: resolve6(runtimeWorkspaceRoot, ".rig", "task-config.json"),
717
+ policyDir,
718
+ policyFile: resolve6(policyDir, "policy.json"),
719
+ pluginsDir: resolve6(definitionRoot, "plugins"),
720
+ hooksDir: resolve6(definitionRoot, "hooks"),
721
+ toolsDir: resolve6(definitionRoot, "tools"),
722
+ templatesDir: resolve6(definitionRoot, "templates"),
723
+ validationDir: resolve6(definitionRoot, "validation"),
724
+ stateDir: runtimeLayout.stateDir,
725
+ logsDir: runtimeLayout.logsDir,
726
+ notificationsDir: resolve6(definitionRoot, "notifications"),
727
+ runtimeDir: runtimeLayout.runtimeDir,
728
+ distDir: runtimeLayout.distDir,
729
+ binDir: runtimeLayout.binDir,
730
+ pluginBinDir: runtimeLayout.pluginBinDir,
731
+ keybindingsPath: resolve6(definitionRoot, "keybindings.json"),
732
+ controlPlaneEventsFile: runtimeLayout.controlPlaneEventsFile
733
+ };
734
+ }
735
+
736
+ // packages/runtime/src/control-plane/native/runtime-native.ts
737
+ import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
738
+ import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
739
+ import { tmpdir as tmpdir4 } from "os";
740
+ import { dirname as dirname6, resolve as resolve7 } from "path";
741
+ var sharedNativeRuntimeOutputDir = resolve7(tmpdir4(), "rig-native");
742
+ var sharedNativeRuntimeOutputPath = resolve7(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
743
+ var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
744
+ var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
745
+ function requireNativeRuntimeLibrary(feature) {
746
+ if (!nativeRuntimeLibrary) {
747
+ throw new Error(`Native Zig runtime is required for ${feature}`);
748
+ }
749
+ return nativeRuntimeLibrary;
750
+ }
751
+ async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
752
+ if (await buildNativeRuntimeLibrary(outputPath, options)) {
753
+ return outputPath;
754
+ }
755
+ return !options.force && existsSync5(outputPath) ? outputPath : null;
756
+ }
757
+ async function loadNativeRuntimeLibrary() {
758
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
759
+ return null;
760
+ }
761
+ for (const candidate of nativeRuntimeLibraryCandidates()) {
762
+ if (!candidate || !existsSync5(candidate)) {
763
+ continue;
764
+ }
765
+ const loaded = tryDlopenNativeRuntimeLibrary(candidate);
766
+ if (loaded) {
767
+ return loaded;
768
+ }
769
+ }
770
+ const builtLibraryPath = await ensureNativeRuntimeLibraryPath(sharedNativeRuntimeOutputPath, { force: true });
771
+ if (!builtLibraryPath) {
772
+ return null;
773
+ }
774
+ return tryDlopenNativeRuntimeLibrary(builtLibraryPath);
775
+ }
776
+ function nativePackageLibraryCandidates(fromDir, names) {
777
+ const candidates = [];
778
+ let cursor = resolve7(fromDir);
779
+ for (let index = 0;index < 8; index += 1) {
780
+ for (const name of names) {
781
+ candidates.push(resolve7(cursor, "native", `${process.platform}-${process.arch}`, name), resolve7(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve7(cursor, "native", name), resolve7(cursor, "native", "lib", name));
782
+ }
783
+ const parent = dirname6(cursor);
784
+ if (parent === cursor)
785
+ break;
786
+ cursor = parent;
787
+ }
788
+ return candidates;
789
+ }
790
+ function nativeRuntimeLibraryCandidates() {
791
+ const explicit = process.env.RIG_NATIVE_RUNTIME_LIB?.trim() || "";
792
+ const execDir = process.execPath?.trim() ? dirname6(process.execPath.trim()) : "";
793
+ const platformSpecific = `runtime-native-${process.platform}-${process.arch}.${suffix}`;
794
+ return [...new Set([
795
+ explicit,
796
+ ...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
797
+ execDir ? resolve7(execDir, colocatedNativeRuntimeFileName) : "",
798
+ execDir ? resolve7(execDir, platformSpecific) : "",
799
+ execDir ? resolve7(execDir, "..", colocatedNativeRuntimeFileName) : "",
800
+ execDir ? resolve7(execDir, "..", platformSpecific) : "",
801
+ execDir ? resolve7(execDir, "lib", colocatedNativeRuntimeFileName) : "",
802
+ execDir ? resolve7(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
803
+ sharedNativeRuntimeOutputPath
804
+ ].filter(Boolean))];
805
+ }
806
+ function resolveNativeRuntimeSourcePath() {
807
+ const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
808
+ if (explicit && existsSync5(explicit)) {
809
+ return explicit;
810
+ }
811
+ const bundled = resolve7(import.meta.dir, "../../../native/snapshot.zig");
812
+ return existsSync5(bundled) ? bundled : null;
813
+ }
814
+ async function buildNativeRuntimeLibrary(outputPath, options = {}) {
815
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
816
+ return false;
817
+ }
818
+ const zigBinary = Bun.which("zig");
819
+ const sourcePath = resolveNativeRuntimeSourcePath();
820
+ if (!zigBinary || !sourcePath) {
821
+ return false;
822
+ }
823
+ const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
824
+ try {
825
+ mkdirSync3(dirname6(outputPath), { recursive: true });
826
+ const needsBuild = options.force === true || !existsSync5(outputPath) || statSync(sourcePath).mtimeMs > statSync(outputPath).mtimeMs;
827
+ if (!needsBuild) {
828
+ return true;
829
+ }
830
+ const build = Bun.spawn([
831
+ zigBinary,
832
+ "build-lib",
833
+ sourcePath,
834
+ "-dynamic",
835
+ "-O",
836
+ "ReleaseFast",
837
+ `-femit-bin=${tempOutputPath}`
838
+ ], {
839
+ cwd: import.meta.dir,
840
+ stdout: "pipe",
841
+ stderr: "pipe"
842
+ });
843
+ const exitCode = await build.exited;
844
+ if (exitCode !== 0 || !existsSync5(tempOutputPath)) {
845
+ rmSync2(tempOutputPath, { force: true });
846
+ return false;
847
+ }
848
+ renameSync2(tempOutputPath, outputPath);
849
+ return true;
850
+ } catch {
851
+ rmSync2(tempOutputPath, { force: true });
852
+ return false;
853
+ }
854
+ }
855
+ function tryDlopenNativeRuntimeLibrary(outputPath) {
856
+ try {
857
+ return dlopen(outputPath, {
858
+ rig_scope_match: {
859
+ args: ["ptr", "ptr"],
860
+ returns: "u8"
861
+ },
862
+ snapshot_capture: {
863
+ args: ["ptr", "u64", "ptr", "u64"],
864
+ returns: "ptr"
865
+ },
866
+ snapshot_delta: {
867
+ args: ["ptr", "ptr"],
868
+ returns: "ptr"
869
+ },
870
+ snapshot_store_delta: {
871
+ args: ["ptr", "ptr", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
872
+ returns: "ptr"
873
+ },
874
+ snapshot_inspect_delta: {
875
+ args: ["ptr", "u64"],
876
+ returns: "ptr"
877
+ },
878
+ snapshot_apply_delta: {
879
+ args: ["ptr", "u64", "ptr", "u64"],
880
+ returns: "ptr"
881
+ },
882
+ snapshot_release: {
883
+ args: ["ptr"],
884
+ returns: "void"
885
+ },
886
+ runtime_hash_file: {
887
+ args: ["ptr", "u64"],
888
+ returns: "ptr"
889
+ },
890
+ runtime_hash_tree: {
891
+ args: ["ptr", "u64"],
892
+ returns: "ptr"
893
+ },
894
+ runtime_prepare_paths: {
895
+ args: ["ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
896
+ returns: "ptr"
897
+ },
898
+ runtime_link_dependency_layer: {
899
+ args: ["ptr", "u64", "ptr", "u64"],
900
+ returns: "ptr"
901
+ },
902
+ runtime_scan_worktrees: {
903
+ args: ["ptr", "u64"],
904
+ returns: "ptr"
905
+ }
906
+ });
907
+ } catch {
908
+ return null;
909
+ }
910
+ }
911
+
912
+ // packages/runtime/src/control-plane/native/utils.ts
913
+ function resolveMonorepoRoot2(projectRoot) {
914
+ return resolveMonorepoRoot(projectRoot);
915
+ }
916
+ var nativeScopeMatcher = null;
917
+ var scopeRegexCache = new Map;
918
+ function runCapture(command, cwd, env) {
919
+ const result = Bun.spawnSync(command, {
920
+ cwd,
921
+ env: env ? { ...process.env, ...env } : process.env,
922
+ stdout: "pipe",
923
+ stderr: "pipe"
924
+ });
925
+ return {
926
+ exitCode: result.exitCode,
927
+ stdout: result.stdout.toString(),
928
+ stderr: result.stderr.toString()
929
+ };
930
+ }
931
+ function readJsonFile(path, fallback) {
932
+ if (!existsSync6(path)) {
933
+ return fallback;
934
+ }
935
+ try {
936
+ return JSON.parse(readFileSync4(path, "utf-8"));
937
+ } catch {
938
+ return fallback;
939
+ }
940
+ }
941
+ function nowIso() {
942
+ return new Date().toISOString();
943
+ }
944
+ function unique(values) {
945
+ return [...new Set(values)];
946
+ }
947
+ function resolveHarnessPaths(projectRoot) {
948
+ const hasRuntimeWorkspace = Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
949
+ const monorepoRoot = resolveMonorepoRoot2(projectRoot);
950
+ const harnessRoot = resolve8(projectRoot, "rig");
951
+ const stateRoot = resolve8(projectRoot, ".rig");
952
+ const layout = hasRuntimeWorkspace ? resolveRigLayout(projectRoot) : null;
953
+ const stateDir = layout?.stateDir ?? resolve8(stateRoot, "state");
954
+ const logsDir = layout?.logsDir ?? resolve8(stateRoot, "logs");
955
+ const artifactsDir = layout?.artifactsRoot ?? resolve8(monorepoRoot, "artifacts");
956
+ const taskConfigPath = layout?.taskConfigPath ?? resolve8(monorepoRoot, ".rig", "task-config.json");
957
+ const binDir = layout?.binDir ?? resolve8(stateRoot, "bin");
958
+ return {
959
+ harnessRoot,
960
+ stateDir: process.env.RIG_STATE_DIR || stateDir,
961
+ artifactsDir,
962
+ logsDir: process.env.RIG_LOGS_DIR || logsDir,
963
+ binDir,
964
+ hooksDir: resolve8(harnessRoot, "hooks"),
965
+ validationDir: resolve8(harnessRoot, "validation"),
966
+ taskConfigPath,
967
+ sessionPath: process.env.RIG_SESSION_FILE || resolve8(stateRoot, "session", "session.json"),
968
+ monorepoRoot,
969
+ tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve8(monorepoRoot, "TSAPITests"),
970
+ taskRepoCommitsPath: resolve8(stateDir, "task-repo-commits.json"),
971
+ baseRepoPinsPath: resolve8(stateDir, "base-repo-pins.json"),
972
+ failedApproachesPath: resolve8(stateDir, "failed_approaches.md"),
973
+ agentProfilePath: resolve8(stateDir, "agent-profile.json"),
974
+ reviewProfilePath: resolve8(stateDir, "review-profile.json")
975
+ };
976
+ }
977
+ function normalizeRelativeScopePath(inputPath) {
978
+ let normalized = inputPath.replace(/^\.\//, "");
979
+ const rules = getScopeRules();
980
+ if (rules?.stripPrefixes) {
981
+ for (const prefix of rules.stripPrefixes) {
982
+ if (normalized.startsWith(prefix)) {
983
+ normalized = normalized.slice(prefix.length);
984
+ }
985
+ }
986
+ }
987
+ return normalized;
988
+ }
989
+ function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
990
+ let normalized = inputPath.replace(/^\.\//, "");
991
+ if (normalized.startsWith(projectRoot + "/")) {
992
+ normalized = normalized.slice(projectRoot.length + 1);
993
+ }
994
+ if (normalized.startsWith(monorepoRoot + "/")) {
995
+ normalized = normalized.slice(monorepoRoot.length + 1);
996
+ }
997
+ return normalizeRelativeScopePath(normalized);
998
+ }
999
+ function scopeMatches(path, scopes) {
1000
+ const matcher = getNativeScopeMatcher();
1001
+ const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
1002
+ for (const scope of scopes) {
1003
+ const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
1004
+ for (const candidatePath of pathVariants) {
1005
+ for (const candidateScope of scopeVariants) {
1006
+ if (candidatePath === candidateScope) {
1007
+ return true;
1008
+ }
1009
+ if (matcher.match(candidateScope, candidatePath)) {
1010
+ return true;
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+ return false;
1016
+ }
1017
+ function getNativeScopeMatcher() {
1018
+ if (nativeScopeMatcher) {
1019
+ return nativeScopeMatcher;
1020
+ }
1021
+ nativeScopeMatcher = createNativeScopeMatcher();
1022
+ return nativeScopeMatcher;
1023
+ }
1024
+ function createNativeScopeMatcher() {
1025
+ const library = requireNativeRuntimeLibrary("scope matching");
1026
+ return {
1027
+ match: (pattern, path) => {
1028
+ const patternBuffer = Buffer.from(`${pattern}\x00`);
1029
+ const pathBuffer = Buffer.from(`${path}\x00`);
1030
+ return library.symbols.rig_scope_match(Number(ptr2(patternBuffer)), Number(ptr2(pathBuffer))) !== 0;
1031
+ }
1032
+ };
1033
+ }
1034
+ // packages/runtime/src/control-plane/state-sync/reconcile.ts
1035
+ var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
1036
+ // packages/runtime/src/control-plane/native/task-state.ts
1037
+ function readTaskConfig(projectRoot) {
1038
+ const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
1039
+ return stripTaskConfigMetadata(raw);
1040
+ }
1041
+ function readSourceTaskConfig(projectRoot) {
1042
+ const raw = readAndSyncSourceTaskConfig(projectRoot);
1043
+ return stripTaskConfigMetadata(raw);
1044
+ }
1045
+ function currentTaskId(projectRoot) {
1046
+ const fromEnv = (process.env.RIG_TASK_ID || "").trim();
1047
+ if (fromEnv) {
1048
+ return fromEnv;
1049
+ }
1050
+ const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
1051
+ if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
1052
+ return runtimeId.slice("task-".length);
1053
+ }
1054
+ const workspace = (process.env.RIG_TASK_WORKSPACE || "").trim();
1055
+ const inferredFromWorkspace = inferTaskIdFromRuntimePath(workspace);
1056
+ if (inferredFromWorkspace) {
1057
+ return inferredFromWorkspace;
1058
+ }
1059
+ const inferredFromCwd = inferTaskIdFromRuntimePath(process.cwd());
1060
+ if (inferredFromCwd) {
1061
+ return inferredFromCwd;
1062
+ }
1063
+ try {
1064
+ const paths = resolveHarnessPaths(projectRoot);
1065
+ const session = readJsonFile(paths.sessionPath, {});
1066
+ return session.activeTaskIds?.[0] || "";
1067
+ } catch {
1068
+ return "";
1069
+ }
1070
+ }
1071
+ function stripTaskConfigMetadata(raw) {
1072
+ const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
1073
+ return tasks;
1074
+ }
1075
+ function coerceValidationDescriptions(candidate) {
1076
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
1077
+ return {};
1078
+ }
1079
+ const descriptions = {};
1080
+ for (const [key, value] of Object.entries(candidate)) {
1081
+ if (typeof value === "string" && value.length > 0) {
1082
+ descriptions[key] = value;
1083
+ }
1084
+ }
1085
+ return descriptions;
1086
+ }
1087
+ function readValidationDescriptionsFromMeta(meta) {
1088
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
1089
+ return;
1090
+ }
1091
+ return meta.validation_descriptions;
1092
+ }
1093
+ function inferTaskIdFromRuntimePath(path) {
1094
+ if (!path) {
1095
+ return "";
1096
+ }
1097
+ const match = path.match(/\/\.rig\/runtime\/agents\/task-([^/]+)\/worktree(?:\/|$)/) || path.match(/\/\.worktrees\/([^/]+)(?:\/|$)/);
1098
+ const candidate = match?.[1] || "";
1099
+ return candidate.startsWith("bd-") ? candidate : "";
1100
+ }
1101
+ function lookupTask(projectRoot, input) {
1102
+ if (!input) {
1103
+ return "";
1104
+ }
1105
+ if (!input.startsWith("bd-")) {
1106
+ return input;
1107
+ }
1108
+ try {
1109
+ const taskConfig2 = readTaskConfig(projectRoot);
1110
+ if (taskConfig2[input]) {
1111
+ return input;
1112
+ }
1113
+ } catch {}
1114
+ const taskConfig = readSourceTaskConfig(projectRoot);
1115
+ return taskConfig[input] ? input : "";
1116
+ }
1117
+ function artifactDirForId(projectRoot, id) {
1118
+ const workspaceDir = process.env.RIG_TASK_WORKSPACE?.trim();
1119
+ if (workspaceDir) {
1120
+ const worktreeArtifacts = resolve9(workspaceDir, "artifacts", id);
1121
+ if (existsSync7(worktreeArtifacts) || existsSync7(resolve9(workspaceDir, "artifacts"))) {
1122
+ return worktreeArtifacts;
1123
+ }
1124
+ }
1125
+ try {
1126
+ const paths = resolveHarnessPaths(projectRoot);
1127
+ return resolve9(paths.artifactsDir, id);
1128
+ } catch {
1129
+ return resolve9(resolveMonorepoRoot2(projectRoot), "artifacts", id);
1130
+ }
1131
+ }
1132
+ function resolveTaskConfigPath(projectRoot) {
1133
+ const paths = resolveHarnessPaths(projectRoot);
1134
+ if (existsSync7(paths.taskConfigPath)) {
1135
+ return paths.taskConfigPath;
1136
+ }
1137
+ for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
1138
+ if (existsSync7(candidate)) {
1139
+ return candidate;
1140
+ }
1141
+ }
1142
+ throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
1143
+ }
1144
+ function findSourceTaskConfigPath(projectRoot) {
1145
+ for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
1146
+ if (existsSync7(candidate)) {
1147
+ return candidate;
1148
+ }
1149
+ }
1150
+ return null;
1151
+ }
1152
+ var FILE_TASK_PATTERN = /\.(task\.)?json$/;
1153
+ function readAndSyncSourceTaskConfig(projectRoot) {
1154
+ const sourcePath = findSourceTaskConfigPath(projectRoot);
1155
+ const raw = sourcePath ? readJsonFile(sourcePath, {}) : readConfiguredFileTaskConfig(projectRoot);
1156
+ const synced = synchronizeTaskConfigWithTracker(projectRoot, raw);
1157
+ if (sourcePath && synced.updated) {
1158
+ try {
1159
+ writeFileSync3(sourcePath, `${JSON.stringify(synced.config, null, 2)}
1160
+ `, "utf-8");
1161
+ } catch {}
1162
+ }
1163
+ return synced.config;
1164
+ }
1165
+ function synchronizeTaskConfigWithTracker(projectRoot, rawConfig) {
1166
+ const issues = readSourceIssueRecords(projectRoot);
1167
+ if (issues.length === 0) {
1168
+ return { config: rawConfig, updated: false };
1169
+ }
1170
+ const taskConfig = stripTaskConfigMetadata(rawConfig);
1171
+ const mergedConfig = { ...taskConfig };
1172
+ const validationDescriptions = coerceValidationDescriptions(rawConfig.validation_descriptions);
1173
+ const metaValidationDescriptions = coerceValidationDescriptions(readValidationDescriptionsFromMeta(rawConfig._meta));
1174
+ let updated = false;
1175
+ for (const issue of issues) {
1176
+ if (issue.issueType !== "task") {
1177
+ continue;
1178
+ }
1179
+ if (mergedConfig[issue.id] && !shouldRefreshAutoSyncedTaskConfigEntry(mergedConfig[issue.id])) {
1180
+ continue;
1181
+ }
1182
+ mergedConfig[issue.id] = buildAutoSyncedTaskConfigEntry(issue);
1183
+ updated = true;
1184
+ }
1185
+ return {
1186
+ config: {
1187
+ ...mergedConfig,
1188
+ ...Object.keys(validationDescriptions).length > 0 ? { validation_descriptions: validationDescriptions } : {},
1189
+ ...Object.keys(metaValidationDescriptions).length > 0 ? { _meta: { validation_descriptions: metaValidationDescriptions } } : {}
1190
+ },
1191
+ updated
1192
+ };
1193
+ }
1194
+ function shouldRefreshAutoSyncedTaskConfigEntry(entry) {
1195
+ if (!entry || typeof entry !== "object") {
1196
+ return false;
1197
+ }
1198
+ const candidate = entry;
1199
+ if (!candidate.auto_synced) {
1200
+ return false;
1201
+ }
1202
+ if (!Array.isArray(candidate.scope) || candidate.scope.length === 0) {
1203
+ return true;
1204
+ }
1205
+ if (candidate.scope.some((glob) => typeof glob !== "string" || glob.trim().length === 0)) {
1206
+ return true;
1207
+ }
1208
+ return !candidate.role;
1209
+ }
1210
+ function readSourceIssueRecords(projectRoot) {
1211
+ const issuesPath = resolve9(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
1212
+ if (!existsSync7(issuesPath)) {
1213
+ return [];
1214
+ }
1215
+ const records = [];
1216
+ for (const line of readFileSync5(issuesPath, "utf-8").split(/\r?\n/)) {
1217
+ const trimmed = line.trim();
1218
+ if (!trimmed) {
1219
+ continue;
1220
+ }
1221
+ try {
1222
+ const parsed = JSON.parse(trimmed);
1223
+ const id = typeof parsed.id === "string" ? parsed.id.trim() : "";
1224
+ if (!id) {
1225
+ continue;
1226
+ }
1227
+ records.push({
1228
+ id,
1229
+ title: typeof parsed.title === "string" ? parsed.title.trim() : "",
1230
+ issueType: typeof parsed.issue_type === "string" ? parsed.issue_type.trim() : null,
1231
+ labels: Array.isArray(parsed.labels) ? parsed.labels.filter((label) => typeof label === "string") : []
1232
+ });
1233
+ } catch {}
1234
+ }
1235
+ return records;
1236
+ }
1237
+ function buildAutoSyncedTaskConfigEntry(issue) {
1238
+ return {
1239
+ auto_synced: true,
1240
+ role: inferAutoSyncedTaskRole(issue),
1241
+ scope: inferAutoSyncedTaskScope(issue),
1242
+ validation: []
1243
+ };
1244
+ }
1245
+ function inferAutoSyncedTaskRole(issue) {
1246
+ for (const label of issue.labels) {
1247
+ if (label === "role:architect")
1248
+ return "architect";
1249
+ if (label === "role:extractor")
1250
+ return "extractor";
1251
+ if (label === "role:mechanic")
1252
+ return "mechanic";
1253
+ if (label === "role:verifier")
1254
+ return "verifier";
1255
+ }
1256
+ if (/\bDESIGN\b/i.test(issue.title)) {
1257
+ return "architect";
1258
+ }
1259
+ if (/\bInitialize\b/i.test(issue.title)) {
1260
+ return "mechanic";
1261
+ }
1262
+ return "extractor";
1263
+ }
1264
+ function inferAutoSyncedTaskScope(issue) {
1265
+ return [`artifacts/${issue.id}/**`];
1266
+ }
1267
+ function readConfiguredFileTaskConfig(projectRoot) {
1268
+ const sourcePath = readConfiguredFilesTaskSourcePath(projectRoot);
1269
+ if (!sourcePath) {
1270
+ return {};
1271
+ }
1272
+ const directory = resolve9(projectRoot, sourcePath);
1273
+ if (!existsSync7(directory)) {
1274
+ return {};
1275
+ }
1276
+ const config = {};
1277
+ for (const name of readdirSync(directory)) {
1278
+ if (!FILE_TASK_PATTERN.test(name))
1279
+ continue;
1280
+ const file = resolve9(directory, name);
1281
+ try {
1282
+ if (!statSync2(file).isFile())
1283
+ continue;
1284
+ const raw = JSON.parse(readFileSync5(file, "utf8"));
1285
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1286
+ continue;
1287
+ const record = raw;
1288
+ const inferredId = basename4(name).replace(FILE_TASK_PATTERN, "");
1289
+ const id = typeof record.id === "string" && record.id.trim().length > 0 ? record.id.trim() : inferredId;
1290
+ config[id] = fileTaskToConfigEntry(record, { kind: "files", path: sourcePath });
1291
+ } catch {}
1292
+ }
1293
+ return config;
1294
+ }
1295
+ function fileTaskToConfigEntry(task, source) {
1296
+ const labels = Array.isArray(task.labels) ? task.labels.filter((label) => typeof label === "string") : [];
1297
+ const scope = firstStringList(task.scope, labels.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length)));
1298
+ const validation = firstStringList(task.validation, task.validators, labels.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length)));
1299
+ const roleLabel = labels.find((label) => label.startsWith("role:"));
1300
+ const role = typeof task.role === "string" && task.role.trim().length > 0 ? task.role.trim() : roleLabel ? roleLabel.slice("role:".length) : undefined;
1301
+ return {
1302
+ auto_synced: true,
1303
+ ...typeof task.title === "string" ? { title: task.title } : {},
1304
+ ...typeof task.status === "string" ? { status: task.status } : {},
1305
+ ...typeof task.description === "string" ? { description: task.description } : {},
1306
+ ...typeof task.acceptance_criteria === "string" ? { acceptance_criteria: task.acceptance_criteria } : typeof task.acceptanceCriteria === "string" ? { acceptance_criteria: task.acceptanceCriteria } : {},
1307
+ ...role ? { role } : {},
1308
+ ...scope.length > 0 ? { scope } : {},
1309
+ ...validation.length > 0 ? { validation } : {},
1310
+ _rig: { taskSource: source }
1311
+ };
1312
+ }
1313
+ function firstStringList(...candidates) {
1314
+ for (const candidate of candidates) {
1315
+ if (!Array.isArray(candidate)) {
1316
+ continue;
1317
+ }
1318
+ const list = candidate.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
1319
+ if (list.length > 0) {
1320
+ return list;
1321
+ }
1322
+ }
1323
+ return [];
1324
+ }
1325
+ function readConfiguredFilesTaskSourcePath(projectRoot) {
1326
+ const jsonPath = resolve9(projectRoot, "rig.config.json");
1327
+ if (existsSync7(jsonPath)) {
1328
+ try {
1329
+ const parsed = JSON.parse(readFileSync5(jsonPath, "utf8"));
1330
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1331
+ const taskSource = parsed.taskSource;
1332
+ if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
1333
+ const record = taskSource;
1334
+ return record.kind === "files" && typeof record.path === "string" ? record.path : null;
1335
+ }
1336
+ }
1337
+ } catch {
1338
+ return null;
1339
+ }
1340
+ }
1341
+ const tsPath = resolve9(projectRoot, "rig.config.ts");
1342
+ if (!existsSync7(tsPath)) {
1343
+ return null;
1344
+ }
1345
+ try {
1346
+ const source = readFileSync5(tsPath, "utf8");
1347
+ const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
1348
+ const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
1349
+ if (kind !== "files") {
1350
+ return null;
1351
+ }
1352
+ return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
1353
+ } catch {
1354
+ return null;
1355
+ }
1356
+ }
1357
+ function sourceTaskConfigCandidates(projectRoot) {
1358
+ const runtimeContext = loadRuntimeContextFromEnv();
1359
+ return [
1360
+ runtimeContext?.monorepoMainRoot ? resolve9(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
1361
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve9(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
1362
+ resolve9(resolveMonorepoRoot2(projectRoot), ".rig", "task-config.json")
1363
+ ].filter(Boolean);
1364
+ }
1365
+
1366
+ // packages/runtime/src/binary-run.ts
1367
+ var runtimeBinaryBuildQueue = Promise.resolve();
1368
+ // packages/runtime/src/control-plane/provider/runtime-instructions.ts
1369
+ var CLAUDE_ROUTER_TOOL_NAMES = [
1370
+ "`mcp__rig_runtime_tools__read`",
1371
+ "`mcp__rig_runtime_tools__write`",
1372
+ "`mcp__rig_runtime_tools__edit`",
1373
+ "`mcp__rig_runtime_tools__glob`",
1374
+ "`mcp__rig_runtime_tools__grep`"
1375
+ ].join(", ");
1376
+ var CODEX_DYNAMIC_TOOL_NAMES = [
1377
+ "`shell`",
1378
+ "`read`",
1379
+ "`write`",
1380
+ "`edit`",
1381
+ "`glob`",
1382
+ "`grep`"
1383
+ ].join(", ");
1384
+
1385
+ // packages/runtime/src/control-plane/native/task-ops.ts
1386
+ var BUILD_CONFIG = readBuildConfig();
1387
+ var BAKED_INFO_OUTPUT = BUILD_CONFIG.AGENT_INFO_OUTPUT ?? "";
1388
+ var BAKED_DEPS_OUTPUT = BUILD_CONFIG.AGENT_DEPS_OUTPUT ?? "";
1389
+ var BAKED_STATUS_OUTPUT = BUILD_CONFIG.AGENT_STATUS_OUTPUT ?? "";
1390
+ var REOPENABLE_TASK_STATUSES = new Set(["completed", "cancelled", "blocked"]);
1391
+ function hasRuntimeWorkspace() {
1392
+ return Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
1393
+ }
1394
+ function readTaskConfigForInvocation(projectRoot) {
1395
+ return hasRuntimeWorkspace() ? readTaskConfig(projectRoot) : readSourceTaskConfig(projectRoot);
1396
+ }
1397
+ var GENERATED_TASK_ARTIFACT_FILES = new Set([
1398
+ "changed-files.txt",
1399
+ "decision-log.md",
1400
+ "next-actions.md",
1401
+ "task-result.json",
1402
+ "validation-summary.json",
1403
+ "review-feedback.md",
1404
+ "review-state.json",
1405
+ "review-status.txt",
1406
+ "review-greptile-raw.json",
1407
+ "pr-state.json",
1408
+ "git-state.txt"
1409
+ ]);
1410
+ function collectTaskChangedFiles(projectRoot, taskId, includeCommitted) {
1411
+ const paths = resolveHarnessPaths(projectRoot);
1412
+ const monorepoRepoRoot = resolveTaskMonorepoRoot(projectRoot);
1413
+ const files = [];
1414
+ const projectBaseline = resolveRuntimeDirtyBaseline(projectRoot, projectRoot);
1415
+ const monorepoBaseline = resolveRuntimeDirtyBaseline(projectRoot, monorepoRepoRoot);
1416
+ for (const [repo, prefix] of [
1417
+ [projectRoot, ""],
1418
+ [monorepoRepoRoot, ""]
1419
+ ]) {
1420
+ if (!existsSync8(resolve10(repo, ".git"))) {
1421
+ continue;
1422
+ }
1423
+ if (includeCommitted && repo === monorepoRepoRoot) {
1424
+ for (const line of collectCommittedMonorepoFiles(projectRoot, repo)) {
1425
+ files.push(`${prefix}${line}`);
1426
+ }
1427
+ }
1428
+ const baseline = repo === paths.monorepoRoot ? monorepoBaseline : projectBaseline;
1429
+ for (const line of collectWorkingTreeFiles(projectRoot, repo, baseline)) {
1430
+ files.push(`${prefix}${line}`);
1431
+ }
1432
+ }
1433
+ const uniqueFiles = unique(files).map((file) => normalizeChangedFilePath(file)).filter(Boolean);
1434
+ return filterTaskChangedFiles(projectRoot, taskId, uniqueFiles, false);
1435
+ }
1436
+ function filterTaskChangedFiles(projectRoot, taskId, files, scoped) {
1437
+ const uniqueFiles = unique(files).map((file) => normalizeChangedFilePath(file)).filter(Boolean);
1438
+ if (!taskId) {
1439
+ return unique(uniqueFiles).sort();
1440
+ }
1441
+ const filteredFiles = unique(uniqueFiles).filter((file) => !isGeneratedTaskChangePath(taskId, file));
1442
+ if (!scoped) {
1443
+ return filteredFiles.sort();
1444
+ }
1445
+ const paths = resolveHarnessPaths(projectRoot);
1446
+ const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
1447
+ const scopes = readTaskConfigForInvocation(projectRoot)[taskId]?.scope || [];
1448
+ if (scopes.length === 0) {
1449
+ return [];
1450
+ }
1451
+ return filteredFiles.filter((file) => {
1452
+ const normalized = normalizePathToScope(projectRoot, monorepoRoot || paths.monorepoRoot, file);
1453
+ return scopeMatches(file, scopes) || scopeMatches(normalized, scopes);
1454
+ }).sort();
1455
+ }
1456
+ function resolveTaskMonorepoRoot(projectRoot) {
1457
+ const runtimeWorkspace = loadRuntimeContextFromEnv()?.workspaceDir || process.env.RIG_TASK_WORKSPACE?.trim();
1458
+ if (runtimeWorkspace && existsSync8(resolve10(runtimeWorkspace, ".git"))) {
1459
+ return resolve10(runtimeWorkspace);
1460
+ }
1461
+ return resolveHarnessPaths(projectRoot).monorepoRoot;
1462
+ }
1463
+ function collectCommittedMonorepoFiles(projectRoot, repo) {
1464
+ const initialHeadCommit = resolveRuntimeInitialHeadCommit(projectRoot, repo);
1465
+ if (initialHeadCommit) {
1466
+ const result = runCapture(["git", "-C", repo, "diff", "--name-only", `${initialHeadCommit}..HEAD`], projectRoot);
1467
+ if (result.exitCode === 0) {
1468
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1469
+ }
1470
+ }
1471
+ const baseCommit = resolveMonorepoBaseCommit(projectRoot, repo);
1472
+ if (!baseCommit) {
1473
+ return [];
1474
+ }
1475
+ for (const revision of [`${baseCommit}...HEAD`, `${baseCommit}..HEAD`]) {
1476
+ const result = runCapture(["git", "-C", repo, "diff", "--name-only", revision], projectRoot);
1477
+ if (result.exitCode === 0) {
1478
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1479
+ }
1480
+ }
1481
+ return [];
1482
+ }
1483
+ function resolveRuntimeInitialHeadCommit(projectRoot, repo) {
1484
+ const runtimeContext = loadRuntimeContextFromEnv();
1485
+ if (runtimeContext?.initialHeadCommits?.monorepo?.trim()) {
1486
+ const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
1487
+ if (resolve10(monorepoRoot) === resolve10(repo)) {
1488
+ return runtimeContext.initialHeadCommits.monorepo.trim();
1489
+ }
1490
+ }
1491
+ return "";
1492
+ }
1493
+ function resolveMonorepoBaseCommit(projectRoot, repo) {
1494
+ const runtimeContext = loadRuntimeContextFromEnv();
1495
+ if (runtimeContext?.monorepoBaseCommit?.trim()) {
1496
+ const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
1497
+ if (resolve10(monorepoRoot) === resolve10(repo)) {
1498
+ return runtimeContext.monorepoBaseCommit.trim();
1499
+ }
1500
+ }
1501
+ return "";
1502
+ }
1503
+ function collectWorkingTreeFiles(projectRoot, repo, baseline) {
1504
+ const files = new Set;
1505
+ for (const args of [
1506
+ ["diff", "--name-only"],
1507
+ ["diff", "--cached", "--name-only"],
1508
+ ["ls-files", "--others", "--exclude-standard"]
1509
+ ]) {
1510
+ const result = runCapture(["git", "-C", repo, ...args], projectRoot);
1511
+ if (result.exitCode !== 0) {
1512
+ continue;
1513
+ }
1514
+ for (const line of result.stdout.split(/\r?\n/)) {
1515
+ const normalized = normalizeChangedFilePath(line);
1516
+ if (!normalized || baseline.has(normalized)) {
1517
+ continue;
1518
+ }
1519
+ files.add(normalized);
1520
+ }
1521
+ }
1522
+ return [...files].sort();
1523
+ }
1524
+ function resolveRuntimeDirtyBaseline(projectRoot, repo) {
1525
+ const runtimeContext = loadRuntimeContextFromEnv();
1526
+ const dirtyFiles = runtimeContext?.initialDirtyFiles;
1527
+ if (!dirtyFiles) {
1528
+ return new Set;
1529
+ }
1530
+ const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
1531
+ const selected = resolve10(repo) === resolve10(monorepoRoot) ? dirtyFiles.monorepo : resolve10(repo) === resolve10(projectRoot) ? dirtyFiles.project : undefined;
1532
+ return new Set((selected || []).map((file) => normalizeChangedFilePath(file)).filter(Boolean));
1533
+ }
1534
+ function normalizeChangedFilePath(file) {
1535
+ return file.trim().replace(/\\/g, "/").replace(/^\.\//, "");
1536
+ }
1537
+ function isGeneratedTaskChangePath(taskId, file) {
1538
+ const normalized = normalizeChangedFilePath(file);
1539
+ if (!normalized) {
1540
+ return true;
1541
+ }
1542
+ if (normalized.startsWith(".rig/") || normalized.startsWith(".beads/")) {
1543
+ return true;
1544
+ }
1545
+ const taskArtifactPrefix = `artifacts/${taskId}/`;
1546
+ if (!normalized.startsWith(taskArtifactPrefix)) {
1547
+ return false;
1548
+ }
1549
+ const artifactRelativePath = normalized.slice(taskArtifactPrefix.length);
1550
+ return GENERATED_TASK_ARTIFACT_FILES.has(artifactRelativePath) || artifactRelativePath.startsWith("runtime-snapshots/");
1551
+ }
1552
+ function changedFilesForTask(projectRoot, taskId, scoped) {
1553
+ return filterTaskChangedFiles(projectRoot, taskId, collectTaskChangedFiles(projectRoot, taskId, true), scoped);
1554
+ }
1555
+
1556
+ // packages/runtime/src/control-plane/native/git-ops.ts
1557
+ var TASK_RUNTIME_STAGE_EXCLUDES = [
1558
+ ".rig/bin/**",
1559
+ ".rig/cache/**",
1560
+ ".rig/home/**",
1561
+ ".rig/logs/**",
1562
+ ".rig/runtime/**",
1563
+ ".rig/session/**",
1564
+ ".rig/state/**",
1565
+ ".rig/runtime-context.json"
1566
+ ];
1567
+ var GENERATED_STAGE_EXCLUDES = ["artifacts/*/runtime-snapshots/**"];
1568
+ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
1569
+ "changed-files.txt",
1570
+ "contract-changes.md",
1571
+ "decision-log.md",
1572
+ "git-state.txt",
1573
+ "next-actions.md",
1574
+ "pr-state.json",
1575
+ "task-result.json",
1576
+ "validation-summary.json"
1577
+ ]);
1578
+ function resolveHostRigBinDir(root) {
1579
+ return resolve11(root, ".rig", "bin");
1580
+ }
1581
+ function isRuntimeGatewayGitPath(candidate) {
1582
+ return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
1583
+ }
1584
+ function resolveOptionalMonorepoRoot(projectRoot) {
1585
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
1586
+ if (runtimeWorkspace && existsSync9(resolve11(runtimeWorkspace, ".git"))) {
1587
+ return resolve11(runtimeWorkspace);
1588
+ }
1589
+ try {
1590
+ return resolveMonorepoRoot2(projectRoot);
1591
+ } catch {
1592
+ return null;
1593
+ }
1594
+ }
1595
+ function resolveGitBinary(projectRoot) {
1596
+ const candidates = new Set;
1597
+ const explicit = process.env.RIG_GIT_BIN?.trim();
1598
+ if (explicit) {
1599
+ candidates.add(explicit);
1600
+ }
1601
+ for (const candidate of ["/usr/bin/git", "/opt/homebrew/bin/git", "/usr/local/bin/git"]) {
1602
+ candidates.add(candidate);
1603
+ }
1604
+ const bunResolved = Bun.which("git");
1605
+ if (bunResolved && !isRuntimeGatewayGitPath(bunResolved)) {
1606
+ candidates.add(bunResolved);
1607
+ }
1608
+ for (const candidate of candidates) {
1609
+ if (!candidate || isRuntimeGatewayGitPath(candidate)) {
1610
+ continue;
1611
+ }
1612
+ if (existsSync9(candidate)) {
1613
+ return candidate;
1614
+ }
1615
+ }
1616
+ return "git";
1617
+ }
1618
+ function safeCurrentTaskId(projectRoot) {
1619
+ try {
1620
+ const taskId = currentTaskId(projectRoot);
1621
+ return /^bd-[a-z0-9-]+$/.test(taskId) ? taskId : "";
1622
+ } catch {
1623
+ return "";
1624
+ }
1625
+ }
1626
+ function gitCmd(projectRoot, repoRoot, ...args) {
1627
+ return [resolveGitBinary(projectRoot), "-C", repoRoot, ...args];
1628
+ }
1629
+ function shouldScopeGitCommit(args, hasTaskContext) {
1630
+ if (!hasTaskContext) {
1631
+ return false;
1632
+ }
1633
+ return !args.includes("--all") && !args.includes("--unscoped");
1634
+ }
1635
+ function gitStatus(projectRoot, taskId) {
1636
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
1637
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
1638
+ const expected = resolvedTask ? `rig/${resolveTaskBranchId(projectRoot, resolvedTask)}` : "";
1639
+ console.log("=== Git Flow Status ===");
1640
+ if (resolvedTask) {
1641
+ console.log(`Task: ${resolvedTask}`);
1642
+ console.log(`Expected monorepo branch: ${expected}`);
1643
+ } else {
1644
+ console.log("Task: (none active)");
1645
+ }
1646
+ console.log("");
1647
+ printRepoStatus(projectRoot, "project-rig", projectRoot, "");
1648
+ const monorepoPath = monorepoRoot || resolveMonorepoRoot2(projectRoot);
1649
+ if (monorepoPath !== projectRoot) {
1650
+ printRepoStatus(projectRoot, "monorepo", monorepoPath, expected);
1651
+ }
1652
+ }
1653
+ function gitChanged(projectRoot, taskId, scoped) {
1654
+ if (scoped) {
1655
+ const resolvedTask = taskId || currentTaskId(projectRoot);
1656
+ if (!resolvedTask) {
1657
+ throw new Error("No task specified and no active task in session. Use --task or omit --scoped.");
1658
+ }
1659
+ return changedFilesForTask(projectRoot, resolvedTask, true);
1660
+ }
1661
+ return changedFilesForTask(projectRoot, taskId || currentTaskId(projectRoot) || "", false);
1662
+ }
1663
+ function gitPreflight(projectRoot, taskId, strict) {
1664
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
1665
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
1666
+ const expected = resolvedTask ? `rig/${resolveTaskBranchId(projectRoot, resolvedTask)}` : "";
1667
+ console.log("=== Git Flow Preflight ===");
1668
+ let issues = 0;
1669
+ if (!existsSync9(resolve11(projectRoot, ".git"))) {
1670
+ console.log(`ERROR: project root is not a git repo (${projectRoot})`);
1671
+ issues += 1;
1672
+ }
1673
+ if (monorepoRoot && existsSync9(resolve11(monorepoRoot, ".git"))) {
1674
+ const monoBranch = branchName(projectRoot, monorepoRoot);
1675
+ if (expected && monoBranch !== expected) {
1676
+ console.log(`WARN: monorepo branch is ${monoBranch}, expected ${expected} for task ${resolvedTask}`);
1677
+ if (strict) {
1678
+ issues += 1;
1679
+ }
1680
+ }
1681
+ const monoChanges = changeCount(projectRoot, monorepoRoot);
1682
+ if (monoChanges > 0 && !monoBranch.startsWith("rig/")) {
1683
+ console.log(`WARN: monorepo has uncommitted changes on non-rig branch (${monoBranch})`);
1684
+ issues += 1;
1685
+ }
1686
+ } else {
1687
+ console.log(`WARN: monorepo repo unavailable.`);
1688
+ }
1689
+ const projectChanges = changeCount(projectRoot, projectRoot);
1690
+ if (projectChanges > 0) {
1691
+ console.log(`INFO: project-rig has ${projectChanges} changed file(s).`);
1692
+ }
1693
+ if (issues > 0) {
1694
+ console.log(`Preflight: ${issues} issue(s) detected.`);
1695
+ return false;
1696
+ }
1697
+ console.log("Preflight: OK");
1698
+ return true;
1699
+ }
1700
+ function gitSyncBranch(projectRoot, taskId, targetRepo = "monorepo") {
1701
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
1702
+ if (!resolvedTask) {
1703
+ throw new Error("No task specified and no active task in session.");
1704
+ }
1705
+ const repoRoot = targetRepo === "monorepo" ? resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot2(projectRoot) : projectRoot;
1706
+ const repoLabel = targetRepo === "monorepo" ? "Monorepo" : "Project";
1707
+ if (!existsSync9(resolve11(repoRoot, ".git"))) {
1708
+ throw new Error(`${repoLabel} repo not found at ${repoRoot}`);
1709
+ }
1710
+ const branchId = resolveTaskBranchId(projectRoot, resolvedTask);
1711
+ const branchTarget = `rig/${branchId}`;
1712
+ const current = branchName(projectRoot, repoRoot);
1713
+ if (current === branchTarget) {
1714
+ console.log(`${repoLabel} branch: already on ${branchTarget}`);
1715
+ return;
1716
+ }
1717
+ const hasBranch = runCapture2(gitCmd(projectRoot, repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branchTarget}`), projectRoot).exitCode === 0;
1718
+ const cmd = hasBranch && current === "HEAD" ? gitCmd(projectRoot, repoRoot, "checkout", "-B", branchTarget) : hasBranch ? gitCmd(projectRoot, repoRoot, "checkout", branchTarget) : gitCmd(projectRoot, repoRoot, "checkout", "-b", branchTarget);
1719
+ const checkout = runCapture2(cmd, projectRoot);
1720
+ if (checkout.exitCode !== 0) {
1721
+ throw new Error(`Failed to sync ${repoLabel.toLowerCase()} branch: ${checkout.stderr || checkout.stdout}`);
1722
+ }
1723
+ const action = hasBranch && current === "HEAD" ? "reset" : hasBranch ? "checked out" : "created";
1724
+ console.log(`${repoLabel} branch: ${action} ${branchTarget}`);
1725
+ }
1726
+ function gitCommit(options) {
1727
+ const { projectRoot } = options;
1728
+ const resolvedTask = options.taskId || safeCurrentTaskId(projectRoot);
1729
+ const baseMessage = options.message || (resolvedTask ? `rig: ${resolvedTask}` : "rig: harness update");
1730
+ const changedFilesManifest = resolvedTask && options.scoped === true ? refreshChangedFilesManifest(projectRoot, resolvedTask) : "";
1731
+ const changedFiles = resolvedTask && options.scoped === true ? readChangedFilesManifest(projectRoot, resolvedTask) : resolvedTask ? changedFilesForTask(projectRoot, resolvedTask, false) : [];
1732
+ if (options.target === "project" || options.target === "both") {
1733
+ if (resolvedTask) {
1734
+ gitSyncBranch(projectRoot, resolvedTask, "project");
1735
+ }
1736
+ const projectFiles = resolveScopedStageFilesForRepo(projectRoot, projectRoot, resolvedTask, changedFiles);
1737
+ commitRepo(projectRoot, projectRoot, "project-rig", options.target === "both" ? `${baseMessage} [harness]` : baseMessage, options.allowEmpty, options.scoped === true, projectFiles, changedFilesManifest);
1738
+ }
1739
+ if (options.target === "monorepo" || options.target === "both") {
1740
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot2(projectRoot);
1741
+ if (resolvedTask) {
1742
+ gitSyncBranch(projectRoot, resolvedTask, "monorepo");
1743
+ }
1744
+ const monorepoFiles = resolveScopedStageFilesForRepo(projectRoot, monorepoRoot, resolvedTask, changedFiles);
1745
+ commitRepo(projectRoot, monorepoRoot, "monorepo", options.target === "both" ? `${baseMessage} [monorepo]` : baseMessage, options.allowEmpty, options.scoped === true, monorepoFiles, changedFilesManifest);
1746
+ }
1747
+ }
1748
+ function gitSnapshot(projectRoot, taskId, outputPath) {
1749
+ const monorepoRoot = resolveOptionalMonorepoRoot(projectRoot);
1750
+ const resolvedTask = taskId || safeCurrentTaskId(projectRoot);
1751
+ const output = outputPath || (resolvedTask ? resolveArtifactSnapshot(projectRoot, resolvedTask) : resolve11(resolve11(projectRoot, ".rig", "state"), "git-state.txt"));
1752
+ mkdirSync5(dirname7(output), { recursive: true });
1753
+ const lines = ["# Git Snapshot", `timestamp: ${nowIso()}`];
1754
+ if (resolvedTask) {
1755
+ lines.push(`task: ${resolvedTask}`);
1756
+ }
1757
+ lines.push("");
1758
+ lines.push(...snapshotRepo(projectRoot, "project-rig", projectRoot));
1759
+ if (monorepoRoot && monorepoRoot !== projectRoot) {
1760
+ lines.push(...snapshotRepo(projectRoot, "monorepo", monorepoRoot));
1761
+ }
1762
+ writeFileSync5(output, `${lines.join(`
1763
+ `)}
1764
+ `, "utf-8");
1765
+ return output;
1766
+ }
1767
+ function gitOpenPr(options) {
1768
+ const gh = resolveGithubCliBinary(options.projectRoot);
1769
+ if (!gh) {
1770
+ throw new Error("gh CLI is required for open-pr. Install and authenticate with: gh auth login");
1771
+ }
1772
+ const taskId = options.taskId || safeCurrentTaskId(options.projectRoot);
1773
+ const target = options.target || (taskId ? "monorepo" : "project");
1774
+ let repoRoot = options.projectRoot;
1775
+ let repoLabel = "project-rig";
1776
+ let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
1777
+ if (target === "monorepo") {
1778
+ repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
1779
+ repoLabel = "monorepo";
1780
+ defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
1781
+ if (taskId) {
1782
+ gitSyncBranch(options.projectRoot, taskId, "monorepo");
1783
+ }
1784
+ } else if (taskId) {
1785
+ gitSyncBranch(options.projectRoot, taskId, "project");
1786
+ defaultBase = inferProjectBase(options.projectRoot, defaultBase);
1787
+ }
1788
+ if (!existsSync9(resolve11(repoRoot, ".git"))) {
1789
+ throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
1790
+ }
1791
+ const branch = branchName(options.projectRoot, repoRoot);
1792
+ if (!branch || branch === "HEAD") {
1793
+ throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
1794
+ }
1795
+ const base = options.base || defaultBase;
1796
+ const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
1797
+ const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
1798
+ refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
1799
+ let reviewer = (options.reviewer || "").trim();
1800
+ let reviewerSource = reviewer ? "flag" : undefined;
1801
+ if (!reviewer && taskId) {
1802
+ reviewer = defaultReviewerForTask(options.projectRoot, taskId);
1803
+ if (reviewer) {
1804
+ reviewerSource = "task-config";
1805
+ }
1806
+ }
1807
+ if (!reviewer) {
1808
+ reviewer = inferReviewerFromChangedFiles(options.projectRoot, repoRoot, base, branch);
1809
+ if (reviewer) {
1810
+ reviewerSource = "changed-files";
1811
+ }
1812
+ }
1813
+ if (!reviewer) {
1814
+ reviewer = (process.env.RIG_PR_REVIEWER || "").trim();
1815
+ if (reviewer) {
1816
+ reviewerSource = "env";
1817
+ }
1818
+ }
1819
+ let title = options.title || "";
1820
+ if (!title) {
1821
+ if (taskId) {
1822
+ title = `rig: ${taskId}`;
1823
+ } else {
1824
+ title = `rig: update ${branch}`;
1825
+ }
1826
+ }
1827
+ const body = options.body || [
1828
+ "## Summary",
1829
+ "- Automated task output prepared in isolated runtime.",
1830
+ "",
1831
+ "## Task",
1832
+ `- beads: ${taskId || "n/a"}`,
1833
+ "",
1834
+ "## Review",
1835
+ "- Completion verification will run validation, verifier review, and PR policy checks.",
1836
+ "- When repository policy allows it, Rig enables GitHub auto-merge after approval."
1837
+ ].join(`
1838
+ `);
1839
+ const preCheck = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
1840
+ const preCheckEntry = preCheck.exitCode === 0 ? preCheck.stdout.trim() : "";
1841
+ if (preCheckEntry && preCheckEntry !== "null" && currentHeadMatchesMergedBase(options.projectRoot, repoRoot, base, networkRemote)) {
1842
+ const mergedPr = JSON.parse(preCheckEntry);
1843
+ console.log(`Branch ${branch} was already merged: ${mergedPr.url}`);
1844
+ const result2 = { url: mergedPr.url, target, repoLabel, branch, base };
1845
+ if (taskId)
1846
+ writePrMetadata(options.projectRoot, taskId, result2);
1847
+ return result2;
1848
+ }
1849
+ const pushArgs = gitCmd(options.projectRoot, repoRoot, "push", "-u", networkRemote, branch);
1850
+ const fetchResult = runCapture2(gitCmd(options.projectRoot, repoRoot, "fetch", networkRemote, branch), repoRoot);
1851
+ if (fetchResult.exitCode === 0) {
1852
+ const remoteAhead = runCapture2(gitCmd(options.projectRoot, repoRoot, "log", "--oneline", `HEAD..${networkRemote}/${branch}`), repoRoot);
1853
+ if (remoteAhead.exitCode === 0 && remoteAhead.stdout.trim()) {
1854
+ console.log(`Remote branch has diverged \u2014 force pushing task-owned branch ${branch} with --force-with-lease...`);
1855
+ pushArgs.splice(4, 0, "--force-with-lease");
1856
+ }
1857
+ }
1858
+ runOrThrow(options.projectRoot, pushArgs, `Failed to push branch ${branch} in ${repoLabel}`);
1859
+ const existing = runCapture2(withGhRepo([gh, "pr", "list", "--state", "open", "--head", branch, "--json", "url", "--jq", ".[0].url"], repoNameWithOwner), repoRoot);
1860
+ const existingUrl = existing.exitCode === 0 ? existing.stdout.trim() : "";
1861
+ if (!existingUrl || existingUrl === "null") {
1862
+ const merged = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
1863
+ const mergedEntry = merged.exitCode === 0 ? merged.stdout.trim() : "";
1864
+ if (mergedEntry && mergedEntry !== "null" && currentHeadMatchesMergedBase(options.projectRoot, repoRoot, base, networkRemote)) {
1865
+ const mergedPr = JSON.parse(mergedEntry);
1866
+ console.log(`Branch ${branch} was already merged: ${mergedPr.url}`);
1867
+ const result2 = { url: mergedPr.url, target, repoLabel, branch, base };
1868
+ if (taskId)
1869
+ writePrMetadata(options.projectRoot, taskId, result2);
1870
+ return result2;
1871
+ }
1872
+ }
1873
+ let prUrl = "";
1874
+ if (existingUrl && existingUrl !== "null") {
1875
+ prUrl = existingUrl;
1876
+ } else {
1877
+ const createArgs = [
1878
+ gh,
1879
+ "pr",
1880
+ "create",
1881
+ ...ghRepoArgs(repoNameWithOwner),
1882
+ "--base",
1883
+ base,
1884
+ "--head",
1885
+ branch,
1886
+ "--title",
1887
+ title,
1888
+ "--body",
1889
+ body
1890
+ ];
1891
+ if (options.draft) {
1892
+ createArgs.push("--draft");
1893
+ }
1894
+ const created = runCapture2(createArgs, repoRoot);
1895
+ if (created.exitCode !== 0) {
1896
+ throw new Error(`Failed to create PR in ${repoLabel}: ${created.stderr || created.stdout}`);
1897
+ }
1898
+ prUrl = created.stdout.trim();
1899
+ }
1900
+ if (!prUrl) {
1901
+ throw new Error(`Failed to resolve PR URL for branch ${branch}.`);
1902
+ }
1903
+ assertPrHasNoGitConflicts(readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl), repoLabel, base);
1904
+ if (reviewer) {
1905
+ const edit = runCapture2(withGhRepo([gh, "pr", "edit", prUrl, "--add-reviewer", reviewer], repoNameWithOwner), repoRoot);
1906
+ if (edit.exitCode !== 0) {
1907
+ throw new Error(`Failed to assign reviewer '${reviewer}': ${edit.stderr || edit.stdout}`);
1908
+ }
1909
+ }
1910
+ const result = {
1911
+ url: prUrl,
1912
+ reviewer,
1913
+ reviewerSource,
1914
+ target,
1915
+ repoLabel,
1916
+ branch,
1917
+ base
1918
+ };
1919
+ if (taskId) {
1920
+ writePrMetadata(options.projectRoot, taskId, result);
1921
+ }
1922
+ return result;
1923
+ }
1924
+ function resolveTaskBranchRef(projectRoot, taskId) {
1925
+ return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1926
+ }
1927
+ function readPrViewState(gh, repoRoot, repoNameWithOwner, prUrl) {
1928
+ const view = runCapture2(withGhRepo([
1929
+ gh,
1930
+ "pr",
1931
+ "view",
1932
+ prUrl,
1933
+ "--json",
1934
+ "state,isDraft,url,mergedAt,autoMergeRequest,mergeable,mergeStateStatus,reviewDecision,headRefOid,statusCheckRollup",
1935
+ "--jq",
1936
+ "."
1937
+ ], repoNameWithOwner), repoRoot);
1938
+ if (view.exitCode !== 0) {
1939
+ throw new Error(`Failed to inspect PR ${prUrl}: ${view.stderr || view.stdout}`);
1940
+ }
1941
+ try {
1942
+ const parsed = JSON.parse(view.stdout);
1943
+ return {
1944
+ state: parsed.state || "OPEN",
1945
+ isDraft: parsed.isDraft === true,
1946
+ url: typeof parsed.url === "string" ? parsed.url : prUrl,
1947
+ mergedAt: typeof parsed.mergedAt === "string" ? parsed.mergedAt : null,
1948
+ autoMergeRequest: parsed.autoMergeRequest ?? null,
1949
+ mergeable: typeof parsed.mergeable === "string" ? parsed.mergeable : "",
1950
+ mergeStateStatus: typeof parsed.mergeStateStatus === "string" ? parsed.mergeStateStatus : "",
1951
+ reviewDecision: typeof parsed.reviewDecision === "string" ? parsed.reviewDecision : "",
1952
+ headRefOid: typeof parsed.headRefOid === "string" ? parsed.headRefOid : null,
1953
+ statusCheckRollup: Array.isArray(parsed.statusCheckRollup) ? parsed.statusCheckRollup : []
1954
+ };
1955
+ } catch {
1956
+ return {
1957
+ state: "OPEN",
1958
+ isDraft: false,
1959
+ url: prUrl,
1960
+ mergedAt: null,
1961
+ autoMergeRequest: null,
1962
+ mergeable: "",
1963
+ mergeStateStatus: "",
1964
+ reviewDecision: "",
1965
+ headRefOid: null,
1966
+ statusCheckRollup: []
1967
+ };
1968
+ }
1969
+ }
1970
+ function hasSatisfiedStatusChecks(prState) {
1971
+ if (prState.statusCheckRollup.length === 0) {
1972
+ return false;
1973
+ }
1974
+ return prState.statusCheckRollup.every((entry) => {
1975
+ if (entry.__typename === "CheckRun") {
1976
+ const status = entry.status?.toUpperCase() || "";
1977
+ const conclusion = entry.conclusion?.toUpperCase() || "";
1978
+ return status === "COMPLETED" && (conclusion === "SUCCESS" || conclusion === "SKIPPED" || conclusion === "NEUTRAL");
1979
+ }
1980
+ if (entry.__typename === "StatusContext") {
1981
+ return (entry.state?.toUpperCase() || "") === "SUCCESS";
1982
+ }
1983
+ return false;
1984
+ });
1985
+ }
1986
+ function canAdminMergeApprovedPr(prState) {
1987
+ return prState.state === "OPEN" && prState.autoMergeRequest !== null && prState.mergeable.toUpperCase() === "MERGEABLE" && prState.reviewDecision.toUpperCase() === "APPROVED" && hasSatisfiedStatusChecks(prState);
1988
+ }
1989
+ function gitMergePr(options) {
1990
+ const gh = resolveGithubCliBinary(options.projectRoot);
1991
+ if (!gh) {
1992
+ throw new Error("gh CLI is required for merge-pr. Install and authenticate with: gh auth login");
1993
+ }
1994
+ const repoRoot = resolveRepoRoot(options.projectRoot, options.pr.target);
1995
+ const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
1996
+ if (!existsSync9(resolve11(repoRoot, ".git"))) {
1997
+ throw new Error(`Repository not available for merge-pr target ${options.pr.target}: ${repoRoot}`);
1998
+ }
1999
+ const prState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2000
+ const state = prState.state;
2001
+ const isDraft = prState.isDraft;
2002
+ assertPrHasNoGitConflicts(prState, options.pr.repoLabel, options.pr.base);
2003
+ if (state === "MERGED") {
2004
+ console.log(`PR already merged (${options.pr.repoLabel}): ${options.pr.url}`);
2005
+ return { status: "already-merged", url: options.pr.url };
2006
+ }
2007
+ if (state !== "OPEN") {
2008
+ throw new Error(`Cannot auto-merge PR ${options.pr.url}: state is ${state}.`);
2009
+ }
2010
+ if (isDraft) {
2011
+ throw new Error(`Cannot auto-merge draft PR ${options.pr.url}.`);
2012
+ }
2013
+ const mergeArgs = withGhRepo([gh, "pr", "merge", options.pr.url], repoNameWithOwner);
2014
+ const method = options.method || "squash";
2015
+ mergeArgs.push(method === "merge" ? "--merge" : method === "rebase" ? "--rebase" : "--squash");
2016
+ if (options.deleteBranch !== false) {
2017
+ mergeArgs.push("--delete-branch");
2018
+ }
2019
+ const autoMergeArgs = [...mergeArgs, "--auto"];
2020
+ const autoMerge = runCapture2(autoMergeArgs, repoRoot);
2021
+ if (autoMerge.exitCode === 0) {
2022
+ const postAutoMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2023
+ if (postAutoMergeState.state === "MERGED" || postAutoMergeState.mergedAt) {
2024
+ console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
2025
+ return { status: "merged", url: options.pr.url };
2026
+ }
2027
+ if (postAutoMergeState.state === "OPEN" && postAutoMergeState.autoMergeRequest) {
2028
+ if (canAdminMergeApprovedPr(postAutoMergeState)) {
2029
+ const adminMergeArgs = [...mergeArgs];
2030
+ if (postAutoMergeState.headRefOid) {
2031
+ adminMergeArgs.push("--match-head-commit", postAutoMergeState.headRefOid);
2032
+ }
2033
+ adminMergeArgs.push("--admin");
2034
+ const adminMerge = runCapture2(adminMergeArgs, repoRoot);
2035
+ if (adminMerge.exitCode === 0) {
2036
+ const postAdminMergeState = readPrViewState(gh, repoRoot, repoNameWithOwner, options.pr.url);
2037
+ if (postAdminMergeState.state === "MERGED" || postAdminMergeState.mergedAt) {
2038
+ console.log(`Merged PR (${options.pr.repoLabel}) with admin fallback: ${options.pr.url}`);
2039
+ return { status: "merged", url: options.pr.url };
2040
+ }
2041
+ throw new Error(`Admin merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub still reports it open.`);
2042
+ }
2043
+ const adminMergeMessage = `${adminMerge.stderr}
2044
+ ${adminMerge.stdout}`.trim();
2045
+ if (!/admin|administrator|permission|not permitted|not allowed/i.test(adminMergeMessage)) {
2046
+ throw new Error(`Failed to admin-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${adminMergeMessage}`);
2047
+ }
2048
+ }
2049
+ console.log(`Auto-merge enabled (${options.pr.repoLabel}): ${options.pr.url}`);
2050
+ return { status: "auto-merge-enabled", url: options.pr.url };
2051
+ }
2052
+ throw new Error(`Auto-merge command succeeded for PR ${options.pr.url} in ${options.pr.repoLabel}, but GitHub did not report a merged or auto-merge-enabled state.`);
2053
+ }
2054
+ const autoMergeMessage = `${autoMerge.stderr}
2055
+ ${autoMerge.stdout}`.trim();
2056
+ const autoMergeUnsupported = /auto.?merge.*(not enabled|not allowed|disabled|unsupported)|enablePullRequestAutoMerge|Auto merge is not allowed/i.test(autoMergeMessage);
2057
+ if (!autoMergeUnsupported) {
2058
+ throw new Error(`Failed to auto-merge PR ${options.pr.url} in ${options.pr.repoLabel}: ${autoMergeMessage}`);
2059
+ }
2060
+ runOrThrow(options.projectRoot, mergeArgs, `Failed to merge PR ${options.pr.url} in ${options.pr.repoLabel}`);
2061
+ console.log(`Merged PR (${options.pr.repoLabel}): ${options.pr.url}`);
2062
+ return { status: "merged", url: options.pr.url };
2063
+ }
2064
+ function assertPrHasNoGitConflicts(prState, repoLabel, baseRef) {
2065
+ const mergeable = prState.mergeable.toUpperCase();
2066
+ const mergeStateStatus = prState.mergeStateStatus.toUpperCase();
2067
+ if (mergeable === "CONFLICTING" || mergeStateStatus === "DIRTY") {
2068
+ throw new Error(`PR ${prState.url || "unknown"} conflicts with ${baseRef} in ${repoLabel} (mergeable=${prState.mergeable || "unknown"}, mergeStateStatus=${prState.mergeStateStatus || "unknown"}). Rebase or merge ${baseRef} and resolve conflicts before completion-verification.`);
2069
+ }
2070
+ }
2071
+ function writePrMetadata(projectRoot, taskId, result) {
2072
+ const dir = artifactDirForId(projectRoot, taskId);
2073
+ mkdirSync5(dir, { recursive: true });
2074
+ const path = resolve11(dir, "pr-state.json");
2075
+ let prs = {};
2076
+ if (existsSync9(path)) {
2077
+ try {
2078
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
2079
+ if (parsed && typeof parsed === "object" && parsed.prs && typeof parsed.prs === "object") {
2080
+ prs = parsed.prs;
2081
+ }
2082
+ } catch {
2083
+ prs = {};
2084
+ }
2085
+ }
2086
+ prs[result.target] = result;
2087
+ const primary = prs.monorepo || prs.project;
2088
+ const artifact = {
2089
+ task_id: taskId,
2090
+ prs,
2091
+ ...primary || {},
2092
+ updated_at: nowIso()
2093
+ };
2094
+ writeFileSync5(path, `${JSON.stringify(artifact, null, 2)}
2095
+ `, "utf-8");
2096
+ }
2097
+ function readPrMetadata(projectRoot, taskId) {
2098
+ const path = resolve11(artifactDirForId(projectRoot, taskId), "pr-state.json");
2099
+ if (!existsSync9(path)) {
2100
+ return [];
2101
+ }
2102
+ try {
2103
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
2104
+ if (!parsed || typeof parsed !== "object") {
2105
+ return [];
2106
+ }
2107
+ if (parsed.prs && typeof parsed.prs === "object") {
2108
+ return Object.values(parsed.prs).filter(isGitOpenPrResult);
2109
+ }
2110
+ return isGitOpenPrResult(parsed) ? [parsed] : [];
2111
+ } catch {
2112
+ return [];
2113
+ }
2114
+ }
2115
+ function resolveArtifactSnapshot(projectRoot, taskId) {
2116
+ const artifactDir = resolve11(resolveHarnessPaths(projectRoot).artifactsDir, taskId);
2117
+ return resolve11(artifactDir, "git-state.txt");
2118
+ }
2119
+ function isGitOpenPrResult(value) {
2120
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2121
+ return false;
2122
+ }
2123
+ const record = value;
2124
+ return typeof record.url === "string" && typeof record.branch === "string" && typeof record.base === "string" && (record.target === "project" || record.target === "monorepo") && typeof record.repoLabel === "string";
2125
+ }
2126
+ function resolveRepoRoot(projectRoot, target) {
2127
+ return target === "monorepo" ? resolveOptionalMonorepoRoot(projectRoot) || resolveMonorepoRoot2(projectRoot) : projectRoot;
2128
+ }
2129
+ function ensureFullGitHistory(projectRoot, repoRoot, remoteName = "origin") {
2130
+ const shallow = runCapture2(gitCmd(projectRoot, repoRoot, "rev-parse", "--is-shallow-repository"), projectRoot);
2131
+ if (shallow.exitCode !== 0 || shallow.stdout.trim() !== "true") {
2132
+ return;
2133
+ }
2134
+ const unshallow = runCapture2(gitCmd(projectRoot, repoRoot, "fetch", "--unshallow", "--tags", remoteName), projectRoot);
2135
+ if (unshallow.exitCode === 0) {
2136
+ return;
2137
+ }
2138
+ const output = `${unshallow.stderr}
2139
+ ${unshallow.stdout}`.trim();
2140
+ if (/--unshallow on a complete repository|does not make sense/i.test(output)) {
2141
+ return;
2142
+ }
2143
+ throw new Error(`Failed to expand git history for ${repoRoot}: ${output}`);
2144
+ }
2145
+ function refreshRemoteBaseRef(projectRoot, repoRoot, baseRef, repoNameWithOwner = "") {
2146
+ const remoteName = resolveNetworkRemoteName(projectRoot, repoRoot, repoNameWithOwner || resolveRepoNameWithOwner(projectRoot, repoRoot));
2147
+ const remoteUrl = runCapture2(gitCmd(projectRoot, repoRoot, "remote", "get-url", remoteName), projectRoot);
2148
+ if (remoteUrl.exitCode !== 0) {
2149
+ return "";
2150
+ }
2151
+ ensureFullGitHistory(projectRoot, repoRoot, remoteName);
2152
+ const fetch2 = runCapture2(gitCmd(projectRoot, repoRoot, "fetch", "--prune", "--tags", remoteName, `+refs/heads/${baseRef}:refs/remotes/${remoteName}/${baseRef}`), projectRoot);
2153
+ if (fetch2.exitCode !== 0) {
2154
+ throw new Error(`Failed to refresh ${remoteName}/${baseRef} at ${repoRoot}: ${fetch2.stderr || fetch2.stdout}`);
2155
+ }
2156
+ return remoteName;
2157
+ }
2158
+ function currentHeadMatchesMergedBase(projectRoot, repoRoot, baseRef, remoteName = "origin") {
2159
+ const activeRemote = refreshRemoteBaseRef(projectRoot, repoRoot, baseRef) || remoteName;
2160
+ const remoteBase = `${activeRemote}/${baseRef}`;
2161
+ const hasRemoteBase = runCapture2(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", remoteBase), repoRoot).exitCode === 0;
2162
+ const targetRef = hasRemoteBase ? remoteBase : baseRef;
2163
+ if (runCapture2(gitCmd(projectRoot, repoRoot, "merge-base", "--is-ancestor", "HEAD", targetRef), repoRoot).exitCode === 0) {
2164
+ return true;
2165
+ }
2166
+ return runCapture2(gitCmd(projectRoot, repoRoot, "diff", "--quiet", "HEAD", targetRef, "--"), repoRoot).exitCode === 0;
2167
+ }
2168
+ function resolveGithubCliBinary(projectRoot) {
2169
+ const candidates = new Set;
2170
+ const explicit = process.env.RIG_GH_BIN?.trim();
2171
+ if (explicit) {
2172
+ candidates.add(explicit);
2173
+ }
2174
+ const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
2175
+ for (const entry of explicitPathEntries) {
2176
+ candidates.add(resolve11(entry, "gh"));
2177
+ }
2178
+ const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
2179
+ if (hostProjectRoot) {
2180
+ candidates.add(resolve11(resolveHostRigBinDir(hostProjectRoot), "gh"));
2181
+ }
2182
+ candidates.add(resolve11(resolveHostRigBinDir(projectRoot), "gh"));
2183
+ const runtimeContext = loadRuntimeContextFromEnv();
2184
+ if (runtimeContext?.binDir) {
2185
+ candidates.add(resolve11(runtimeContext.binDir, "gh"));
2186
+ }
2187
+ const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
2188
+ if (runtimeHome) {
2189
+ candidates.add(resolve11(runtimeHome, "bin", "gh"));
2190
+ }
2191
+ for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
2192
+ candidates.add(candidate);
2193
+ }
2194
+ const bunResolved = Bun.which("gh");
2195
+ if (bunResolved) {
2196
+ candidates.add(bunResolved);
2197
+ }
2198
+ for (const candidate of candidates) {
2199
+ if (candidate && existsSync9(candidate)) {
2200
+ return candidate;
2201
+ }
2202
+ }
2203
+ return "";
2204
+ }
2205
+ function defaultReviewerForTask(projectRoot, taskId) {
2206
+ if (!taskId) {
2207
+ return "";
2208
+ }
2209
+ const entry = readTaskConfig(projectRoot)[taskId];
2210
+ const reviewer = entry?.reviewer;
2211
+ if (typeof reviewer === "string" && reviewer.trim()) {
2212
+ return reviewer.trim();
2213
+ }
2214
+ const responsibleReviewer = entry?.responsible_reviewer;
2215
+ if (typeof responsibleReviewer === "string" && responsibleReviewer.trim()) {
2216
+ return responsibleReviewer.trim();
2217
+ }
2218
+ return "";
2219
+ }
2220
+ function resolveRepoNameWithOwner(projectRoot, repoRoot) {
2221
+ const explicit = normalizeGithubRepoNameWithOwner(process.env.GH_REPO || "");
2222
+ if (explicit) {
2223
+ return explicit;
2224
+ }
2225
+ const visited = new Set;
2226
+ return resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, repoRoot, repoRoot, visited);
2227
+ }
2228
+ function resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, gitRoot, cwd, visited) {
2229
+ const normalizedGitRoot = resolve11(gitRoot);
2230
+ if (visited.has(normalizedGitRoot)) {
2231
+ return "";
2232
+ }
2233
+ visited.add(normalizedGitRoot);
2234
+ const remotes = listGitRemotes(projectRoot, gitRoot, cwd);
2235
+ for (const remote of remotes) {
2236
+ const urls = listGitRemoteUrls(projectRoot, gitRoot, cwd, remote);
2237
+ for (const url of urls) {
2238
+ const direct = normalizeGithubRepoNameWithOwner(url);
2239
+ if (direct) {
2240
+ return direct;
2241
+ }
2242
+ const localGitRoot = resolveLocalGitRemoteRoot(url, gitRoot);
2243
+ if (!localGitRoot) {
2244
+ continue;
2245
+ }
2246
+ const viaMirror = resolveGithubRepoNameWithOwnerFromGitRoot(projectRoot, localGitRoot, cwd, visited);
2247
+ if (viaMirror) {
2248
+ return viaMirror;
2249
+ }
2250
+ }
2251
+ }
2252
+ return "";
2253
+ }
2254
+ function listGitRemotes(projectRoot, gitRoot, cwd) {
2255
+ const result = gitQuery(projectRoot, gitRoot, cwd, "remote");
2256
+ if (result.exitCode !== 0) {
2257
+ return [];
2258
+ }
2259
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2260
+ }
2261
+ function listGitRemoteUrls(projectRoot, gitRoot, cwd, remote) {
2262
+ const result = gitQuery(projectRoot, gitRoot, cwd, "remote", "get-url", "--all", remote);
2263
+ if (result.exitCode !== 0) {
2264
+ return [];
2265
+ }
2266
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2267
+ }
2268
+ function resolveNetworkRemoteName(projectRoot, repoRoot, repoNameWithOwner) {
2269
+ const remotes = listGitRemotes(projectRoot, repoRoot, repoRoot);
2270
+ if (remotes.length === 0) {
2271
+ return "origin";
2272
+ }
2273
+ if (!repoNameWithOwner) {
2274
+ return remotes.includes("origin") ? "origin" : remotes[0];
2275
+ }
2276
+ const expectedRepo = normalizeGithubRepoNameWithOwner(repoNameWithOwner).toLowerCase();
2277
+ let directMatch = "";
2278
+ for (const remote of remotes) {
2279
+ const urls = listGitRemoteUrls(projectRoot, repoRoot, repoRoot, remote);
2280
+ for (const url of urls) {
2281
+ const direct = normalizeGithubRepoNameWithOwner(url);
2282
+ if (direct && direct.toLowerCase() === expectedRepo) {
2283
+ if (remote === "github") {
2284
+ return remote;
2285
+ }
2286
+ if (!directMatch) {
2287
+ directMatch = remote;
2288
+ }
2289
+ }
2290
+ }
2291
+ }
2292
+ if (remotes.includes("github")) {
2293
+ return "github";
2294
+ }
2295
+ if (directMatch) {
2296
+ return directMatch;
2297
+ }
2298
+ return remotes.includes("origin") ? "origin" : remotes[0];
2299
+ }
2300
+ function gitQuery(projectRoot, gitRoot, cwd, ...args) {
2301
+ const gitArgs = existsSync9(resolve11(gitRoot, ".git")) ? gitCmd(projectRoot, gitRoot, ...args) : [resolveGitBinary(projectRoot), "--git-dir", gitRoot, ...args];
2302
+ return runCapture2(gitArgs, cwd, projectRoot);
2303
+ }
2304
+ function resolveLocalGitRemoteRoot(remoteUrl, gitRoot) {
2305
+ const normalized = remoteUrl.trim();
2306
+ if (!normalized) {
2307
+ return "";
2308
+ }
2309
+ let candidate = normalized;
2310
+ if (normalized.startsWith("file://")) {
2311
+ try {
2312
+ candidate = fileURLToPath(normalized);
2313
+ } catch {
2314
+ return "";
2315
+ }
2316
+ } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(normalized) || /^[^@]+@[^:]+:.+$/.test(normalized)) {
2317
+ return "";
2318
+ } else if (!isAbsolute2(normalized)) {
2319
+ candidate = resolve11(gitRoot, normalized);
2320
+ }
2321
+ return existsSync9(candidate) ? candidate : "";
2322
+ }
2323
+ function normalizeGithubRepoNameWithOwner(value) {
2324
+ const normalized = value.trim();
2325
+ if (!normalized) {
2326
+ return "";
2327
+ }
2328
+ const scpMatch = normalized.match(/^(?:ssh:\/\/)?git@github\.com[:/](.+?)(?:\.git)?$/i);
2329
+ if (scpMatch?.[1]) {
2330
+ return scpMatch[1].replace(/^\/+|\/+$/g, "");
2331
+ }
2332
+ const httpMatch = normalized.match(/^https?:\/\/github\.com\/(.+?)(?:\.git)?(?:\/)?$/i);
2333
+ if (httpMatch?.[1]) {
2334
+ return httpMatch[1].replace(/^\/+|\/+$/g, "");
2335
+ }
2336
+ const bareMatch = normalized.match(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/);
2337
+ return bareMatch ? bareMatch[0] : "";
2338
+ }
2339
+ function ghRepoArgs(repoNameWithOwner) {
2340
+ return repoNameWithOwner ? ["-R", repoNameWithOwner] : [];
2341
+ }
2342
+ function withGhRepo(command, repoNameWithOwner) {
2343
+ if (!repoNameWithOwner || command.length < 3) {
2344
+ return command;
2345
+ }
2346
+ return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
2347
+ }
2348
+ function inferProjectBase(projectRoot, fallback) {
2349
+ const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
2350
+ if (containing.exitCode !== 0) {
2351
+ return fallback;
2352
+ }
2353
+ const candidates = containing.stdout.split(/\r?\n/).map((line) => line.replace(/^\*/, "").trim()).filter(Boolean).map((line) => line.replace(/^origin\//, "")).filter((line) => line !== "HEAD" && !line.startsWith("rig/"));
2354
+ return candidates[0] || fallback;
2355
+ }
2356
+ function currentGithubLogin(repoRoot) {
2357
+ const gh = resolveGithubCliBinary(repoRoot);
2358
+ if (!gh) {
2359
+ return "";
2360
+ }
2361
+ const result = runCapture2([gh, "api", "user", "--jq", ".login"], repoRoot);
2362
+ return result.exitCode === 0 ? result.stdout.trim() : "";
2363
+ }
2364
+ function collectPrChangedFiles(projectRoot, repoRoot, baseRef, branchRef) {
2365
+ const repoNameWithOwner = resolveRepoNameWithOwner(projectRoot, repoRoot);
2366
+ const remoteName = refreshRemoteBaseRef(projectRoot, repoRoot, baseRef, repoNameWithOwner);
2367
+ const hasRemoteBase = remoteName ? runCapture2(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", `${remoteName}/${baseRef}`), projectRoot).exitCode === 0 : false;
2368
+ const hasLocalBase = runCapture2(gitCmd(projectRoot, repoRoot, "rev-parse", "--verify", "--quiet", baseRef), projectRoot).exitCode === 0;
2369
+ let changed = "";
2370
+ if (hasRemoteBase) {
2371
+ changed = runCapture2(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `${remoteName}/${baseRef}...${branchRef}`), projectRoot).stdout;
2372
+ } else if (hasLocalBase) {
2373
+ changed = runCapture2(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `${baseRef}...${branchRef}`), projectRoot).stdout;
2374
+ } else {
2375
+ const fallback = runCapture2(gitCmd(projectRoot, repoRoot, "diff", "--name-only", `HEAD~1..${branchRef}`), projectRoot);
2376
+ changed = fallback.exitCode === 0 ? fallback.stdout : runCapture2(gitCmd(projectRoot, repoRoot, "diff", "--name-only"), projectRoot).stdout;
2377
+ }
2378
+ return changed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 60);
2379
+ }
2380
+ function inferReviewerFromChangedFiles(projectRoot, repoRoot, baseRef, branchRef) {
2381
+ const repoNameWithOwner = resolveRepoNameWithOwner(projectRoot, repoRoot);
2382
+ if (!repoNameWithOwner) {
2383
+ return "";
2384
+ }
2385
+ const actorLogin = currentGithubLogin(repoRoot);
2386
+ const changedFiles = collectPrChangedFiles(projectRoot, repoRoot, baseRef, branchRef);
2387
+ if (changedFiles.length === 0) {
2388
+ return "";
2389
+ }
2390
+ const counts = new Map;
2391
+ for (const path of changedFiles) {
2392
+ const result = runCapture2([
2393
+ resolveGithubCliBinary(repoRoot) || "gh",
2394
+ "api",
2395
+ `repos/${repoNameWithOwner}/commits`,
2396
+ "-f",
2397
+ `path=${path}`,
2398
+ "-f",
2399
+ `sha=${baseRef}`,
2400
+ "-f",
2401
+ "per_page=1",
2402
+ "--jq",
2403
+ ".[0].author.login // empty"
2404
+ ], repoRoot);
2405
+ const author = result.exitCode === 0 ? result.stdout.trim() : "";
2406
+ if (!author || author === actorLogin) {
2407
+ continue;
2408
+ }
2409
+ counts.set(author, (counts.get(author) || 0) + 1);
2410
+ }
2411
+ let best = "";
2412
+ let max = -1;
2413
+ for (const [author, count] of counts.entries()) {
2414
+ if (count > max || count === max && author < best) {
2415
+ best = author;
2416
+ max = count;
2417
+ }
2418
+ }
2419
+ return best;
2420
+ }
2421
+ function snapshotRepo(projectRoot, label, repo) {
2422
+ if (!existsSync9(resolve11(repo, ".git"))) {
2423
+ return [`## ${label}`, `repo: ${repo}`, "status: unavailable", ""];
2424
+ }
2425
+ const status = runCapture2(gitCmd(projectRoot, repo, "status", "--short"), projectRoot).stdout.trim();
2426
+ const branch = branchName(projectRoot, repo);
2427
+ let head;
2428
+ const nativeHead = nativeHeadOid(repo);
2429
+ if (nativeHead !== null) {
2430
+ head = nativeHead;
2431
+ } else {
2432
+ head = runCapture2(gitCmd(projectRoot, repo, "rev-parse", "HEAD"), projectRoot).stdout.trim();
2433
+ }
2434
+ return [
2435
+ `## ${label}`,
2436
+ `repo: ${repo}`,
2437
+ `branch: ${branch}`,
2438
+ `head: ${head}`,
2439
+ "status:",
2440
+ status || "(clean)",
2441
+ ""
2442
+ ];
2443
+ }
2444
+ function commitRepo(projectRoot, repo, label, message, allowEmpty, scoped, files, changedFilesManifest) {
2445
+ if (!existsSync9(resolve11(repo, ".git"))) {
2446
+ console.log(`Skipping ${label}: repo not available (${repo})`);
2447
+ return;
2448
+ }
2449
+ const scopedFiles = (files || []).filter(Boolean).filter((file) => !pathResolvesBeyondSymlink(repo, file));
2450
+ const repoChanges = changeCount(projectRoot, repo);
2451
+ if (scopedFiles.length === 0 && repoChanges === 0 && !allowEmpty) {
2452
+ console.log(`Skipping ${label}: no changes to commit.`);
2453
+ return;
2454
+ }
2455
+ const addArgs = buildStageAddArgs(repo, scopedFiles, scoped);
2456
+ if (addArgs) {
2457
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, ...addArgs), `Failed to stage changes for ${label}`);
2458
+ }
2459
+ const stagedChanges = stagedChangeCount(projectRoot, repo);
2460
+ if (stagedChanges === 0) {
2461
+ if (allowEmpty) {
2462
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, "commit", "--allow-empty", "-m", message), `Failed to commit ${label}`);
2463
+ console.log(`Committed ${label}: ${message}`);
2464
+ return;
2465
+ }
2466
+ if (scoped && repoChanges > 0) {
2467
+ const manifestHint = changedFilesManifest ? ` Refresh ${changedFilesManifest} and retry, or use --all/--unscoped intentionally.` : "";
2468
+ throw new Error(`Scoped commit for ${label} resolved no stageable files.${manifestHint}`);
2469
+ }
2470
+ console.log(`Skipping ${label}: no stageable changes to commit.`);
2471
+ return;
2472
+ }
2473
+ runOrThrow(projectRoot, gitCmd(projectRoot, repo, "commit", ...allowEmpty ? ["--allow-empty"] : [], "-m", message), `Failed to commit ${label}`);
2474
+ console.log(`Committed ${label}: ${message}`);
2475
+ }
2476
+ function readChangedFilesManifest(projectRoot, taskId) {
2477
+ const manifestPath = resolve11(artifactDirForId(projectRoot, taskId), "changed-files.txt");
2478
+ if (!existsSync9(manifestPath)) {
2479
+ return [];
2480
+ }
2481
+ const files = readFileSync7(manifestPath, "utf-8").split(/\r?\n/).map((line) => normalizeChangedFilePath2(line)).filter(Boolean);
2482
+ return [...new Set(files)];
2483
+ }
2484
+ function refreshChangedFilesManifest(projectRoot, taskId) {
2485
+ const manifestPath = resolve11(artifactDirForId(projectRoot, taskId), "changed-files.txt");
2486
+ mkdirSync5(dirname7(manifestPath), { recursive: true });
2487
+ const changedFiles = changedFilesForTask(projectRoot, taskId, true);
2488
+ writeFileSync5(manifestPath, `${changedFiles.join(`
2489
+ `)}
2490
+ `, "utf-8");
2491
+ return manifestPath;
2492
+ }
2493
+ function normalizeChangedFilePath2(file) {
2494
+ return file.trim().replace(/\\/g, "/").replace(/^\.\//, "");
2495
+ }
2496
+ function buildStageAddArgs(repoRoot, files, scoped) {
2497
+ if (files.length > 0) {
2498
+ return ["add", "--", ...files];
2499
+ }
2500
+ if (scoped) {
2501
+ return null;
2502
+ }
2503
+ return ["add", "-A", "--", ".", ...stageExcludePathspecs(repoRoot)];
2504
+ }
2505
+ function resolveScopedStageFilesForRepo(projectRoot, repoRoot, taskId, files) {
2506
+ const resolvedManifestFiles = resolveScopedFilesForRepo(projectRoot, repoRoot, files);
2507
+ if (resolvedManifestFiles.length > 0 || !taskId) {
2508
+ return resolvedManifestFiles;
2509
+ }
2510
+ return resolveChangedTaskArtifactFiles(projectRoot, repoRoot, taskId);
2511
+ }
2512
+ function resolveScopedFilesForRepo(projectRoot, repoRoot, files) {
2513
+ const resolvedFiles = [];
2514
+ const seen = new Set;
2515
+ for (const file of files) {
2516
+ const candidate = resolveScopedRepoPath(repoRoot, file);
2517
+ if (!candidate || seen.has(candidate) || pathResolvesBeyondSymlink(repoRoot, candidate)) {
2518
+ continue;
2519
+ }
2520
+ if (!repoHasPathChange(projectRoot, repoRoot, candidate)) {
2521
+ continue;
2522
+ }
2523
+ seen.add(candidate);
2524
+ resolvedFiles.push(candidate);
2525
+ }
2526
+ return resolvedFiles;
2527
+ }
2528
+ function resolveChangedTaskArtifactFiles(projectRoot, repoRoot, taskId) {
2529
+ const artifactPrefix = `artifacts/${taskId}/`;
2530
+ const resolvedFiles = [];
2531
+ const seen = new Set;
2532
+ for (const file of collectRepoPendingFiles(projectRoot, repoRoot)) {
2533
+ if (!file.startsWith(artifactPrefix)) {
2534
+ continue;
2535
+ }
2536
+ const artifactRelativePath = file.slice(artifactPrefix.length);
2537
+ if (!TASK_ARTIFACT_STAGE_FALLBACK.has(artifactRelativePath)) {
2538
+ continue;
2539
+ }
2540
+ if (seen.has(file) || pathResolvesBeyondSymlink(repoRoot, file)) {
2541
+ continue;
2542
+ }
2543
+ seen.add(file);
2544
+ resolvedFiles.push(file);
2545
+ }
2546
+ return resolvedFiles.sort();
2547
+ }
2548
+ function collectRepoPendingFiles(projectRoot, repoRoot) {
2549
+ const pending = nativePendingFiles(repoRoot);
2550
+ if (pending !== null) {
2551
+ if (pending.length > 0) {
2552
+ return [...new Set(pending.map((f) => normalizeChangedFilePath2(f.path)).filter(Boolean))].sort();
2553
+ }
2554
+ }
2555
+ const files = new Set;
2556
+ for (const args of [
2557
+ ["diff", "--name-only"],
2558
+ ["diff", "--cached", "--name-only"],
2559
+ ["ls-files", "--others", "--exclude-standard"]
2560
+ ]) {
2561
+ const result = runCapture2(gitCmd(projectRoot, repoRoot, ...args), projectRoot);
2562
+ if (result.exitCode !== 0) {
2563
+ continue;
2564
+ }
2565
+ for (const line of result.stdout.split(/\r?\n/)) {
2566
+ const normalized = normalizeChangedFilePath2(line);
2567
+ if (!normalized) {
2568
+ continue;
2569
+ }
2570
+ files.add(normalized);
2571
+ }
2572
+ }
2573
+ return [...files].sort();
2574
+ }
2575
+ function resolveScopedRepoPath(repoRoot, file) {
2576
+ const normalized = normalizeChangedFilePath2(file);
2577
+ if (!normalized) {
2578
+ return "";
2579
+ }
2580
+ const rules = getScopeRules();
2581
+ if (rules?.stripPrefixes) {
2582
+ let result = normalized;
2583
+ for (const prefix of rules.stripPrefixes) {
2584
+ if (result.startsWith(prefix)) {
2585
+ result = result.slice(prefix.length);
2586
+ }
2587
+ }
2588
+ return result;
2589
+ }
2590
+ return normalized;
2591
+ }
2592
+ function repoHasPathChange(projectRoot, repoRoot, relativePath) {
2593
+ const native = nativeFileHasChanges(repoRoot, relativePath);
2594
+ if (native !== null) {
2595
+ return native;
2596
+ }
2597
+ const result = runCapture2(gitCmd(projectRoot, repoRoot, "status", "--short", "--", relativePath), projectRoot);
2598
+ return result.exitCode === 0 && result.stdout.trim().length > 0;
2599
+ }
2600
+ function stageExcludePathspecs(repoRoot) {
2601
+ const patterns = existsSync9(resolve11(repoRoot, ".rig", "task-config.json")) ? [...TASK_RUNTIME_STAGE_EXCLUDES, ...GENERATED_STAGE_EXCLUDES] : [".rig/**", ...GENERATED_STAGE_EXCLUDES];
2602
+ return patterns.map((pattern) => `:(glob,exclude)${pattern}`);
2603
+ }
2604
+ function pathResolvesBeyondSymlink(repoRoot, relativePath) {
2605
+ const parts = relativePath.split("/").filter(Boolean);
2606
+ if (parts.length <= 1) {
2607
+ return false;
2608
+ }
2609
+ let current = repoRoot;
2610
+ for (let index = 0;index < parts.length - 1; index += 1) {
2611
+ current = resolve11(current, parts[index]);
2612
+ try {
2613
+ if (lstatSync(current).isSymbolicLink()) {
2614
+ return true;
2615
+ }
2616
+ } catch {
2617
+ return false;
2618
+ }
2619
+ }
2620
+ return false;
2621
+ }
2622
+ function printRepoStatus(projectRoot, label, repo, expectedBranch) {
2623
+ if (!existsSync9(resolve11(repo, ".git"))) {
2624
+ console.log(`${label}: unavailable (${repo})`);
2625
+ return;
2626
+ }
2627
+ const branch = branchName(projectRoot, repo);
2628
+ const changes = changeCount(projectRoot, repo);
2629
+ console.log(`${label}:`);
2630
+ console.log(` branch: ${branch || "unknown"}`);
2631
+ console.log(` changed files: ${changes}`);
2632
+ if (expectedBranch && label !== "project-rig" && branch !== expectedBranch) {
2633
+ console.log(` warning: branch mismatch (expected ${expectedBranch})`);
2634
+ }
2635
+ }
2636
+ function resolveTaskBranchId(projectRoot, taskId) {
2637
+ if (/^bd-[a-z0-9-]+$/.test(taskId)) {
2638
+ return taskId;
2639
+ }
2640
+ const normalizedTaskId = lookupTask(projectRoot, taskId);
2641
+ if (normalizedTaskId) {
2642
+ return normalizedTaskId;
2643
+ }
2644
+ const currentTask = currentTaskId(projectRoot);
2645
+ if (currentTask && currentTask === taskId) {
2646
+ return currentTask;
2647
+ }
2648
+ const runtimeIdFromEnv = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
2649
+ if (runtimeIdFromEnv.startsWith("task-") && runtimeIdFromEnv.length > "task-".length) {
2650
+ return runtimeIdFromEnv.slice("task-".length);
2651
+ }
2652
+ try {
2653
+ const runtimeIdFromContext = loadRuntimeContextFromEnv()?.runtimeId || "";
2654
+ if (runtimeIdFromContext.startsWith("task-") && runtimeIdFromContext.length > "task-".length) {
2655
+ return runtimeIdFromContext.slice("task-".length);
2656
+ }
2657
+ } catch {}
2658
+ const artifactDir = artifactDirForId(projectRoot, taskId);
2659
+ if (existsSync9(artifactDir)) {
2660
+ return taskId;
2661
+ }
2662
+ throw new Error(`Unknown task id: ${taskId}`);
2663
+ }
2664
+ function branchName(projectRoot, repo) {
2665
+ const native = nativeBranchName(repo);
2666
+ if (native !== null) {
2667
+ return native;
2668
+ }
2669
+ return runCapture2(gitCmd(projectRoot, repo, "rev-parse", "--abbrev-ref", "HEAD"), projectRoot).stdout.trim();
2670
+ }
2671
+ function changeCount(projectRoot, repo) {
2672
+ const native = nativeChangeCount(repo);
2673
+ if (native !== null) {
2674
+ return native;
2675
+ }
2676
+ const status = runCapture2(gitCmd(projectRoot, repo, "status", "--short"), projectRoot).stdout.trim();
2677
+ return status ? status.split(/\r?\n/).filter(Boolean).length : 0;
2678
+ }
2679
+ function stagedChangeCount(projectRoot, repo) {
2680
+ const staged = runCapture2(gitCmd(projectRoot, repo, "diff", "--cached", "--name-only"), projectRoot).stdout.trim();
2681
+ return staged ? staged.split(/\r?\n/).filter(Boolean).length : 0;
2682
+ }
2683
+ function runOrThrow(projectRoot, command, errorPrefix) {
2684
+ const result = runCapture2(command, projectRoot);
2685
+ if (result.exitCode !== 0) {
2686
+ throw new Error(`${errorPrefix}:
2687
+ ${result.stderr || result.stdout}`);
2688
+ }
2689
+ }
2690
+ function runCapture2(command, cwd, projectRoot = cwd) {
2691
+ return runCapture(command, cwd, runtimeGitEnv(projectRoot));
2692
+ }
2693
+ function runtimeGitEnv(projectRoot) {
2694
+ const { ctx, runtimeRoot } = resolveRuntimeMetadata(projectRoot);
2695
+ const runtimeHome = runtimeRoot ? resolve11(runtimeRoot, "home") : "";
2696
+ const runtimeTmp = runtimeRoot ? resolve11(runtimeRoot, "tmp") : "";
2697
+ const runtimeCache = runtimeRoot ? resolve11(runtimeRoot, "cache") : "";
2698
+ const runtimeKnownHosts = runtimeHome ? resolve11(runtimeHome, ".ssh", "known_hosts") : "";
2699
+ const runtimeKey = runtimeHome ? resolve11(runtimeHome, ".ssh", "rig-agent-key") : "";
2700
+ const env = {};
2701
+ if (ctx?.workspaceDir) {
2702
+ env.PROJECT_RIG_ROOT = projectRoot;
2703
+ env.RIG_TASK_WORKSPACE = ctx.workspaceDir;
2704
+ env.MONOREPO_ROOT = ctx.workspaceDir;
2705
+ env.MONOREPO_MAIN_ROOT = resolveMonorepoRoot2(projectRoot);
2706
+ } else if (projectRoot) {
2707
+ env.PROJECT_RIG_ROOT = projectRoot;
2708
+ }
2709
+ if (runtimeRoot) {
2710
+ env.RIG_RUNTIME_HOME = runtimeRoot;
2711
+ }
2712
+ if (runtimeHome && existsSync9(runtimeHome)) {
2713
+ env.HOME = runtimeHome;
2714
+ env.OPENSSL_CONF = ensureRuntimeOpenSslConfig(runtimeHome);
2715
+ }
2716
+ if (runtimeTmp && existsSync9(runtimeTmp)) {
2717
+ env.TMPDIR = runtimeTmp;
2718
+ }
2719
+ if (runtimeCache && existsSync9(runtimeCache)) {
2720
+ env.XDG_CACHE_HOME = runtimeCache;
2721
+ }
2722
+ const workspaceSecrets = loadDotEnvSecrets(ctx?.workspaceDir || projectRoot, process.env);
2723
+ for (const [key, value] of Object.entries(resolveRuntimeSecrets(process.env, workspaceSecrets))) {
2724
+ if (key === "GITHUB_SSH_KEY" || !value) {
2725
+ continue;
2726
+ }
2727
+ env[key] = value;
2728
+ }
2729
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
2730
+ env.GITHUB_TOKEN = env.GH_TOKEN;
2731
+ }
2732
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
2733
+ env.GH_TOKEN = env.GITHUB_TOKEN;
2734
+ }
2735
+ if (!env.GREPTILE_GITHUB_TOKEN && env.GITHUB_TOKEN) {
2736
+ env.GREPTILE_GITHUB_TOKEN = env.GITHUB_TOKEN;
2737
+ }
2738
+ const persistedSecrets = loadPersistedRuntimeSecrets(runtimeRoot);
2739
+ for (const [key, value] of Object.entries(persistedSecrets)) {
2740
+ if (!value)
2741
+ continue;
2742
+ if (!env[key]) {
2743
+ env[key] = value;
2744
+ }
2745
+ }
2746
+ if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
2747
+ env.GITHUB_TOKEN = env.GH_TOKEN;
2748
+ }
2749
+ if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
2750
+ env.GH_TOKEN = env.GITHUB_TOKEN;
2751
+ }
2752
+ if (runtimeKnownHosts && existsSync9(runtimeKnownHosts)) {
2753
+ const sshParts = [
2754
+ "ssh",
2755
+ `-o UserKnownHostsFile="${runtimeKnownHosts}"`,
2756
+ "-o StrictHostKeyChecking=yes",
2757
+ "-F /dev/null"
2758
+ ];
2759
+ if (runtimeKey && existsSync9(runtimeKey)) {
2760
+ sshParts.splice(1, 0, `-i "${runtimeKey}"`, "-o IdentitiesOnly=yes");
2761
+ }
2762
+ env.GIT_SSH_COMMAND = sshParts.join(" ");
2763
+ } else if (process.env.GIT_SSH_COMMAND?.trim()) {
2764
+ env.GIT_SSH_COMMAND = process.env.GIT_SSH_COMMAND;
2765
+ }
2766
+ return Object.keys(env).length > 0 ? env : undefined;
2767
+ }
2768
+ function loadPersistedRuntimeSecrets(runtimeRoot) {
2769
+ if (!runtimeRoot) {
2770
+ return {};
2771
+ }
2772
+ const path = resolve11(runtimeRoot, "runtime-secrets.json");
2773
+ if (!existsSync9(path)) {
2774
+ return {};
2775
+ }
2776
+ try {
2777
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
2778
+ const entries = Object.entries(parsed).filter((entry) => typeof entry[1] === "string");
2779
+ return Object.fromEntries(entries);
2780
+ } catch {
2781
+ return {};
2782
+ }
2783
+ }
2784
+ function ensureRuntimeOpenSslConfig(runtimeHome) {
2785
+ const sslDir = resolve11(runtimeHome, ".ssl");
2786
+ const sslConfig = resolve11(sslDir, "openssl.cnf");
2787
+ if (!existsSync9(sslDir)) {
2788
+ mkdirSync5(sslDir, { recursive: true });
2789
+ }
2790
+ if (!existsSync9(sslConfig)) {
2791
+ writeFileSync5(sslConfig, `# Rig runtime OpenSSL config placeholder
2792
+ `);
2793
+ }
2794
+ return sslConfig;
2795
+ }
2796
+ function resolveRuntimeMetadata(projectRoot) {
2797
+ const contextFile = process.env.RIG_RUNTIME_CONTEXT_FILE?.trim();
2798
+ const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
2799
+ let ctx = loadRuntimeContextFromEnv();
2800
+ if (runtimeHome) {
2801
+ return {
2802
+ ctx,
2803
+ runtimeRoot: runtimeHome
2804
+ };
2805
+ }
2806
+ if (contextFile) {
2807
+ return {
2808
+ ctx,
2809
+ runtimeRoot: dirname7(resolve11(contextFile))
2810
+ };
2811
+ }
2812
+ const inferredContextFile = findRuntimeContextFile2(projectRoot);
2813
+ if (existsSync9(inferredContextFile)) {
2814
+ try {
2815
+ ctx = loadRuntimeContext(inferredContextFile);
2816
+ } catch {}
2817
+ return {
2818
+ ctx,
2819
+ runtimeRoot: dirname7(inferredContextFile)
2820
+ };
2821
+ }
2822
+ return { ctx, runtimeRoot: "" };
2823
+ }
2824
+ function findRuntimeContextFile2(startPath) {
2825
+ let current = resolve11(startPath);
2826
+ while (true) {
2827
+ const candidate = resolve11(current, "runtime-context.json");
2828
+ if (existsSync9(candidate)) {
2829
+ return candidate;
2830
+ }
2831
+ const parent = dirname7(current);
2832
+ if (parent === current) {
2833
+ return "";
2834
+ }
2835
+ current = parent;
2836
+ }
2837
+ }
2838
+ var __testOnly = {
2839
+ buildStageAddArgs,
2840
+ refreshChangedFilesManifest,
2841
+ readChangedFilesManifest,
2842
+ resolveChangedTaskArtifactFiles,
2843
+ resolveScopedStageFilesForRepo,
2844
+ resolveScopedFilesForRepo,
2845
+ stageExcludePathspecs
2846
+ };
2847
+ export {
2848
+ shouldScopeGitCommit,
2849
+ resolveTaskBranchRef,
2850
+ readPrMetadata,
2851
+ gitSyncBranch,
2852
+ gitStatus,
2853
+ gitSnapshot,
2854
+ gitPreflight,
2855
+ gitOpenPr,
2856
+ gitMergePr,
2857
+ gitCommit,
2858
+ gitChanged,
2859
+ __testOnly
2860
+ };