@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,1469 @@
1
+ // @bun
2
+ // packages/runtime/src/control-plane/state-sync/write.ts
3
+ import { isDeepStrictEqual } from "util";
4
+
5
+ // packages/runtime/src/control-plane/native/git-native.ts
6
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
7
+ import { tmpdir } from "os";
8
+ import { dirname, isAbsolute, resolve } from "path";
9
+ import { createHash } from "crypto";
10
+ function isTextTreeCommitUpdate(update) {
11
+ return typeof update.content === "string";
12
+ }
13
+ var sharedGitNativeOutputDir = resolve(tmpdir(), "rig-native");
14
+ var sharedGitNativeOutputPath = resolve(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
15
+ var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
16
+ function temporaryGitBinaryOutputPath(outputPath) {
17
+ const suffix = process.platform === "win32" ? ".exe" : "";
18
+ return resolve(dirname(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
19
+ }
20
+ function publishGitBinary(tempOutputPath, outputPath) {
21
+ try {
22
+ renameSync(tempOutputPath, outputPath);
23
+ } catch (error) {
24
+ if (process.platform === "win32" && existsSync(outputPath)) {
25
+ rmSync(outputPath, { force: true });
26
+ renameSync(tempOutputPath, outputPath);
27
+ return;
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+ function runtimeRigGitFileName() {
33
+ return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
34
+ }
35
+ function rigGitSourceCandidates() {
36
+ const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
37
+ const cwd = process.cwd()?.trim() || "";
38
+ const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
39
+ const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
40
+ const moduleRelativeSource = resolve(import.meta.dir, "../../../native/rig-git.zig");
41
+ return [...new Set([
42
+ process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
43
+ moduleRelativeSource,
44
+ projectRoot ? resolve(projectRoot, "packages/runtime/native/rig-git.zig") : "",
45
+ hostProjectRoot ? resolve(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
46
+ cwd ? resolve(cwd, "packages/runtime/native/rig-git.zig") : "",
47
+ execDir ? resolve(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
48
+ execDir ? resolve(execDir, "..", "native", "rig-git.zig") : ""
49
+ ].filter(Boolean))];
50
+ }
51
+ function nativePackageBinaryCandidates(fromDir, fileName) {
52
+ const candidates = [];
53
+ let cursor = resolve(fromDir);
54
+ for (let index = 0;index < 8; index += 1) {
55
+ candidates.push(resolve(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve(cursor, "native", fileName), resolve(cursor, "native", "bin", fileName));
56
+ const parent = dirname(cursor);
57
+ if (parent === cursor)
58
+ break;
59
+ cursor = parent;
60
+ }
61
+ return candidates;
62
+ }
63
+ function rigGitBinaryCandidates() {
64
+ const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
65
+ const fileName = runtimeRigGitFileName();
66
+ const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
67
+ return [...new Set([
68
+ explicit,
69
+ ...nativePackageBinaryCandidates(import.meta.dir, fileName),
70
+ execDir ? resolve(execDir, fileName) : "",
71
+ execDir ? resolve(execDir, "..", fileName) : "",
72
+ execDir ? resolve(execDir, "..", "bin", fileName) : "",
73
+ sharedGitNativeOutputPath
74
+ ].filter(Boolean))];
75
+ }
76
+ function resolveGitSourcePath() {
77
+ for (const candidate of rigGitSourceCandidates()) {
78
+ if (candidate && existsSync(candidate)) {
79
+ return candidate;
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ function resolveGitBinaryPath() {
85
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
86
+ return null;
87
+ }
88
+ for (const candidate of rigGitBinaryCandidates()) {
89
+ if (candidate && existsSync(candidate)) {
90
+ return candidate;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ function preferredGitBinaryOutputPath() {
96
+ const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
97
+ return explicit || sharedGitNativeOutputPath;
98
+ }
99
+ function binarySupportsTrackerCommandsSync(binaryPath) {
100
+ try {
101
+ const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
102
+ stdout: "pipe",
103
+ stderr: "pipe"
104
+ });
105
+ const stdout = probe.stdout.toString().trim();
106
+ const stderr = probe.stderr.toString().trim();
107
+ if (stdout.includes('"error":"unknown command"')) {
108
+ return false;
109
+ }
110
+ return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ function nativeBuildManifestPath(outputPath) {
116
+ return `${outputPath}.build-manifest.json`;
117
+ }
118
+ function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
119
+ if (!existsSync(manifestPath)) {
120
+ return false;
121
+ }
122
+ try {
123
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
124
+ return manifest.version === 1 && manifest.buildKey === buildKey;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+ function sha256FileSync(path) {
130
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
131
+ }
132
+ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
133
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
134
+ throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
135
+ }
136
+ const sourcePath = resolveGitSourcePath();
137
+ if (!sourcePath) {
138
+ const binaryPath = resolveGitBinaryPath();
139
+ if (binaryPath) {
140
+ return binaryPath;
141
+ }
142
+ throw new Error("rig-git.zig source file not found.");
143
+ }
144
+ const zigBinary = Bun.which("zig");
145
+ if (!zigBinary) {
146
+ throw new Error("zig is required to build native Rig git tools.");
147
+ }
148
+ mkdirSync(dirname(outputPath), { recursive: true });
149
+ const sourceDigest = sha256FileSync(sourcePath);
150
+ const buildKey = JSON.stringify({
151
+ version: 1,
152
+ zigBinary,
153
+ platform: process.platform,
154
+ arch: process.arch,
155
+ sourcePath,
156
+ sourceDigest
157
+ });
158
+ const manifestPath = nativeBuildManifestPath(outputPath);
159
+ const needsBuild = !existsSync(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
160
+ if (!needsBuild) {
161
+ chmodSync(outputPath, 493);
162
+ return outputPath;
163
+ }
164
+ const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
165
+ const build = Bun.spawnSync([
166
+ zigBinary,
167
+ "build-exe",
168
+ sourcePath,
169
+ "-O",
170
+ "ReleaseFast",
171
+ `-femit-bin=${tempOutputPath}`
172
+ ], {
173
+ cwd: dirname(sourcePath),
174
+ stdout: "pipe",
175
+ stderr: "pipe"
176
+ });
177
+ if (build.exitCode !== 0 || !existsSync(tempOutputPath)) {
178
+ const stderr = build.stderr.toString().trim();
179
+ const stdout = build.stdout.toString().trim();
180
+ const details = [stderr, stdout].filter(Boolean).join(`
181
+ `);
182
+ throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
183
+ }
184
+ chmodSync(tempOutputPath, 493);
185
+ if (existsSync(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
186
+ rmSync(tempOutputPath, { force: true });
187
+ chmodSync(outputPath, 493);
188
+ return outputPath;
189
+ }
190
+ publishGitBinary(tempOutputPath, outputPath);
191
+ if (!binarySupportsTrackerCommandsSync(outputPath)) {
192
+ rmSync(outputPath, { force: true });
193
+ throw new Error("Failed to build native Rig git tools: tracker command probe failed");
194
+ }
195
+ writeFileSync(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
196
+ `, "utf8");
197
+ return outputPath;
198
+ }
199
+ function runGitNative(command, args) {
200
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
201
+ return { ok: false, error: "rig-git native disabled" };
202
+ }
203
+ const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
204
+ let binaryPath = null;
205
+ if (trackerCommand) {
206
+ try {
207
+ binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : String(error);
210
+ if (message.includes("rig-git.zig source file not found")) {
211
+ return { ok: false, error: "rig-git binary not found" };
212
+ }
213
+ return { ok: false, error: message };
214
+ }
215
+ } else {
216
+ const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
217
+ binaryPath = explicitBinaryPath && existsSync(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
218
+ if (!binaryPath) {
219
+ try {
220
+ binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
221
+ } catch (error) {
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ if (message.includes("rig-git.zig source file not found")) {
224
+ return { ok: false, error: "rig-git binary not found" };
225
+ }
226
+ return { ok: false, error: message };
227
+ }
228
+ }
229
+ }
230
+ try {
231
+ const proc = Bun.spawnSync([binaryPath, command, ...args], {
232
+ stdout: "pipe",
233
+ stderr: "pipe",
234
+ env: process.env
235
+ });
236
+ if (proc.exitCode !== 0) {
237
+ const stdoutText = proc.stdout.toString().trim();
238
+ if (stdoutText) {
239
+ try {
240
+ const parsed = JSON.parse(stdoutText);
241
+ if (!parsed.ok) {
242
+ return parsed;
243
+ }
244
+ } catch {}
245
+ }
246
+ const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
247
+ return { ok: false, error: errText };
248
+ }
249
+ const output = proc.stdout.toString().trim();
250
+ return JSON.parse(output);
251
+ } catch (err) {
252
+ return { ok: false, error: String(err) };
253
+ }
254
+ }
255
+ function requireGitNative(command, args) {
256
+ const result = runGitNative(command, args);
257
+ if (!result.ok) {
258
+ throw new Error(`rig-git ${command} failed: ${result.error}`);
259
+ }
260
+ return result;
261
+ }
262
+ function requireGitNativeString(command, args) {
263
+ const result = requireGitNative(command, args);
264
+ if ("value" in result && typeof result.value === "string") {
265
+ return result.value;
266
+ }
267
+ throw new Error(`rig-git ${command} returned an unexpected result payload`);
268
+ }
269
+ function nativeFetchRef(repoPath, remote, branch) {
270
+ return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
271
+ }
272
+ function nativeReadBlobAtRef(repoPath, ref, path) {
273
+ const requestDir = resolve(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
274
+ mkdirSync(requestDir, { recursive: true });
275
+ const outputPath = resolve(requestDir, "blob.txt");
276
+ try {
277
+ requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
278
+ return readFileSync(outputPath, "utf8");
279
+ } finally {
280
+ rmSync(requestDir, { recursive: true, force: true });
281
+ }
282
+ }
283
+ function serializeTreeCommitUpdates(updates) {
284
+ return updates.map((update) => {
285
+ if (isTextTreeCommitUpdate(update)) {
286
+ return { path: update.path, kind: "text", content: update.content };
287
+ }
288
+ if (!isAbsolute(update.sourceFilePath)) {
289
+ throw new Error("tree commit binary updates require an absolute sourceFilePath");
290
+ }
291
+ return { path: update.path, kind: "file", sourceFilePath: update.sourceFilePath };
292
+ });
293
+ }
294
+ function buildTreeCommitUpdatesJson(updates) {
295
+ return `${JSON.stringify(serializeTreeCommitUpdates(updates), null, 2)}
296
+ `;
297
+ }
298
+ function nativeWriteTreeCommit(repoPath, baseRef, updates, message) {
299
+ const requestDir = resolve(sharedGitNativeOutputDir, "requests", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
300
+ mkdirSync(requestDir, { recursive: true });
301
+ const messagePath = resolve(requestDir, "message.txt");
302
+ const updatesPath = resolve(requestDir, "updates.json");
303
+ try {
304
+ writeFileSync(messagePath, message, "utf8");
305
+ writeFileSync(updatesPath, buildTreeCommitUpdatesJson(updates), "utf8");
306
+ return requireGitNativeString("write-tree-commit", [repoPath, baseRef, messagePath, updatesPath]);
307
+ } finally {
308
+ rmSync(requestDir, { recursive: true, force: true });
309
+ }
310
+ }
311
+ function nativePushRefWithLease(repoPath, localOid, remoteRef, expectedOldOid, remote = "origin") {
312
+ return requireGitNativeString("push-ref-with-lease", [
313
+ repoPath,
314
+ localOid,
315
+ remoteRef,
316
+ expectedOldOid,
317
+ remote
318
+ ]);
319
+ }
320
+
321
+ // packages/runtime/src/control-plane/state-sync/read.ts
322
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
323
+ import { resolve as resolve6 } from "path";
324
+
325
+ // packages/runtime/src/layout.ts
326
+ import { existsSync as existsSync2 } from "fs";
327
+ import { basename, dirname as dirname2, resolve as resolve2 } from "path";
328
+ function resolveMonorepoRoot(projectRoot) {
329
+ const normalizedProjectRoot = resolve2(projectRoot);
330
+ const explicit = process.env.MONOREPO_ROOT?.trim();
331
+ if (explicit) {
332
+ const explicitRoot = resolve2(explicit);
333
+ const explicitParent = dirname2(explicitRoot);
334
+ if (basename(explicitParent) === ".worktrees") {
335
+ const owner = dirname2(explicitParent);
336
+ const ownerHasGit = existsSync2(resolve2(owner, ".git"));
337
+ const ownerHasTaskConfig = existsSync2(resolve2(owner, ".rig", "task-config.json"));
338
+ const ownerHasRigConfig = existsSync2(resolve2(owner, "rig.config.ts"));
339
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
340
+ return owner;
341
+ }
342
+ throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
343
+ }
344
+ if (!existsSync2(resolve2(explicitRoot, ".git"))) {
345
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
346
+ }
347
+ const hasTaskConfig = existsSync2(resolve2(explicitRoot, ".rig", "task-config.json"));
348
+ const hasRigConfig = existsSync2(resolve2(explicitRoot, "rig.config.ts"));
349
+ if (!hasTaskConfig && !hasRigConfig) {
350
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
351
+ }
352
+ return explicitRoot;
353
+ }
354
+ const projectParent = dirname2(normalizedProjectRoot);
355
+ if (basename(projectParent) === ".worktrees") {
356
+ const worktreeOwner = dirname2(projectParent);
357
+ const ownerHasGit = existsSync2(resolve2(worktreeOwner, ".git"));
358
+ const ownerHasTaskConfig = existsSync2(resolve2(worktreeOwner, ".rig", "task-config.json"));
359
+ const ownerHasRigConfig = existsSync2(resolve2(worktreeOwner, "rig.config.ts"));
360
+ if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
361
+ return worktreeOwner;
362
+ }
363
+ }
364
+ return normalizedProjectRoot;
365
+ }
366
+
367
+ // packages/runtime/src/control-plane/native/runtime-native.ts
368
+ import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
369
+ import { copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
370
+ import { tmpdir as tmpdir2 } from "os";
371
+ import { dirname as dirname3, resolve as resolve3 } from "path";
372
+ var sharedNativeRuntimeOutputDir = resolve3(tmpdir2(), "rig-native");
373
+ var sharedNativeRuntimeOutputPath = resolve3(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
374
+ var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
375
+ var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
376
+ async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
377
+ if (await buildNativeRuntimeLibrary(outputPath, options)) {
378
+ return outputPath;
379
+ }
380
+ return !options.force && existsSync3(outputPath) ? outputPath : null;
381
+ }
382
+ async function loadNativeRuntimeLibrary() {
383
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
384
+ return null;
385
+ }
386
+ for (const candidate of nativeRuntimeLibraryCandidates()) {
387
+ if (!candidate || !existsSync3(candidate)) {
388
+ continue;
389
+ }
390
+ const loaded = tryDlopenNativeRuntimeLibrary(candidate);
391
+ if (loaded) {
392
+ return loaded;
393
+ }
394
+ }
395
+ const builtLibraryPath = await ensureNativeRuntimeLibraryPath(sharedNativeRuntimeOutputPath, { force: true });
396
+ if (!builtLibraryPath) {
397
+ return null;
398
+ }
399
+ return tryDlopenNativeRuntimeLibrary(builtLibraryPath);
400
+ }
401
+ function nativePackageLibraryCandidates(fromDir, names) {
402
+ const candidates = [];
403
+ let cursor = resolve3(fromDir);
404
+ for (let index = 0;index < 8; index += 1) {
405
+ for (const name of names) {
406
+ candidates.push(resolve3(cursor, "native", `${process.platform}-${process.arch}`, name), resolve3(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve3(cursor, "native", name), resolve3(cursor, "native", "lib", name));
407
+ }
408
+ const parent = dirname3(cursor);
409
+ if (parent === cursor)
410
+ break;
411
+ cursor = parent;
412
+ }
413
+ return candidates;
414
+ }
415
+ function nativeRuntimeLibraryCandidates() {
416
+ const explicit = process.env.RIG_NATIVE_RUNTIME_LIB?.trim() || "";
417
+ const execDir = process.execPath?.trim() ? dirname3(process.execPath.trim()) : "";
418
+ const platformSpecific = `runtime-native-${process.platform}-${process.arch}.${suffix}`;
419
+ return [...new Set([
420
+ explicit,
421
+ ...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
422
+ execDir ? resolve3(execDir, colocatedNativeRuntimeFileName) : "",
423
+ execDir ? resolve3(execDir, platformSpecific) : "",
424
+ execDir ? resolve3(execDir, "..", colocatedNativeRuntimeFileName) : "",
425
+ execDir ? resolve3(execDir, "..", platformSpecific) : "",
426
+ execDir ? resolve3(execDir, "lib", colocatedNativeRuntimeFileName) : "",
427
+ execDir ? resolve3(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
428
+ sharedNativeRuntimeOutputPath
429
+ ].filter(Boolean))];
430
+ }
431
+ function resolveNativeRuntimeSourcePath() {
432
+ const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
433
+ if (explicit && existsSync3(explicit)) {
434
+ return explicit;
435
+ }
436
+ const bundled = resolve3(import.meta.dir, "../../../native/snapshot.zig");
437
+ return existsSync3(bundled) ? bundled : null;
438
+ }
439
+ async function buildNativeRuntimeLibrary(outputPath, options = {}) {
440
+ if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
441
+ return false;
442
+ }
443
+ const zigBinary = Bun.which("zig");
444
+ const sourcePath = resolveNativeRuntimeSourcePath();
445
+ if (!zigBinary || !sourcePath) {
446
+ return false;
447
+ }
448
+ const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
449
+ try {
450
+ mkdirSync2(dirname3(outputPath), { recursive: true });
451
+ const needsBuild = options.force === true || !existsSync3(outputPath) || statSync(sourcePath).mtimeMs > statSync(outputPath).mtimeMs;
452
+ if (!needsBuild) {
453
+ return true;
454
+ }
455
+ const build = Bun.spawn([
456
+ zigBinary,
457
+ "build-lib",
458
+ sourcePath,
459
+ "-dynamic",
460
+ "-O",
461
+ "ReleaseFast",
462
+ `-femit-bin=${tempOutputPath}`
463
+ ], {
464
+ cwd: import.meta.dir,
465
+ stdout: "pipe",
466
+ stderr: "pipe"
467
+ });
468
+ const exitCode = await build.exited;
469
+ if (exitCode !== 0 || !existsSync3(tempOutputPath)) {
470
+ rmSync2(tempOutputPath, { force: true });
471
+ return false;
472
+ }
473
+ renameSync2(tempOutputPath, outputPath);
474
+ return true;
475
+ } catch {
476
+ rmSync2(tempOutputPath, { force: true });
477
+ return false;
478
+ }
479
+ }
480
+ function tryDlopenNativeRuntimeLibrary(outputPath) {
481
+ try {
482
+ return dlopen(outputPath, {
483
+ rig_scope_match: {
484
+ args: ["ptr", "ptr"],
485
+ returns: "u8"
486
+ },
487
+ snapshot_capture: {
488
+ args: ["ptr", "u64", "ptr", "u64"],
489
+ returns: "ptr"
490
+ },
491
+ snapshot_delta: {
492
+ args: ["ptr", "ptr"],
493
+ returns: "ptr"
494
+ },
495
+ snapshot_store_delta: {
496
+ args: ["ptr", "ptr", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
497
+ returns: "ptr"
498
+ },
499
+ snapshot_inspect_delta: {
500
+ args: ["ptr", "u64"],
501
+ returns: "ptr"
502
+ },
503
+ snapshot_apply_delta: {
504
+ args: ["ptr", "u64", "ptr", "u64"],
505
+ returns: "ptr"
506
+ },
507
+ snapshot_release: {
508
+ args: ["ptr"],
509
+ returns: "void"
510
+ },
511
+ runtime_hash_file: {
512
+ args: ["ptr", "u64"],
513
+ returns: "ptr"
514
+ },
515
+ runtime_hash_tree: {
516
+ args: ["ptr", "u64"],
517
+ returns: "ptr"
518
+ },
519
+ runtime_prepare_paths: {
520
+ args: ["ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
521
+ returns: "ptr"
522
+ },
523
+ runtime_link_dependency_layer: {
524
+ args: ["ptr", "u64", "ptr", "u64"],
525
+ returns: "ptr"
526
+ },
527
+ runtime_scan_worktrees: {
528
+ args: ["ptr", "u64"],
529
+ returns: "ptr"
530
+ }
531
+ });
532
+ } catch {
533
+ return null;
534
+ }
535
+ }
536
+
537
+ // packages/runtime/src/control-plane/native/utils.ts
538
+ function resolveMonorepoRoot2(projectRoot) {
539
+ return resolveMonorepoRoot(projectRoot);
540
+ }
541
+ var scopeRegexCache = new Map;
542
+
543
+ // packages/runtime/src/control-plane/runtime/context.ts
544
+ var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
545
+
546
+ // packages/runtime/src/control-plane/state-sync/repo.ts
547
+ import { existsSync as existsSync5 } from "fs";
548
+ import { resolve as resolve5 } from "path";
549
+
550
+ // packages/runtime/src/control-plane/repos/layout.ts
551
+ import { existsSync as existsSync4 } from "fs";
552
+ import { basename as basename2, dirname as dirname4, join, resolve as resolve4 } from "path";
553
+
554
+ // packages/runtime/src/control-plane/repos/registry.ts
555
+ var MANAGED_REPOS = new Map;
556
+ function getManagedRepoEntry(repoId) {
557
+ const entry = MANAGED_REPOS.get(repoId);
558
+ if (!entry) {
559
+ throw new Error(`managed repo not registered: ${repoId}. Plugins contribute repos via RigPlugin.contributes.repoSources; ` + `make sure a plugin declares this id and the plugin host has been initialized.`);
560
+ }
561
+ return entry;
562
+ }
563
+ function listManagedRepoEntries() {
564
+ return Array.from(MANAGED_REPOS.values());
565
+ }
566
+
567
+ // packages/runtime/src/control-plane/repos/layout.ts
568
+ function resolveRepoStateDir(projectRoot) {
569
+ const normalizedProjectRoot = resolve4(projectRoot);
570
+ const projectParent = dirname4(normalizedProjectRoot);
571
+ if (basename2(projectParent) === ".worktrees") {
572
+ const ownerRoot = dirname4(projectParent);
573
+ const ownerHasRepoMarkers = existsSync4(resolve4(ownerRoot, ".git")) || existsSync4(resolve4(ownerRoot, ".rig", "state"));
574
+ if (ownerHasRepoMarkers) {
575
+ return resolve4(ownerRoot, ".rig", "state");
576
+ }
577
+ }
578
+ return resolve4(projectRoot, ".rig", "state");
579
+ }
580
+ function resolveManagedRepoLayout(projectRoot, repoId) {
581
+ const normalizedProjectRoot = resolve4(projectRoot);
582
+ const entry = getManagedRepoEntry(repoId);
583
+ const stateDir = resolveRepoStateDir(normalizedProjectRoot);
584
+ const metadataRelativePath = join("repos", entry.id);
585
+ const metadataRoot = resolve4(stateDir, metadataRelativePath);
586
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
587
+ const runsInsideTaskWorktree = runtimeWorkspace && resolve4(runtimeWorkspace) === normalizedProjectRoot || basename2(dirname4(normalizedProjectRoot)) === ".worktrees";
588
+ const isPrimaryManagedRepo = listManagedRepoEntries()[0]?.id === repoId;
589
+ const checkoutRoot = isPrimaryManagedRepo && runsInsideTaskWorktree ? resolveMonorepoRoot(normalizedProjectRoot) : entry.checkoutEnvVar && process.env[entry.checkoutEnvVar]?.trim() ? resolve4(process.env[entry.checkoutEnvVar].trim()) : resolve4(normalizedProjectRoot, entry.alias);
590
+ return {
591
+ projectRoot: normalizedProjectRoot,
592
+ repoId: entry.id,
593
+ alias: entry.alias,
594
+ defaultBranch: entry.defaultBranch,
595
+ remoteUrl: entry.remoteEnvVar && process.env[entry.remoteEnvVar]?.trim() ? process.env[entry.remoteEnvVar].trim() : entry.defaultRemoteUrl,
596
+ checkoutRoot,
597
+ worktreesRoot: resolve4(checkoutRoot, ".worktrees"),
598
+ stateDir,
599
+ metadataRoot,
600
+ metadataRelativePath,
601
+ mirrorRoot: resolve4(metadataRoot, "mirror.git"),
602
+ mirrorStatePath: resolve4(metadataRoot, "mirror-state.json"),
603
+ mirrorStateRelativePath: join(metadataRelativePath, "mirror-state.json")
604
+ };
605
+ }
606
+ function resolveMonorepoRepoLayout(projectRoot) {
607
+ const entries = listManagedRepoEntries();
608
+ if (entries.length === 0) {
609
+ throw new Error("resolveMonorepoRepoLayout: no managed repos registered. Either contribute one via " + "RigPlugin.contributes.repoSources (with defaultBranch set), or avoid calling this " + "function for projects where the project root IS the monorepo.");
610
+ }
611
+ const primary = entries[0];
612
+ return resolveManagedRepoLayout(projectRoot, primary.id);
613
+ }
614
+
615
+ // packages/runtime/src/control-plane/state-sync/repo.ts
616
+ function resolveTrackerRepoPath(projectRoot) {
617
+ const monorepoRoot = resolveMonorepoRoot2(projectRoot);
618
+ try {
619
+ const layout = resolveMonorepoRepoLayout(projectRoot);
620
+ if (existsSync5(resolve5(layout.mirrorRoot, "HEAD"))) {
621
+ return layout.mirrorRoot;
622
+ }
623
+ } catch {}
624
+ return monorepoRoot;
625
+ }
626
+
627
+ // packages/runtime/src/control-plane/state-sync/types.ts
628
+ var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
629
+ var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
630
+ "draft",
631
+ "open",
632
+ "ready",
633
+ "queued",
634
+ "in_progress",
635
+ "under_review",
636
+ "blocked",
637
+ "completed",
638
+ "cancelled"
639
+ ]);
640
+ function normalizeTaskLifecycleStatus(status) {
641
+ switch (status) {
642
+ case "draft":
643
+ case "open":
644
+ case "ready":
645
+ case "queued":
646
+ case "in_progress":
647
+ case "under_review":
648
+ case "blocked":
649
+ case "completed":
650
+ case "cancelled":
651
+ return status;
652
+ case "closed":
653
+ return "completed";
654
+ case "running":
655
+ return "in_progress";
656
+ case "failed":
657
+ return "ready";
658
+ default:
659
+ return null;
660
+ }
661
+ }
662
+ function normalizeTaskStateMetadataStatus(status) {
663
+ if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
664
+ return status;
665
+ }
666
+ switch (status) {
667
+ case "closed":
668
+ return "completed";
669
+ case "running":
670
+ return "in_progress";
671
+ case "failed":
672
+ return "ready";
673
+ default:
674
+ return;
675
+ }
676
+ }
677
+ function canonicalizeTaskStateMetadata(raw) {
678
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
679
+ return null;
680
+ }
681
+ const metadata = raw;
682
+ const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
683
+ const status = normalizeTaskStateMetadataStatus(metadata.status);
684
+ if (!status) {
685
+ return null;
686
+ }
687
+ return {
688
+ ...claimId ? { claimId } : {},
689
+ status,
690
+ ...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
691
+ ...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
692
+ ...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
693
+ ...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
694
+ ...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
695
+ ...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
696
+ ...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
697
+ ...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
698
+ ...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
699
+ ...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
700
+ };
701
+ }
702
+ function discardMismatchedTaskStateMetadata(input) {
703
+ input.taskId;
704
+ const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
705
+ if (!canonicalMetadata || !input.lifecycleStatus) {
706
+ return null;
707
+ }
708
+ const metadataStatus = canonicalMetadata.status ?? null;
709
+ if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
710
+ return null;
711
+ }
712
+ return canonicalMetadata;
713
+ }
714
+ function readTaskStateMetadataEnvelope(raw) {
715
+ if (!raw || typeof raw !== "object") {
716
+ return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
717
+ }
718
+ const envelope = raw;
719
+ const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
720
+ if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
721
+ return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
722
+ }
723
+ const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
724
+ const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
725
+ return {
726
+ schemaVersion,
727
+ supported: true,
728
+ baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
729
+ tasks
730
+ };
731
+ }
732
+
733
+ // packages/runtime/src/control-plane/state-sync/read.ts
734
+ var DEFAULT_READ_DEPS = {
735
+ fetchRef: nativeFetchRef,
736
+ readBlobAtRef: nativeReadBlobAtRef,
737
+ exists: existsSync6,
738
+ readFile: (path) => readFileSync2(path, "utf8")
739
+ };
740
+ function parseIssueStatus(rawStatus) {
741
+ const normalized = normalizeTaskLifecycleStatus(rawStatus);
742
+ return normalized ?? "unknown";
743
+ }
744
+ function parseIssueDependencies(raw) {
745
+ if (!Array.isArray(raw)) {
746
+ return [];
747
+ }
748
+ return raw.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry)).map((entry) => ({
749
+ issueId: typeof entry.issue_id === "string" && entry.issue_id.trim() ? entry.issue_id.trim() : null,
750
+ dependsOnId: typeof entry.depends_on_id === "string" && entry.depends_on_id.trim() ? entry.depends_on_id.trim() : null,
751
+ id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null,
752
+ type: typeof entry.type === "string" && entry.type.trim() ? entry.type.trim() : null
753
+ }));
754
+ }
755
+ function parseIssuesJsonl(raw) {
756
+ const issues = [];
757
+ for (const line of raw.split(/\r?\n/)) {
758
+ const trimmed = line.trim();
759
+ if (!trimmed) {
760
+ continue;
761
+ }
762
+ try {
763
+ const record = JSON.parse(trimmed);
764
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
765
+ if (!id) {
766
+ continue;
767
+ }
768
+ const rawStatus = typeof record.status === "string" && record.status.trim() ? record.status.trim() : null;
769
+ issues.push({
770
+ id,
771
+ title: typeof record.title === "string" && record.title.trim() ? record.title.trim() : null,
772
+ description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
773
+ acceptanceCriteria: typeof record.acceptance_criteria === "string" && record.acceptance_criteria.trim() ? record.acceptance_criteria.trim() : typeof record.acceptanceCriteria === "string" && record.acceptanceCriteria.trim() ? record.acceptanceCriteria.trim() : null,
774
+ issueType: typeof record.issue_type === "string" && record.issue_type.trim() ? record.issue_type.trim() : null,
775
+ status: parseIssueStatus(rawStatus),
776
+ rawStatus,
777
+ priority: typeof record.priority === "number" ? record.priority : null,
778
+ dependencies: parseIssueDependencies(record.dependencies)
779
+ });
780
+ } catch {}
781
+ }
782
+ return issues;
783
+ }
784
+ function parseTaskStateEnvelope(raw) {
785
+ if (!raw || !raw.trim()) {
786
+ return readTaskStateMetadataEnvelope(null);
787
+ }
788
+ try {
789
+ return readTaskStateMetadataEnvelope(JSON.parse(raw));
790
+ } catch {
791
+ return readTaskStateMetadataEnvelope(null);
792
+ }
793
+ }
794
+ function readRemoteBlobAtRef(deps, repoPath, ref, path, options) {
795
+ try {
796
+ return deps.readBlobAtRef(repoPath, ref, path);
797
+ } catch (error) {
798
+ if (options.required) {
799
+ throw error;
800
+ }
801
+ return null;
802
+ }
803
+ }
804
+ function shouldPreferLocalTrackerState(options) {
805
+ if (!options.allowLocalFallback) {
806
+ return false;
807
+ }
808
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
809
+ if (!runtimeWorkspace) {
810
+ return false;
811
+ }
812
+ if (process.env.RIG_TASK_RUNTIME_ID?.trim()) {
813
+ return true;
814
+ }
815
+ const runtimeContextPath = process.env[RUNTIME_CONTEXT_ENV]?.trim();
816
+ if (runtimeContextPath) {
817
+ return true;
818
+ }
819
+ return existsSync6(resolve6(runtimeWorkspace, ".rig", "runtime-context.json"));
820
+ }
821
+ function readLocalTrackerState(projectRoot, deps) {
822
+ const monorepoRoot = resolveMonorepoRoot2(projectRoot);
823
+ const issuesPath = resolve6(monorepoRoot, ".beads", "issues.jsonl");
824
+ const taskStatePath = resolve6(monorepoRoot, ".beads", "task-state.json");
825
+ return projectSyncedTrackerSnapshot({
826
+ source: "local",
827
+ issuesBaseOid: null,
828
+ issuesText: deps.exists(issuesPath) ? deps.readFile(issuesPath) : "",
829
+ taskStateBaseOid: null,
830
+ taskStateText: deps.exists(taskStatePath) ? deps.readFile(taskStatePath) : null
831
+ });
832
+ }
833
+ function projectSyncedTrackerSnapshot(input) {
834
+ if (input.source === "remote" && input.issuesBaseOid && input.taskStateBaseOid && input.issuesBaseOid !== input.taskStateBaseOid) {
835
+ throw new Error("Remote tracker files must be read from the same fetched base.");
836
+ }
837
+ return {
838
+ source: input.source,
839
+ baseOid: input.issuesBaseOid ?? input.taskStateBaseOid ?? null,
840
+ issues: parseIssuesJsonl(input.issuesText),
841
+ taskState: parseTaskStateEnvelope(input.taskStateText)
842
+ };
843
+ }
844
+ function readSyncedTrackerState(projectRoot, deps = {}, options = {}) {
845
+ const readDeps = { ...DEFAULT_READ_DEPS, ...deps };
846
+ const trackerRepoPath = resolveTrackerRepoPath(projectRoot);
847
+ if (shouldPreferLocalTrackerState(options)) {
848
+ return readLocalTrackerState(projectRoot, readDeps);
849
+ }
850
+ try {
851
+ const baseOid = readDeps.fetchRef(trackerRepoPath, "origin", "main");
852
+ return projectSyncedTrackerSnapshot({
853
+ source: "remote",
854
+ issuesBaseOid: baseOid,
855
+ issuesText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/issues.jsonl", {
856
+ required: true
857
+ }) ?? "",
858
+ taskStateBaseOid: baseOid,
859
+ taskStateText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/task-state.json", {
860
+ required: false
861
+ })
862
+ });
863
+ } catch (error) {
864
+ if (!options.allowLocalFallback) {
865
+ throw error;
866
+ }
867
+ return readLocalTrackerState(projectRoot, readDeps);
868
+ }
869
+ }
870
+
871
+ // packages/runtime/src/control-plane/state-sync/reconcile.ts
872
+ var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
873
+ function parseTimestamp(value) {
874
+ if (!value) {
875
+ return null;
876
+ }
877
+ const parsed = Date.parse(value);
878
+ return Number.isFinite(parsed) ? parsed : null;
879
+ }
880
+ function inProgressOwnershipMetadataIssue(metadata) {
881
+ if (!metadata?.claimId || !metadata.ownerId || !metadata.claimedAt || !metadata.lastEvidenceAt) {
882
+ return "missing-ownership-metadata";
883
+ }
884
+ if (parseTimestamp(metadata.claimedAt) == null || parseTimestamp(metadata.lastEvidenceAt) == null) {
885
+ return "invalid-ownership-timestamps";
886
+ }
887
+ return null;
888
+ }
889
+ function hasExplicitPullRequestEvidence(evidence) {
890
+ return evidence != null && Object.prototype.hasOwnProperty.call(evidence, "pr");
891
+ }
892
+ function hasPrCloseoutCompletionEvidence(evidence) {
893
+ const pr = evidence?.pr;
894
+ return evidence?.closeoutPassed === true && (pr?.isOpen === true || pr?.isMerged === true);
895
+ }
896
+ function isTaskClaimStale(metadata, nowIso) {
897
+ if (!metadata) {
898
+ return false;
899
+ }
900
+ const now = parseTimestamp(nowIso);
901
+ if (now == null) {
902
+ return false;
903
+ }
904
+ const reference = parseTimestamp(metadata.lastEvidenceAt) ?? parseTimestamp(metadata.claimedAt);
905
+ if (reference == null) {
906
+ return false;
907
+ }
908
+ return now - reference > STALE_CLAIM_MS;
909
+ }
910
+ function projectTaskReconciliation(input) {
911
+ const reasons = [];
912
+ const nowIso = input.evidence?.now ?? new Date().toISOString();
913
+ const isStale = isTaskClaimStale(input.metadata, nowIso);
914
+ const pr = input.evidence?.pr ?? null;
915
+ const hasExplicitPrEvidence = hasExplicitPullRequestEvidence(input.evidence);
916
+ const activeOwnerIds = (input.evidence?.activeOwnerIds ?? []).filter(Boolean);
917
+ if (new Set(activeOwnerIds).size > 1) {
918
+ return {
919
+ valid: false,
920
+ reasons: ["multiple-active-owners"],
921
+ isStale,
922
+ wouldReconcileTo: null,
923
+ nextMetadata: input.metadata
924
+ };
925
+ }
926
+ if (input.lifecycleStatus === "blocked" || input.lifecycleStatus === "cancelled") {
927
+ return {
928
+ valid: true,
929
+ reasons,
930
+ isStale,
931
+ wouldReconcileTo: null,
932
+ nextMetadata: input.metadata
933
+ };
934
+ }
935
+ if (input.lifecycleStatus === "in_progress") {
936
+ const ownershipMetadataIssue = inProgressOwnershipMetadataIssue(input.metadata);
937
+ const hasOwnershipMetadata = ownershipMetadataIssue == null;
938
+ if (hasPrCloseoutCompletionEvidence(input.evidence)) {
939
+ return {
940
+ valid: hasOwnershipMetadata,
941
+ reasons: hasOwnershipMetadata ? reasons : [ownershipMetadataIssue],
942
+ isStale,
943
+ wouldReconcileTo: "completed",
944
+ nextMetadata: input.metadata ? {
945
+ ...input.metadata,
946
+ status: "completed",
947
+ lastEvidenceAt: nowIso,
948
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
949
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
950
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
951
+ } : {
952
+ status: "completed",
953
+ lastEvidenceAt: nowIso,
954
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
955
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
956
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
957
+ }
958
+ };
959
+ }
960
+ if (ownershipMetadataIssue) {
961
+ return {
962
+ valid: false,
963
+ reasons: [ownershipMetadataIssue],
964
+ isStale,
965
+ wouldReconcileTo: pr?.isOpen ? "under_review" : "ready",
966
+ nextMetadata: pr?.isOpen ? {
967
+ ...input.metadata ?? { status: "under_review" },
968
+ status: "under_review",
969
+ lastEvidenceAt: nowIso,
970
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
971
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
972
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
973
+ } : null
974
+ };
975
+ }
976
+ if (isStale) {
977
+ if (pr?.isOpen) {
978
+ return {
979
+ valid: false,
980
+ reasons: ["stale-claim"],
981
+ isStale,
982
+ wouldReconcileTo: "under_review",
983
+ nextMetadata: {
984
+ ...input.metadata,
985
+ status: "under_review",
986
+ lastEvidenceAt: nowIso,
987
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
988
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
989
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
990
+ }
991
+ };
992
+ }
993
+ return {
994
+ valid: false,
995
+ reasons: ["stale-claim"],
996
+ isStale,
997
+ wouldReconcileTo: "ready",
998
+ nextMetadata: null
999
+ };
1000
+ }
1001
+ }
1002
+ if (hasPrCloseoutCompletionEvidence(input.evidence) && (input.lifecycleStatus === "open" || input.lifecycleStatus === "ready" || input.lifecycleStatus === "queued")) {
1003
+ return {
1004
+ valid: false,
1005
+ reasons: ["pr-closeout-before-completion"],
1006
+ isStale,
1007
+ wouldReconcileTo: "completed",
1008
+ nextMetadata: {
1009
+ ...input.metadata ?? { status: "completed" },
1010
+ status: "completed",
1011
+ lastEvidenceAt: nowIso,
1012
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
1013
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
1014
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
1015
+ }
1016
+ };
1017
+ }
1018
+ if (input.lifecycleStatus === "under_review") {
1019
+ const hasPrLink = Boolean(input.metadata?.prNumber || input.metadata?.prUrl || pr?.prNumber || pr?.prUrl);
1020
+ if (!hasPrLink) {
1021
+ return {
1022
+ valid: false,
1023
+ reasons: ["missing-pr-metadata"],
1024
+ isStale,
1025
+ wouldReconcileTo: "ready",
1026
+ nextMetadata: null
1027
+ };
1028
+ }
1029
+ if (hasExplicitPrEvidence && !pr) {
1030
+ return {
1031
+ valid: false,
1032
+ reasons: ["missing-pr-evidence"],
1033
+ isStale,
1034
+ wouldReconcileTo: "ready",
1035
+ nextMetadata: null
1036
+ };
1037
+ }
1038
+ if (pr?.reviewState === "changes_requested") {
1039
+ const ownershipMetadataIssue = inProgressOwnershipMetadataIssue(input.metadata);
1040
+ if (ownershipMetadataIssue) {
1041
+ return {
1042
+ valid: false,
1043
+ reasons: [ownershipMetadataIssue],
1044
+ isStale,
1045
+ wouldReconcileTo: "ready",
1046
+ nextMetadata: null
1047
+ };
1048
+ }
1049
+ return {
1050
+ valid: true,
1051
+ reasons,
1052
+ isStale,
1053
+ wouldReconcileTo: "in_progress",
1054
+ nextMetadata: input.metadata ? {
1055
+ ...input.metadata,
1056
+ status: "in_progress",
1057
+ lastEvidenceAt: nowIso,
1058
+ reviewState: "changes_requested"
1059
+ } : null
1060
+ };
1061
+ }
1062
+ if (hasPrCloseoutCompletionEvidence(input.evidence)) {
1063
+ return {
1064
+ valid: true,
1065
+ reasons,
1066
+ isStale,
1067
+ wouldReconcileTo: "completed",
1068
+ nextMetadata: input.metadata ? {
1069
+ ...input.metadata,
1070
+ status: "completed",
1071
+ lastEvidenceAt: nowIso,
1072
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
1073
+ } : null
1074
+ };
1075
+ }
1076
+ if (pr && !pr.isOpen && !pr.isMerged) {
1077
+ return {
1078
+ valid: false,
1079
+ reasons: ["closed-pr-without-merge"],
1080
+ isStale,
1081
+ wouldReconcileTo: "ready",
1082
+ nextMetadata: null
1083
+ };
1084
+ }
1085
+ }
1086
+ if (input.lifecycleStatus === "completed" && pr?.isOpen && !hasPrCloseoutCompletionEvidence(input.evidence)) {
1087
+ return {
1088
+ valid: false,
1089
+ reasons: ["completed-with-open-pr"],
1090
+ isStale,
1091
+ wouldReconcileTo: "under_review",
1092
+ nextMetadata: {
1093
+ ...input.metadata ?? { status: "under_review" },
1094
+ status: "under_review",
1095
+ lastEvidenceAt: nowIso,
1096
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
1097
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
1098
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
1099
+ }
1100
+ };
1101
+ }
1102
+ if (pr?.isOpen && (input.lifecycleStatus === "draft" || input.lifecycleStatus === "open" || input.lifecycleStatus === "ready" || input.lifecycleStatus === "queued" || input.lifecycleStatus === "in_progress")) {
1103
+ return {
1104
+ valid: true,
1105
+ reasons,
1106
+ isStale,
1107
+ wouldReconcileTo: "under_review",
1108
+ nextMetadata: {
1109
+ ...input.metadata ?? { status: "under_review" },
1110
+ status: "under_review",
1111
+ lastEvidenceAt: nowIso,
1112
+ ...pr?.prNumber != null ? { prNumber: pr?.prNumber } : {},
1113
+ ...pr?.prUrl ? { prUrl: pr?.prUrl } : {},
1114
+ ...pr?.reviewState ? { reviewState: pr?.reviewState } : {}
1115
+ }
1116
+ };
1117
+ }
1118
+ return {
1119
+ valid: true,
1120
+ reasons,
1121
+ isStale,
1122
+ wouldReconcileTo: null,
1123
+ nextMetadata: input.metadata
1124
+ };
1125
+ }
1126
+
1127
+ // packages/runtime/src/control-plane/state-sync/write.ts
1128
+ var DEFAULT_WRITER_DEPS = {
1129
+ fetchRef: nativeFetchRef,
1130
+ readBlobAtRef: nativeReadBlobAtRef,
1131
+ writeTreeCommit: nativeWriteTreeCommit,
1132
+ pushRefWithLease: nativePushRefWithLease,
1133
+ readSnapshot: readSyncedTrackerState,
1134
+ createClaimId: () => `claim-${crypto.randomUUID()}`,
1135
+ now: () => new Date().toISOString()
1136
+ };
1137
+
1138
+ class TrackerStateMutationError extends Error {
1139
+ code;
1140
+ constructor(message, code) {
1141
+ super(message);
1142
+ this.code = code;
1143
+ }
1144
+ }
1145
+ function parseIssuesJsonl2(raw) {
1146
+ const rows = [];
1147
+ for (const line of raw.split(/\r?\n/)) {
1148
+ const trimmed = line.trim();
1149
+ if (!trimmed) {
1150
+ continue;
1151
+ }
1152
+ try {
1153
+ const parsed = JSON.parse(trimmed);
1154
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1155
+ rows.push(parsed);
1156
+ }
1157
+ } catch {}
1158
+ }
1159
+ return rows;
1160
+ }
1161
+ function parseRawTaskStateFile(raw) {
1162
+ if (!raw || !raw.trim()) {
1163
+ return {
1164
+ schemaVersion: 1,
1165
+ tasks: {}
1166
+ };
1167
+ }
1168
+ try {
1169
+ const parsed = JSON.parse(raw);
1170
+ return {
1171
+ schemaVersion: typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : 1,
1172
+ tasks: parsed.tasks && typeof parsed.tasks === "object" && !Array.isArray(parsed.tasks) ? parsed.tasks : {}
1173
+ };
1174
+ } catch {
1175
+ return null;
1176
+ }
1177
+ }
1178
+ function parseTaskStateEnvelope2(raw, baseOid) {
1179
+ if (!raw || !raw.trim()) {
1180
+ return {
1181
+ schemaVersion: 1,
1182
+ supported: true,
1183
+ baseTrackerCommit: baseOid,
1184
+ tasks: {}
1185
+ };
1186
+ }
1187
+ try {
1188
+ const parsed = readTaskStateMetadataEnvelope(JSON.parse(raw));
1189
+ return {
1190
+ schemaVersion: parsed.supported ? parsed.schemaVersion : 1,
1191
+ supported: true,
1192
+ baseTrackerCommit: parsed.baseTrackerCommit ?? baseOid,
1193
+ tasks: parsed.supported ? parsed.tasks : {}
1194
+ };
1195
+ } catch {
1196
+ return {
1197
+ schemaVersion: 1,
1198
+ supported: true,
1199
+ baseTrackerCommit: baseOid,
1200
+ tasks: {}
1201
+ };
1202
+ }
1203
+ }
1204
+ function sanitizeTaskStateEnvelope(issueRows, envelope) {
1205
+ const lifecycleByTaskId = new Map;
1206
+ for (const row of issueRows) {
1207
+ const taskId = typeof row.id === "string" ? row.id : null;
1208
+ if (!taskId) {
1209
+ continue;
1210
+ }
1211
+ lifecycleByTaskId.set(taskId, normalizeTaskLifecycleStatus(row.status));
1212
+ }
1213
+ return {
1214
+ ...envelope,
1215
+ tasks: Object.fromEntries(Object.entries(envelope.tasks).map(([taskId, metadata]) => [
1216
+ taskId,
1217
+ discardMismatchedTaskStateMetadata({
1218
+ taskId,
1219
+ lifecycleStatus: lifecycleByTaskId.get(taskId) ?? null,
1220
+ metadata
1221
+ })
1222
+ ]).filter((entry) => entry[1] != null))
1223
+ };
1224
+ }
1225
+ function serializeIssuesJsonl(rows) {
1226
+ return `${rows.map((row) => JSON.stringify(row)).join(`
1227
+ `)}
1228
+ `;
1229
+ }
1230
+ function serializeTaskStateEnvelope(envelope) {
1231
+ return `${JSON.stringify({
1232
+ schemaVersion: envelope.schemaVersion,
1233
+ baseTrackerCommit: envelope.baseTrackerCommit,
1234
+ tasks: envelope.tasks
1235
+ }, null, 2)}
1236
+ `;
1237
+ }
1238
+ function findIssueRowIndex(rows, taskId) {
1239
+ return rows.findIndex((row) => typeof row.id === "string" && row.id === taskId);
1240
+ }
1241
+ function assertWritableTrackerStateBases(state) {
1242
+ if (state.issuesBaseOid !== state.taskStateBaseOid) {
1243
+ throw new TrackerStateMutationError("Tracker writes require issues.jsonl and task-state.json from the same remote base.", "same-base-required");
1244
+ }
1245
+ }
1246
+ function readWritableTrackerState(projectRoot, deps) {
1247
+ const repoPath = resolveTrackerRepoPath(projectRoot);
1248
+ const baseOid = deps.fetchRef(repoPath, "origin", "main");
1249
+ const issuesText = deps.readBlobAtRef(repoPath, baseOid, ".beads/issues.jsonl");
1250
+ const taskStateText = (() => {
1251
+ try {
1252
+ return deps.readBlobAtRef(repoPath, baseOid, ".beads/task-state.json");
1253
+ } catch {
1254
+ return null;
1255
+ }
1256
+ })();
1257
+ return {
1258
+ repoPath,
1259
+ baseOid,
1260
+ issuesBaseOid: baseOid,
1261
+ taskStateBaseOid: baseOid,
1262
+ issueRows: parseIssuesJsonl2(issuesText),
1263
+ taskStateEnvelope: sanitizeTaskStateEnvelope(parseIssuesJsonl2(issuesText), parseTaskStateEnvelope2(taskStateText, baseOid))
1264
+ };
1265
+ }
1266
+ function cloneWritableState(state) {
1267
+ return {
1268
+ ...state,
1269
+ issueRows: state.issueRows.map((row) => ({ ...row })),
1270
+ taskStateEnvelope: {
1271
+ ...state.taskStateEnvelope,
1272
+ tasks: Object.fromEntries(Object.entries(state.taskStateEnvelope.tasks).map(([taskId, metadata]) => [taskId, { ...metadata }]))
1273
+ }
1274
+ };
1275
+ }
1276
+ function currentLifecycleStatus(state, taskId) {
1277
+ const issueIndex = findIssueRowIndex(state.issueRows, taskId);
1278
+ if (issueIndex === -1) {
1279
+ throw new TrackerStateMutationError(`Task ${taskId} was not found in issues.jsonl.`, "task-not-found");
1280
+ }
1281
+ const status = normalizeTaskLifecycleStatus(state.issueRows[issueIndex]?.status);
1282
+ if (!status) {
1283
+ throw new TrackerStateMutationError(`Task ${taskId} has an invalid lifecycle status.`, "invalid-task-status");
1284
+ }
1285
+ return status;
1286
+ }
1287
+ function setLifecycleStatus(state, taskId, status) {
1288
+ const issueIndex = findIssueRowIndex(state.issueRows, taskId);
1289
+ if (issueIndex === -1) {
1290
+ throw new TrackerStateMutationError(`Task ${taskId} was not found in issues.jsonl.`, "task-not-found");
1291
+ }
1292
+ state.issueRows[issueIndex] = {
1293
+ ...state.issueRows[issueIndex],
1294
+ status
1295
+ };
1296
+ }
1297
+ function prepareClaimTaskMutation(state, input) {
1298
+ assertWritableTrackerStateBases(state);
1299
+ const next = cloneWritableState(state);
1300
+ const lifecycleStatus = currentLifecycleStatus(next, input.taskId);
1301
+ if (input.expectedStatus && lifecycleStatus !== input.expectedStatus) {
1302
+ throw new TrackerStateMutationError(`Task ${input.taskId} is ${lifecycleStatus}, expected ${input.expectedStatus}.`, "claim-conflict");
1303
+ }
1304
+ if (lifecycleStatus !== "ready" && lifecycleStatus !== "queued") {
1305
+ throw new TrackerStateMutationError(`Task ${input.taskId} cannot be claimed from ${lifecycleStatus}.`, "claim-conflict");
1306
+ }
1307
+ setLifecycleStatus(next, input.taskId, "in_progress");
1308
+ next.taskStateEnvelope.baseTrackerCommit = next.baseOid;
1309
+ next.taskStateEnvelope.tasks[input.taskId] = {
1310
+ status: "in_progress",
1311
+ claimId: input.claimId,
1312
+ ownerId: input.ownerId,
1313
+ claimedAt: input.now,
1314
+ lastEvidenceAt: input.now,
1315
+ ...input.runId ? { runId: input.runId } : {},
1316
+ ...input.branchName ? { branchName: input.branchName } : {},
1317
+ sourceCommit: next.baseOid
1318
+ };
1319
+ return next;
1320
+ }
1321
+ function prepareLifecycleTaskMutation(state, input) {
1322
+ assertWritableTrackerStateBases(state);
1323
+ const next = cloneWritableState(state);
1324
+ const lifecycleStatus = currentLifecycleStatus(next, input.taskId);
1325
+ if (input.allowedFrom && !input.allowedFrom.includes(lifecycleStatus)) {
1326
+ throw new TrackerStateMutationError(`Task ${input.taskId} is ${lifecycleStatus}, expected one of ${input.allowedFrom.join(", ")}.`, "status-conflict");
1327
+ }
1328
+ setLifecycleStatus(next, input.taskId, input.status);
1329
+ next.taskStateEnvelope.baseTrackerCommit = next.baseOid;
1330
+ if (input.clearMetadata || input.metadata == null) {
1331
+ delete next.taskStateEnvelope.tasks[input.taskId];
1332
+ } else {
1333
+ next.taskStateEnvelope.tasks[input.taskId] = input.metadata;
1334
+ }
1335
+ return next;
1336
+ }
1337
+ function prepareReconcileTaskMutation(state, input) {
1338
+ assertWritableTrackerStateBases(state);
1339
+ const next = cloneWritableState(state);
1340
+ const lifecycleStatus = currentLifecycleStatus(next, input.taskId);
1341
+ const projection = projectTaskReconciliation({
1342
+ lifecycleStatus,
1343
+ metadata: next.taskStateEnvelope.tasks[input.taskId] ?? null,
1344
+ evidence: input.evidence
1345
+ });
1346
+ if (!projection.wouldReconcileTo) {
1347
+ return null;
1348
+ }
1349
+ if (!projection.valid && projection.reasons.includes("multiple-active-owners")) {
1350
+ throw new TrackerStateMutationError(`Task ${input.taskId} has multiple active owners.`, "invalid-reconciliation");
1351
+ }
1352
+ setLifecycleStatus(next, input.taskId, projection.wouldReconcileTo);
1353
+ next.taskStateEnvelope.baseTrackerCommit = next.baseOid;
1354
+ if (projection.nextMetadata) {
1355
+ next.taskStateEnvelope.tasks[input.taskId] = projection.nextMetadata;
1356
+ } else {
1357
+ delete next.taskStateEnvelope.tasks[input.taskId];
1358
+ }
1359
+ return next;
1360
+ }
1361
+ function desiredTaskStateSatisfied(raw, desired) {
1362
+ const issue = parseIssuesJsonl2(raw.issuesText).find((entry) => entry.id === desired.taskId);
1363
+ if (!issue || issue.status !== desired.status) {
1364
+ return false;
1365
+ }
1366
+ const rawTaskState = parseRawTaskStateFile(raw.taskStateText);
1367
+ if (!rawTaskState || rawTaskState.schemaVersion !== 1) {
1368
+ return false;
1369
+ }
1370
+ const rawMetadata = rawTaskState.tasks[desired.taskId] ?? null;
1371
+ const snapshotMetadata = discardMismatchedTaskStateMetadata({
1372
+ taskId: desired.taskId,
1373
+ lifecycleStatus: desired.status,
1374
+ metadata: rawMetadata
1375
+ });
1376
+ if (desired.metadata == null) {
1377
+ return rawMetadata == null && snapshotMetadata == null;
1378
+ }
1379
+ if (rawMetadata == null || snapshotMetadata == null) {
1380
+ return false;
1381
+ }
1382
+ return isDeepStrictEqual(snapshotMetadata ?? null, desired.metadata);
1383
+ }
1384
+ function persistTrackerState(projectRoot, state, desired, message, deps) {
1385
+ const updates = [
1386
+ { path: ".beads/issues.jsonl", content: serializeIssuesJsonl(state.issueRows) },
1387
+ { path: ".beads/task-state.json", content: serializeTaskStateEnvelope(state.taskStateEnvelope) }
1388
+ ];
1389
+ const commitOid = deps.writeTreeCommit(state.repoPath, state.baseOid, updates, message);
1390
+ try {
1391
+ deps.pushRefWithLease(state.repoPath, commitOid, "refs/heads/main", state.baseOid);
1392
+ return {
1393
+ outcome: "applied",
1394
+ snapshot: deps.readSnapshot(projectRoot),
1395
+ commitOid
1396
+ };
1397
+ } catch (error) {
1398
+ const latestBaseOid = deps.fetchRef(state.repoPath, "origin", "main");
1399
+ const latestIssuesText = deps.readBlobAtRef(state.repoPath, latestBaseOid, ".beads/issues.jsonl");
1400
+ const latestTaskStateText = (() => {
1401
+ try {
1402
+ return deps.readBlobAtRef(state.repoPath, latestBaseOid, ".beads/task-state.json");
1403
+ } catch {
1404
+ return null;
1405
+ }
1406
+ })();
1407
+ const latestSnapshot = deps.readSnapshot(projectRoot);
1408
+ if (desiredTaskStateSatisfied({
1409
+ issuesText: latestIssuesText,
1410
+ taskStateText: latestTaskStateText
1411
+ }, desired)) {
1412
+ return {
1413
+ outcome: "noop",
1414
+ snapshot: latestSnapshot,
1415
+ commitOid: null
1416
+ };
1417
+ }
1418
+ throw new TrackerStateMutationError(error instanceof Error ? error.message : String(error), "push-conflict");
1419
+ }
1420
+ }
1421
+ function claimRemoteTrackerTask(projectRoot, input, deps = {}) {
1422
+ const writerDeps = { ...DEFAULT_WRITER_DEPS, ...deps };
1423
+ const claimId = writerDeps.createClaimId();
1424
+ const nextState = prepareClaimTaskMutation(readWritableTrackerState(projectRoot, writerDeps), {
1425
+ ...input,
1426
+ now: writerDeps.now(),
1427
+ claimId
1428
+ });
1429
+ const persisted = persistTrackerState(projectRoot, nextState, { taskId: input.taskId, status: "in_progress", metadata: nextState.taskStateEnvelope.tasks[input.taskId] ?? null }, `chore(tracker): claim ${input.taskId}`, writerDeps);
1430
+ return {
1431
+ ...persisted,
1432
+ claimId
1433
+ };
1434
+ }
1435
+ function reconcileRemoteTrackerTask(projectRoot, input, deps = {}) {
1436
+ const writerDeps = { ...DEFAULT_WRITER_DEPS, ...deps };
1437
+ const currentState = readWritableTrackerState(projectRoot, writerDeps);
1438
+ const nextState = prepareReconcileTaskMutation(currentState, {
1439
+ ...input,
1440
+ evidence: {
1441
+ ...input.evidence,
1442
+ now: input.evidence?.now ?? writerDeps.now()
1443
+ }
1444
+ });
1445
+ if (!nextState) {
1446
+ return {
1447
+ outcome: "noop",
1448
+ snapshot: writerDeps.readSnapshot(projectRoot),
1449
+ commitOid: null
1450
+ };
1451
+ }
1452
+ const nextStatus = currentLifecycleStatus(nextState, input.taskId);
1453
+ return persistTrackerState(projectRoot, nextState, { taskId: input.taskId, status: nextStatus, metadata: nextState.taskStateEnvelope.tasks[input.taskId] ?? null }, `chore(tracker): reconcile ${input.taskId}`, writerDeps);
1454
+ }
1455
+ function updateRemoteTrackerTaskLifecycle(projectRoot, input, deps = {}) {
1456
+ const writerDeps = { ...DEFAULT_WRITER_DEPS, ...deps };
1457
+ const nextState = prepareLifecycleTaskMutation(readWritableTrackerState(projectRoot, writerDeps), input);
1458
+ return persistTrackerState(projectRoot, nextState, { taskId: input.taskId, status: input.status, metadata: input.clearMetadata ? null : nextState.taskStateEnvelope.tasks[input.taskId] ?? null }, `chore(tracker): ${input.reason ?? "update"} ${input.taskId} -> ${input.status}`, writerDeps);
1459
+ }
1460
+ export {
1461
+ updateRemoteTrackerTaskLifecycle,
1462
+ reconcileRemoteTrackerTask,
1463
+ prepareReconcileTaskMutation,
1464
+ prepareLifecycleTaskMutation,
1465
+ prepareClaimTaskMutation,
1466
+ claimRemoteTrackerTask,
1467
+ assertWritableTrackerStateBases,
1468
+ TrackerStateMutationError
1469
+ };