@h-rig/task-sources-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/control-plane/native/github-token-env.d.ts +3 -0
- package/dist/src/control-plane/native/github-token-env.js +26 -0
- package/dist/src/control-plane/native/native-git.d.ts +18 -0
- package/dist/src/control-plane/native/native-git.js +291 -0
- package/dist/src/control-plane/native/runtime-binary-build.d.ts +9 -0
- package/dist/src/control-plane/native/runtime-binary-build.js +107 -0
- package/dist/src/control-plane/native/task-ops.d.ts +52 -0
- package/dist/src/control-plane/native/task-ops.js +3192 -0
- package/dist/src/control-plane/native/task-state.d.ts +30 -0
- package/dist/src/control-plane/native/task-state.js +944 -0
- package/dist/src/control-plane/native/utils.d.ts +7 -0
- package/dist/src/control-plane/native/utils.js +110 -0
- package/dist/src/control-plane/native/validator-binaries.d.ts +3 -0
- package/dist/src/control-plane/native/validator-binaries.js +175 -0
- package/dist/src/control-plane/native/validator.d.ts +44 -0
- package/dist/src/control-plane/native/validator.js +979 -0
- package/dist/src/control-plane/state-sync/index.d.ts +4 -0
- package/dist/src/control-plane/state-sync/index.js +1205 -0
- package/dist/src/control-plane/state-sync/native-git.d.ts +1 -0
- package/dist/src/control-plane/state-sync/native-git.js +281 -0
- package/dist/src/control-plane/state-sync/read.d.ts +46 -0
- package/dist/src/control-plane/state-sync/read.js +564 -0
- package/dist/src/control-plane/state-sync/reconcile.d.ts +28 -0
- package/dist/src/control-plane/state-sync/reconcile.js +260 -0
- package/dist/src/control-plane/state-sync/repo.d.ts +1 -0
- package/dist/src/control-plane/state-sync/repo.js +42 -0
- package/dist/src/control-plane/state-sync/types.d.ts +28 -0
- package/dist/src/control-plane/state-sync/types.js +111 -0
- package/dist/src/control-plane/state-sync/write.d.ts +83 -0
- package/dist/src/control-plane/state-sync/write.js +1165 -0
- package/dist/src/control-plane/task-data-service.d.ts +17 -0
- package/dist/src/control-plane/task-data-service.js +3653 -0
- package/dist/src/control-plane/task-fields.d.ts +1 -0
- package/dist/src/control-plane/task-fields.js +6 -0
- package/dist/src/control-plane/task-io-service.d.ts +6 -0
- package/dist/src/control-plane/task-io-service.js +108 -0
- package/dist/src/control-plane/task-source-bootstrap.d.ts +1 -0
- package/dist/src/control-plane/task-source-bootstrap.js +6 -0
- package/dist/src/control-plane/task-source.d.ts +2 -0
- package/dist/src/control-plane/task-source.js +6 -0
- package/dist/src/control-plane/tasks/legacy-task-config-source.d.ts +19 -0
- package/dist/src/control-plane/tasks/legacy-task-config-source.js +124 -0
- package/dist/src/control-plane/tasks/plugin-task-source.d.ts +30 -0
- package/dist/src/control-plane/tasks/plugin-task-source.js +99 -0
- package/dist/src/control-plane/tasks/source-aware-task-config-source.d.ts +28 -0
- package/dist/src/control-plane/tasks/source-aware-task-config-source.js +642 -0
- package/dist/src/control-plane/tasks/source-lifecycle.d.ts +56 -0
- package/dist/src/control-plane/tasks/source-lifecycle.js +834 -0
- package/dist/src/plugin.d.ts +1 -1
- package/dist/src/plugin.js +3927 -64
- package/package.json +57 -4
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/task-sources-plugin/src/control-plane/native/task-state.ts
|
|
3
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4
|
+
import { basename, resolve as resolve4 } from "path";
|
|
5
|
+
import { assertPathInsideRoot, safePathSegment } from "@rig/core/safe-identifiers";
|
|
6
|
+
import { loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
|
|
7
|
+
|
|
8
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/types.ts
|
|
9
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
|
|
10
|
+
var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
11
|
+
"draft",
|
|
12
|
+
"open",
|
|
13
|
+
"ready",
|
|
14
|
+
"queued",
|
|
15
|
+
"in_progress",
|
|
16
|
+
"under_review",
|
|
17
|
+
"blocked",
|
|
18
|
+
"completed",
|
|
19
|
+
"cancelled"
|
|
20
|
+
]);
|
|
21
|
+
function normalizeTaskLifecycleStatus(status) {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case "draft":
|
|
24
|
+
case "open":
|
|
25
|
+
case "ready":
|
|
26
|
+
case "queued":
|
|
27
|
+
case "in_progress":
|
|
28
|
+
case "under_review":
|
|
29
|
+
case "blocked":
|
|
30
|
+
case "completed":
|
|
31
|
+
case "cancelled":
|
|
32
|
+
return status;
|
|
33
|
+
case "closed":
|
|
34
|
+
return "completed";
|
|
35
|
+
case "running":
|
|
36
|
+
return "in_progress";
|
|
37
|
+
case "failed":
|
|
38
|
+
return "ready";
|
|
39
|
+
default:
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function normalizeTaskStateMetadataStatus(status) {
|
|
44
|
+
if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
|
|
45
|
+
return status;
|
|
46
|
+
}
|
|
47
|
+
switch (status) {
|
|
48
|
+
case "closed":
|
|
49
|
+
return "completed";
|
|
50
|
+
case "running":
|
|
51
|
+
return "in_progress";
|
|
52
|
+
case "failed":
|
|
53
|
+
return "ready";
|
|
54
|
+
default:
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function canonicalizeTaskStateMetadata(raw) {
|
|
59
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const metadata = raw;
|
|
63
|
+
const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
|
|
64
|
+
const status = normalizeTaskStateMetadataStatus(metadata.status);
|
|
65
|
+
if (!status) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...claimId ? { claimId } : {},
|
|
70
|
+
status,
|
|
71
|
+
...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
|
|
72
|
+
...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
|
|
73
|
+
...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
|
|
74
|
+
...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
|
|
75
|
+
...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
|
|
76
|
+
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
77
|
+
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
78
|
+
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
79
|
+
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
80
|
+
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function discardMismatchedTaskStateMetadata(input) {
|
|
84
|
+
input.taskId;
|
|
85
|
+
const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
|
|
86
|
+
if (!canonicalMetadata || !input.lifecycleStatus) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const metadataStatus = canonicalMetadata.status ?? null;
|
|
90
|
+
if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return canonicalMetadata;
|
|
94
|
+
}
|
|
95
|
+
function readTaskStateMetadataEnvelope(raw) {
|
|
96
|
+
if (!raw || typeof raw !== "object") {
|
|
97
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
98
|
+
}
|
|
99
|
+
const envelope = raw;
|
|
100
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
101
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
102
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
103
|
+
}
|
|
104
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
105
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
106
|
+
return {
|
|
107
|
+
schemaVersion,
|
|
108
|
+
supported: true,
|
|
109
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
110
|
+
tasks
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/read.ts
|
|
114
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
115
|
+
import { resolve as resolve3 } from "path";
|
|
116
|
+
|
|
117
|
+
// packages/task-sources-plugin/src/control-plane/native/native-git.ts
|
|
118
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
119
|
+
import { tmpdir } from "os";
|
|
120
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
121
|
+
import { createHash } from "crypto";
|
|
122
|
+
var taskSourcesGitNativeOutputDir = resolve(tmpdir(), "rig-task-sources-native");
|
|
123
|
+
var taskSourcesGitNativeOutputPath = resolve(taskSourcesGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
124
|
+
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
125
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
126
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
127
|
+
}
|
|
128
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
129
|
+
const requestDir = resolve(taskSourcesGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
130
|
+
mkdirSync(requestDir, { recursive: true });
|
|
131
|
+
const outputPath = resolve(requestDir, "blob.txt");
|
|
132
|
+
try {
|
|
133
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
134
|
+
return readFileSync(outputPath, "utf8");
|
|
135
|
+
} finally {
|
|
136
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function runGitNative(command, args) {
|
|
140
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
141
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
142
|
+
}
|
|
143
|
+
let binaryPath;
|
|
144
|
+
try {
|
|
145
|
+
binaryPath = resolveGitBinaryPath() ?? ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
return { ok: false, error: message.includes("rig-git.zig source file not found") ? "rig-git binary not found" : message };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
152
|
+
stdout: "pipe",
|
|
153
|
+
stderr: "pipe",
|
|
154
|
+
env: gitNativeEnv()
|
|
155
|
+
});
|
|
156
|
+
if (proc.exitCode !== 0) {
|
|
157
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
158
|
+
if (stdoutText) {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(stdoutText);
|
|
161
|
+
if (!parsed.ok) {
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
const error = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
167
|
+
return { ok: false, error };
|
|
168
|
+
}
|
|
169
|
+
return JSON.parse(proc.stdout.toString().trim());
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return { ok: false, error: String(error) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function requireGitNative(command, args) {
|
|
175
|
+
const result = runGitNative(command, args);
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
function requireGitNativeString(command, args) {
|
|
182
|
+
const result = requireGitNative(command, args);
|
|
183
|
+
if ("value" in result && typeof result.value === "string") {
|
|
184
|
+
return result.value;
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
187
|
+
}
|
|
188
|
+
function gitNativeEnv() {
|
|
189
|
+
const env = { ...process.env };
|
|
190
|
+
const token = env.GITHUB_TOKEN?.trim() || env.GH_TOKEN?.trim() || env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
191
|
+
if (token) {
|
|
192
|
+
env.RIG_GITHUB_TOKEN = env.RIG_GITHUB_TOKEN || token;
|
|
193
|
+
env.GITHUB_TOKEN = env.GITHUB_TOKEN || token;
|
|
194
|
+
env.GH_TOKEN = env.GH_TOKEN || token;
|
|
195
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
196
|
+
env.GIT_CONFIG_COUNT = "2";
|
|
197
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
198
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
199
|
+
env.GIT_CONFIG_KEY_1 = "credential.helper";
|
|
200
|
+
env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
|
|
201
|
+
}
|
|
202
|
+
return env;
|
|
203
|
+
}
|
|
204
|
+
function runtimeRigGitFileName() {
|
|
205
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
206
|
+
}
|
|
207
|
+
function preferredGitBinaryOutputPath() {
|
|
208
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
209
|
+
return explicit || taskSourcesGitNativeOutputPath;
|
|
210
|
+
}
|
|
211
|
+
function resolveGitBinaryPath() {
|
|
212
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
213
|
+
if (explicit && existsSync(explicit)) {
|
|
214
|
+
return explicit;
|
|
215
|
+
}
|
|
216
|
+
if (explicit) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
220
|
+
if (candidate && existsSync(candidate)) {
|
|
221
|
+
return candidate;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function ensureRigGitBinaryPathSync(outputPath = taskSourcesGitNativeOutputPath) {
|
|
227
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
228
|
+
if (explicit && existsSync(explicit)) {
|
|
229
|
+
return explicit;
|
|
230
|
+
}
|
|
231
|
+
const sourcePath = resolveGitSourcePath();
|
|
232
|
+
if (!sourcePath) {
|
|
233
|
+
const binaryPath = resolveGitBinaryPath();
|
|
234
|
+
if (binaryPath) {
|
|
235
|
+
return binaryPath;
|
|
236
|
+
}
|
|
237
|
+
throw new Error("rig-git.zig source file not found.");
|
|
238
|
+
}
|
|
239
|
+
const zigBinary = Bun.which("zig");
|
|
240
|
+
if (!zigBinary) {
|
|
241
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
242
|
+
}
|
|
243
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
244
|
+
const buildKey = JSON.stringify({
|
|
245
|
+
version: 1,
|
|
246
|
+
zigBinary,
|
|
247
|
+
platform: process.platform,
|
|
248
|
+
arch: process.arch,
|
|
249
|
+
sourcePath,
|
|
250
|
+
sourceDigest: createHash("sha256").update(readFileSync(sourcePath)).digest("hex")
|
|
251
|
+
});
|
|
252
|
+
const manifestPath = `${outputPath}.build-manifest.json`;
|
|
253
|
+
if (nativeBuildIsFresh(outputPath, manifestPath, buildKey)) {
|
|
254
|
+
chmodSync(outputPath, 493);
|
|
255
|
+
return outputPath;
|
|
256
|
+
}
|
|
257
|
+
const tempOutputPath = resolve(dirname(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${process.platform === "win32" ? ".exe" : ""}`);
|
|
258
|
+
const build = Bun.spawnSync([
|
|
259
|
+
zigBinary,
|
|
260
|
+
"build-exe",
|
|
261
|
+
sourcePath,
|
|
262
|
+
"-O",
|
|
263
|
+
"ReleaseFast",
|
|
264
|
+
`-femit-bin=${tempOutputPath}`
|
|
265
|
+
], {
|
|
266
|
+
cwd: dirname(sourcePath),
|
|
267
|
+
stdout: "pipe",
|
|
268
|
+
stderr: "pipe"
|
|
269
|
+
});
|
|
270
|
+
if (build.exitCode !== 0 || !existsSync(tempOutputPath)) {
|
|
271
|
+
const details = [build.stderr.toString().trim(), build.stdout.toString().trim()].filter(Boolean).join(`
|
|
272
|
+
`);
|
|
273
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
274
|
+
}
|
|
275
|
+
chmodSync(tempOutputPath, 493);
|
|
276
|
+
renameSync(tempOutputPath, outputPath);
|
|
277
|
+
if (!binarySupportsTrackerCommands(outputPath)) {
|
|
278
|
+
rmSync(outputPath, { force: true });
|
|
279
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
280
|
+
}
|
|
281
|
+
writeFileSync(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
282
|
+
`, "utf8");
|
|
283
|
+
return outputPath;
|
|
284
|
+
}
|
|
285
|
+
function nativeBuildIsFresh(outputPath, manifestPath, buildKey) {
|
|
286
|
+
if (!existsSync(outputPath) || !existsSync(manifestPath)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
291
|
+
return manifest.version === 1 && manifest.buildKey === buildKey && binarySupportsTrackerCommands(outputPath);
|
|
292
|
+
} catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function binarySupportsTrackerCommands(binaryPath) {
|
|
297
|
+
try {
|
|
298
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
299
|
+
stdout: "pipe",
|
|
300
|
+
stderr: "pipe"
|
|
301
|
+
});
|
|
302
|
+
const stdout = probe.stdout.toString().trim();
|
|
303
|
+
const stderr = probe.stderr.toString().trim();
|
|
304
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
308
|
+
} catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function resolveGitSourcePath() {
|
|
313
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
314
|
+
if (candidate && existsSync(candidate)) {
|
|
315
|
+
return candidate;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
function rigGitSourceCandidates() {
|
|
321
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
322
|
+
const cwd = process.cwd()?.trim() || "";
|
|
323
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
324
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
325
|
+
return [...new Set([
|
|
326
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
327
|
+
resolve(import.meta.dir, "../../../native/rig-git.zig"),
|
|
328
|
+
projectRoot ? resolve(projectRoot, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
329
|
+
projectRoot ? resolve(projectRoot, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
330
|
+
projectRoot ? resolve(projectRoot, "packages/shared/native/rig-git.zig") : "",
|
|
331
|
+
hostProjectRoot ? resolve(hostProjectRoot, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
332
|
+
hostProjectRoot ? resolve(hostProjectRoot, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
333
|
+
hostProjectRoot ? resolve(hostProjectRoot, "packages/shared/native/rig-git.zig") : "",
|
|
334
|
+
cwd ? resolve(cwd, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
335
|
+
cwd ? resolve(cwd, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
336
|
+
cwd ? resolve(cwd, "packages/shared/native/rig-git.zig") : "",
|
|
337
|
+
execDir ? resolve(execDir, "..", "native", "rig-git.zig") : ""
|
|
338
|
+
].filter(Boolean))];
|
|
339
|
+
}
|
|
340
|
+
function rigGitBinaryCandidates() {
|
|
341
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
342
|
+
const fileName = runtimeRigGitFileName();
|
|
343
|
+
return [...new Set([
|
|
344
|
+
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
345
|
+
execDir ? resolve(execDir, fileName) : "",
|
|
346
|
+
execDir ? resolve(execDir, "..", fileName) : "",
|
|
347
|
+
execDir ? resolve(execDir, "..", "bin", fileName) : "",
|
|
348
|
+
taskSourcesGitNativeOutputPath
|
|
349
|
+
].filter(Boolean))];
|
|
350
|
+
}
|
|
351
|
+
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
352
|
+
const candidates = [];
|
|
353
|
+
let cursor = resolve(fromDir);
|
|
354
|
+
for (let index = 0;index < 8; index += 1) {
|
|
355
|
+
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));
|
|
356
|
+
const parent = dirname(cursor);
|
|
357
|
+
if (parent === cursor)
|
|
358
|
+
break;
|
|
359
|
+
cursor = parent;
|
|
360
|
+
}
|
|
361
|
+
return candidates;
|
|
362
|
+
}
|
|
363
|
+
// packages/task-sources-plugin/src/control-plane/native/utils.ts
|
|
364
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
365
|
+
import { unique } from "@rig/core/exec";
|
|
366
|
+
import {
|
|
367
|
+
runCapture,
|
|
368
|
+
runCaptureAsync,
|
|
369
|
+
runInherit,
|
|
370
|
+
getValidationTimeoutMs,
|
|
371
|
+
readJsonFile,
|
|
372
|
+
nowIso,
|
|
373
|
+
unique as unique2,
|
|
374
|
+
fileLines
|
|
375
|
+
} from "@rig/core/exec";
|
|
376
|
+
import { resolveCheckoutRoot } from "@rig/core/checkout-root";
|
|
377
|
+
import { resolveHarnessPaths } from "@rig/core/harness-paths";
|
|
378
|
+
var scopeRegexCache = new Map;
|
|
379
|
+
|
|
380
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/read.ts
|
|
381
|
+
import { RUNTIME_CONTEXT_ENV } from "@rig/core/runtime-context";
|
|
382
|
+
|
|
383
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/repo.ts
|
|
384
|
+
import { existsSync as existsSync2 } from "fs";
|
|
385
|
+
import { resolve as resolve2 } from "path";
|
|
386
|
+
import { MANAGED_REPO_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
387
|
+
import { defineCapability } from "@rig/core/capability";
|
|
388
|
+
import { getInstalledCapability } from "@rig/core/capability-loaders";
|
|
389
|
+
function resolveTrackerRepoPath(projectRoot) {
|
|
390
|
+
const monorepoRoot = resolveCheckoutRoot(projectRoot);
|
|
391
|
+
const managedRepos = getInstalledCapability(defineCapability(MANAGED_REPO_SERVICE_CAPABILITY));
|
|
392
|
+
if (managedRepos) {
|
|
393
|
+
try {
|
|
394
|
+
const layout = managedRepos.resolveMonorepoRepoLayout(projectRoot);
|
|
395
|
+
if (existsSync2(resolve2(layout.mirrorRoot, "HEAD"))) {
|
|
396
|
+
return layout.mirrorRoot;
|
|
397
|
+
}
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
return monorepoRoot;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/read.ts
|
|
404
|
+
var DEFAULT_READ_DEPS = {
|
|
405
|
+
fetchRef: nativeFetchRef,
|
|
406
|
+
readBlobAtRef: nativeReadBlobAtRef,
|
|
407
|
+
exists: existsSync3,
|
|
408
|
+
readFile: (path) => readFileSync2(path, "utf8")
|
|
409
|
+
};
|
|
410
|
+
function parseIssueStatus(rawStatus) {
|
|
411
|
+
const normalized = normalizeTaskLifecycleStatus(rawStatus);
|
|
412
|
+
return normalized ?? "unknown";
|
|
413
|
+
}
|
|
414
|
+
function parseIssueDependencies(raw) {
|
|
415
|
+
if (!Array.isArray(raw)) {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
return raw.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry)).map((entry) => ({
|
|
419
|
+
issueId: typeof entry.issue_id === "string" && entry.issue_id.trim() ? entry.issue_id.trim() : null,
|
|
420
|
+
dependsOnId: typeof entry.depends_on_id === "string" && entry.depends_on_id.trim() ? entry.depends_on_id.trim() : null,
|
|
421
|
+
id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
422
|
+
type: typeof entry.type === "string" && entry.type.trim() ? entry.type.trim() : null
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
function parseIssuesJsonl(raw) {
|
|
426
|
+
const issues = [];
|
|
427
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
428
|
+
const trimmed = line.trim();
|
|
429
|
+
if (!trimmed) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const record = JSON.parse(trimmed);
|
|
434
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
|
|
435
|
+
if (!id) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const rawStatus = typeof record.status === "string" && record.status.trim() ? record.status.trim() : null;
|
|
439
|
+
issues.push({
|
|
440
|
+
id,
|
|
441
|
+
title: typeof record.title === "string" && record.title.trim() ? record.title.trim() : null,
|
|
442
|
+
description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
|
|
443
|
+
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,
|
|
444
|
+
issueType: typeof record.issue_type === "string" && record.issue_type.trim() ? record.issue_type.trim() : null,
|
|
445
|
+
status: parseIssueStatus(rawStatus),
|
|
446
|
+
rawStatus,
|
|
447
|
+
priority: typeof record.priority === "number" ? record.priority : null,
|
|
448
|
+
dependencies: parseIssueDependencies(record.dependencies)
|
|
449
|
+
});
|
|
450
|
+
} catch {}
|
|
451
|
+
}
|
|
452
|
+
return issues;
|
|
453
|
+
}
|
|
454
|
+
function parseTaskStateEnvelope(raw) {
|
|
455
|
+
if (!raw || !raw.trim()) {
|
|
456
|
+
return readTaskStateMetadataEnvelope(null);
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
return readTaskStateMetadataEnvelope(JSON.parse(raw));
|
|
460
|
+
} catch {
|
|
461
|
+
return readTaskStateMetadataEnvelope(null);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function readRemoteBlobAtRef(deps, repoPath, ref, path, options) {
|
|
465
|
+
try {
|
|
466
|
+
return deps.readBlobAtRef(repoPath, ref, path);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (options.required) {
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function shouldPreferLocalTrackerState(options) {
|
|
475
|
+
if (!options.allowLocalFallback) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
479
|
+
if (!runtimeWorkspace) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (process.env.RIG_TASK_RUNTIME_ID?.trim()) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
const runtimeContextPath = process.env[RUNTIME_CONTEXT_ENV]?.trim();
|
|
486
|
+
if (runtimeContextPath) {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
return existsSync3(resolve3(runtimeWorkspace, ".rig", "runtime-context.json"));
|
|
490
|
+
}
|
|
491
|
+
function readLocalTrackerState(projectRoot, deps) {
|
|
492
|
+
const monorepoRoot = resolveCheckoutRoot(projectRoot);
|
|
493
|
+
const issuesPath = resolve3(monorepoRoot, ".beads", "issues.jsonl");
|
|
494
|
+
const taskStatePath = resolve3(monorepoRoot, ".beads", "task-state.json");
|
|
495
|
+
return projectSyncedTrackerSnapshot({
|
|
496
|
+
source: "local",
|
|
497
|
+
issuesBaseOid: null,
|
|
498
|
+
issuesText: deps.exists(issuesPath) ? deps.readFile(issuesPath) : "",
|
|
499
|
+
taskStateBaseOid: null,
|
|
500
|
+
taskStateText: deps.exists(taskStatePath) ? deps.readFile(taskStatePath) : null
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function projectSyncedTrackerSnapshot(input) {
|
|
504
|
+
if (input.source === "remote" && input.issuesBaseOid && input.taskStateBaseOid && input.issuesBaseOid !== input.taskStateBaseOid) {
|
|
505
|
+
throw new Error("Remote tracker files must be read from the same fetched base.");
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
source: input.source,
|
|
509
|
+
baseOid: input.issuesBaseOid ?? input.taskStateBaseOid ?? null,
|
|
510
|
+
issues: parseIssuesJsonl(input.issuesText),
|
|
511
|
+
taskState: parseTaskStateEnvelope(input.taskStateText)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function readSyncedTrackerState(projectRoot, deps = {}, options = {}) {
|
|
515
|
+
const readDeps = { ...DEFAULT_READ_DEPS, ...deps };
|
|
516
|
+
const trackerRepoPath = resolveTrackerRepoPath(projectRoot);
|
|
517
|
+
if (shouldPreferLocalTrackerState(options)) {
|
|
518
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const baseOid = readDeps.fetchRef(trackerRepoPath, "origin", "main");
|
|
522
|
+
return projectSyncedTrackerSnapshot({
|
|
523
|
+
source: "remote",
|
|
524
|
+
issuesBaseOid: baseOid,
|
|
525
|
+
issuesText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/issues.jsonl", {
|
|
526
|
+
required: true
|
|
527
|
+
}) ?? "",
|
|
528
|
+
taskStateBaseOid: baseOid,
|
|
529
|
+
taskStateText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/task-state.json", {
|
|
530
|
+
required: false
|
|
531
|
+
})
|
|
532
|
+
});
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (!options.allowLocalFallback) {
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/reconcile.ts
|
|
541
|
+
var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
|
|
542
|
+
// packages/task-sources-plugin/src/control-plane/native/task-state.ts
|
|
543
|
+
function readTaskConfig(projectRoot) {
|
|
544
|
+
const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
|
|
545
|
+
return stripTaskConfigMetadata(raw);
|
|
546
|
+
}
|
|
547
|
+
function readSourceTaskConfig(projectRoot) {
|
|
548
|
+
const raw = readAndSyncSourceTaskConfig(projectRoot);
|
|
549
|
+
return stripTaskConfigMetadata(raw);
|
|
550
|
+
}
|
|
551
|
+
function readValidationDescriptions(projectRoot) {
|
|
552
|
+
const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
|
|
553
|
+
return readValidationDescriptionMap(raw);
|
|
554
|
+
}
|
|
555
|
+
function readSourceValidationDescriptions(projectRoot) {
|
|
556
|
+
const rootRaw = readJsonFile(resolve4(projectRoot, "rig", "task-config.json"), {});
|
|
557
|
+
const sourcePath = findSourceTaskConfigPath(projectRoot);
|
|
558
|
+
const sourceRaw = sourcePath ? readJsonFile(sourcePath, {}) : {};
|
|
559
|
+
const rootDescriptions = readValidationDescriptionMap(rootRaw);
|
|
560
|
+
const sourceDescriptions = readValidationDescriptionMap(sourceRaw);
|
|
561
|
+
return {
|
|
562
|
+
...rootDescriptions,
|
|
563
|
+
...sourceDescriptions
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function currentTaskId(projectRoot) {
|
|
567
|
+
const fromEnv = (process.env.RIG_TASK_ID || "").trim();
|
|
568
|
+
if (fromEnv) {
|
|
569
|
+
return fromEnv;
|
|
570
|
+
}
|
|
571
|
+
const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
|
|
572
|
+
if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
|
|
573
|
+
return runtimeId.slice("task-".length);
|
|
574
|
+
}
|
|
575
|
+
const workspace = (process.env.RIG_TASK_WORKSPACE || "").trim();
|
|
576
|
+
const inferredFromWorkspace = inferTaskIdFromRuntimePath(workspace);
|
|
577
|
+
if (inferredFromWorkspace) {
|
|
578
|
+
return inferredFromWorkspace;
|
|
579
|
+
}
|
|
580
|
+
const inferredFromCwd = inferTaskIdFromRuntimePath(process.cwd());
|
|
581
|
+
if (inferredFromCwd) {
|
|
582
|
+
return inferredFromCwd;
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
586
|
+
const session = readJsonFile(paths.sessionPath, {});
|
|
587
|
+
return session.activeTaskIds?.[0] || "";
|
|
588
|
+
} catch {
|
|
589
|
+
return "";
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function readSourceTaskStateMetadata(projectRoot, taskId, lifecycleStatus, snapshot) {
|
|
593
|
+
const syncedSnapshot = snapshot ?? readSyncedTrackerState(projectRoot);
|
|
594
|
+
const syncedIssue = syncedSnapshot.issues.find((issue) => issue.id === taskId) ?? null;
|
|
595
|
+
const syncedLifecycleStatus = syncedIssue && syncedIssue.status !== "unknown" ? syncedIssue.status : readLocalSourceTaskLifecycleStatus(projectRoot, taskId);
|
|
596
|
+
const metadata = syncedSnapshot.taskState.tasks[taskId] ?? readLocalSourceTaskStateEnvelope(projectRoot).tasks[taskId] ?? null;
|
|
597
|
+
return discardMismatchedTaskStateMetadata({
|
|
598
|
+
taskId,
|
|
599
|
+
lifecycleStatus: syncedLifecycleStatus ?? normalizeTaskLifecycleStatus(lifecycleStatus),
|
|
600
|
+
metadata
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
function readValidationDescriptionMap(raw) {
|
|
604
|
+
return {
|
|
605
|
+
...coerceValidationDescriptions(raw.validation_descriptions),
|
|
606
|
+
...coerceValidationDescriptions(readValidationDescriptionsFromMeta(raw._meta))
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function stripTaskConfigMetadata(raw) {
|
|
610
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
611
|
+
return tasks;
|
|
612
|
+
}
|
|
613
|
+
function coerceValidationDescriptions(candidate) {
|
|
614
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
615
|
+
return {};
|
|
616
|
+
}
|
|
617
|
+
const descriptions = {};
|
|
618
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
619
|
+
if (typeof value === "string" && value.length > 0) {
|
|
620
|
+
descriptions[key] = value;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return descriptions;
|
|
624
|
+
}
|
|
625
|
+
function readValidationDescriptionsFromMeta(meta) {
|
|
626
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
return meta.validation_descriptions;
|
|
630
|
+
}
|
|
631
|
+
function readLocalSourceTaskStateEnvelope(projectRoot) {
|
|
632
|
+
const taskStatePath = resolve4(resolveCheckoutRoot(projectRoot), ".beads", "task-state.json");
|
|
633
|
+
return readTaskStateMetadataEnvelope(readJsonFile(taskStatePath, {}));
|
|
634
|
+
}
|
|
635
|
+
function readLocalSourceTaskLifecycleStatus(projectRoot, taskId) {
|
|
636
|
+
const issuesPath = resolve4(resolveCheckoutRoot(projectRoot), ".beads", "issues.jsonl");
|
|
637
|
+
if (!existsSync4(issuesPath)) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
for (const line of readFileSync3(issuesPath, "utf8").split(/\r?\n/)) {
|
|
641
|
+
const trimmed = line.trim();
|
|
642
|
+
if (!trimmed) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
const parsed = JSON.parse(trimmed);
|
|
647
|
+
if (parsed.id === taskId && parsed.issue_type === "task") {
|
|
648
|
+
return normalizeTaskLifecycleStatus(parsed.status);
|
|
649
|
+
}
|
|
650
|
+
} catch {}
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
function inferTaskIdFromRuntimePath(path) {
|
|
655
|
+
if (!path) {
|
|
656
|
+
return "";
|
|
657
|
+
}
|
|
658
|
+
const match = path.match(/\/\.rig\/runtime\/agents\/task-([^/]+)\/worktree(?:\/|$)/) || path.match(/\/\.worktrees\/([^/]+)(?:\/|$)/);
|
|
659
|
+
const candidate = match?.[1] || "";
|
|
660
|
+
return candidate.startsWith("bd-") ? candidate : "";
|
|
661
|
+
}
|
|
662
|
+
function lookupTask(projectRoot, input) {
|
|
663
|
+
if (!input) {
|
|
664
|
+
return "";
|
|
665
|
+
}
|
|
666
|
+
if (!input.startsWith("bd-")) {
|
|
667
|
+
return input;
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const taskConfig2 = readTaskConfig(projectRoot);
|
|
671
|
+
if (taskConfig2[input]) {
|
|
672
|
+
return input;
|
|
673
|
+
}
|
|
674
|
+
} catch {}
|
|
675
|
+
const taskConfig = readSourceTaskConfig(projectRoot);
|
|
676
|
+
return taskConfig[input] ? input : "";
|
|
677
|
+
}
|
|
678
|
+
function ensureArtifactAlias(projectRoot, id) {}
|
|
679
|
+
function artifactDirForId(projectRoot, id) {
|
|
680
|
+
const safeId = safePathSegment(id, { fallback: "task", maxLength: 96 });
|
|
681
|
+
const workspaceDir = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
682
|
+
if (workspaceDir) {
|
|
683
|
+
const worktreeArtifactsRoot = resolve4(workspaceDir, "artifacts");
|
|
684
|
+
const worktreeArtifacts = assertPathInsideRoot(worktreeArtifactsRoot, resolve4(worktreeArtifactsRoot, safeId), "artifact directory");
|
|
685
|
+
if (existsSync4(worktreeArtifacts) || existsSync4(worktreeArtifactsRoot)) {
|
|
686
|
+
return worktreeArtifacts;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
691
|
+
return assertPathInsideRoot(paths.artifactsDir, resolve4(paths.artifactsDir, safeId), "artifact directory");
|
|
692
|
+
} catch {
|
|
693
|
+
const artifactsRoot = resolve4(resolveCheckoutRoot(projectRoot), "artifacts");
|
|
694
|
+
return assertPathInsideRoot(artifactsRoot, resolve4(artifactsRoot, safeId), "artifact directory");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function resolveTaskConfigPath(projectRoot) {
|
|
698
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
699
|
+
if (existsSync4(paths.taskConfigPath)) {
|
|
700
|
+
return paths.taskConfigPath;
|
|
701
|
+
}
|
|
702
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
703
|
+
if (existsSync4(candidate)) {
|
|
704
|
+
return candidate;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
|
|
708
|
+
}
|
|
709
|
+
function findSourceTaskConfigPath(projectRoot) {
|
|
710
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
711
|
+
if (existsSync4(candidate)) {
|
|
712
|
+
return candidate;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
var FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
718
|
+
function readAndSyncSourceTaskConfig(projectRoot) {
|
|
719
|
+
const sourcePath = findSourceTaskConfigPath(projectRoot);
|
|
720
|
+
const raw = sourcePath ? readJsonFile(sourcePath, {}) : readConfiguredFileTaskConfig(projectRoot);
|
|
721
|
+
const synced = synchronizeTaskConfigWithTracker(projectRoot, raw);
|
|
722
|
+
if (sourcePath && synced.updated) {
|
|
723
|
+
try {
|
|
724
|
+
writeFileSync2(sourcePath, `${JSON.stringify(synced.config, null, 2)}
|
|
725
|
+
`, "utf-8");
|
|
726
|
+
} catch {}
|
|
727
|
+
}
|
|
728
|
+
return synced.config;
|
|
729
|
+
}
|
|
730
|
+
function synchronizeSourceTaskConfig(projectRoot) {
|
|
731
|
+
return readAndSyncSourceTaskConfig(projectRoot);
|
|
732
|
+
}
|
|
733
|
+
function synchronizeTaskConfigWithTracker(projectRoot, rawConfig) {
|
|
734
|
+
const issues = readSourceIssueRecords(projectRoot);
|
|
735
|
+
if (issues.length === 0) {
|
|
736
|
+
return { config: rawConfig, updated: false };
|
|
737
|
+
}
|
|
738
|
+
const taskConfig = stripTaskConfigMetadata(rawConfig);
|
|
739
|
+
const mergedConfig = { ...taskConfig };
|
|
740
|
+
const validationDescriptions = coerceValidationDescriptions(rawConfig.validation_descriptions);
|
|
741
|
+
const metaValidationDescriptions = coerceValidationDescriptions(readValidationDescriptionsFromMeta(rawConfig._meta));
|
|
742
|
+
let updated = false;
|
|
743
|
+
for (const issue of issues) {
|
|
744
|
+
if (issue.issueType !== "task") {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (mergedConfig[issue.id] && !shouldRefreshAutoSyncedTaskConfigEntry(mergedConfig[issue.id])) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
mergedConfig[issue.id] = buildAutoSyncedTaskConfigEntry(issue);
|
|
751
|
+
updated = true;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
config: {
|
|
755
|
+
...mergedConfig,
|
|
756
|
+
...Object.keys(validationDescriptions).length > 0 ? { validation_descriptions: validationDescriptions } : {},
|
|
757
|
+
...Object.keys(metaValidationDescriptions).length > 0 ? { _meta: { validation_descriptions: metaValidationDescriptions } } : {}
|
|
758
|
+
},
|
|
759
|
+
updated
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function shouldRefreshAutoSyncedTaskConfigEntry(entry) {
|
|
763
|
+
if (!entry || typeof entry !== "object") {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
const candidate = entry;
|
|
767
|
+
if (!candidate.auto_synced) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
if (!Array.isArray(candidate.scope) || candidate.scope.length === 0) {
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
if (candidate.scope.some((glob) => typeof glob !== "string" || glob.trim().length === 0)) {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
return !candidate.role;
|
|
777
|
+
}
|
|
778
|
+
function readSourceIssueRecords(projectRoot) {
|
|
779
|
+
const issuesPath = resolve4(resolveCheckoutRoot(projectRoot), ".beads", "issues.jsonl");
|
|
780
|
+
if (!existsSync4(issuesPath)) {
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
const records = [];
|
|
784
|
+
for (const line of readFileSync3(issuesPath, "utf-8").split(/\r?\n/)) {
|
|
785
|
+
const trimmed = line.trim();
|
|
786
|
+
if (!trimmed) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
const parsed = JSON.parse(trimmed);
|
|
791
|
+
const id = typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
792
|
+
if (!id) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
records.push({
|
|
796
|
+
id,
|
|
797
|
+
title: typeof parsed.title === "string" ? parsed.title.trim() : "",
|
|
798
|
+
issueType: typeof parsed.issue_type === "string" ? parsed.issue_type.trim() : null,
|
|
799
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.filter((label) => typeof label === "string") : []
|
|
800
|
+
});
|
|
801
|
+
} catch {}
|
|
802
|
+
}
|
|
803
|
+
return records;
|
|
804
|
+
}
|
|
805
|
+
function buildAutoSyncedTaskConfigEntry(issue) {
|
|
806
|
+
return {
|
|
807
|
+
auto_synced: true,
|
|
808
|
+
role: inferAutoSyncedTaskRole(issue),
|
|
809
|
+
scope: inferAutoSyncedTaskScope(issue),
|
|
810
|
+
validation: []
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function inferAutoSyncedTaskRole(issue) {
|
|
814
|
+
for (const label of issue.labels) {
|
|
815
|
+
if (label === "role:architect")
|
|
816
|
+
return "architect";
|
|
817
|
+
if (label === "role:extractor")
|
|
818
|
+
return "extractor";
|
|
819
|
+
if (label === "role:mechanic")
|
|
820
|
+
return "mechanic";
|
|
821
|
+
if (label === "role:verifier")
|
|
822
|
+
return "verifier";
|
|
823
|
+
}
|
|
824
|
+
if (/\bDESIGN\b/i.test(issue.title)) {
|
|
825
|
+
return "architect";
|
|
826
|
+
}
|
|
827
|
+
if (/\bInitialize\b/i.test(issue.title)) {
|
|
828
|
+
return "mechanic";
|
|
829
|
+
}
|
|
830
|
+
return "extractor";
|
|
831
|
+
}
|
|
832
|
+
function inferAutoSyncedTaskScope(issue) {
|
|
833
|
+
return [`artifacts/${issue.id}/**`];
|
|
834
|
+
}
|
|
835
|
+
function readConfiguredFileTaskConfig(projectRoot) {
|
|
836
|
+
const sourcePath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
837
|
+
if (!sourcePath) {
|
|
838
|
+
return {};
|
|
839
|
+
}
|
|
840
|
+
const directory = resolve4(projectRoot, sourcePath);
|
|
841
|
+
if (!existsSync4(directory)) {
|
|
842
|
+
return {};
|
|
843
|
+
}
|
|
844
|
+
const config = {};
|
|
845
|
+
for (const name of readdirSync(directory)) {
|
|
846
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
847
|
+
continue;
|
|
848
|
+
const file = resolve4(directory, name);
|
|
849
|
+
try {
|
|
850
|
+
if (!statSync(file).isFile())
|
|
851
|
+
continue;
|
|
852
|
+
const raw = JSON.parse(readFileSync3(file, "utf8"));
|
|
853
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
854
|
+
continue;
|
|
855
|
+
const record = raw;
|
|
856
|
+
const inferredId = basename(name).replace(FILE_TASK_PATTERN, "");
|
|
857
|
+
const id = typeof record.id === "string" && record.id.trim().length > 0 ? record.id.trim() : inferredId;
|
|
858
|
+
config[id] = fileTaskToConfigEntry(record, { kind: "files", path: sourcePath });
|
|
859
|
+
} catch {}
|
|
860
|
+
}
|
|
861
|
+
return config;
|
|
862
|
+
}
|
|
863
|
+
function fileTaskToConfigEntry(task, source) {
|
|
864
|
+
const labels = Array.isArray(task.labels) ? task.labels.filter((label) => typeof label === "string") : [];
|
|
865
|
+
const scope = firstStringList(task.scope, labels.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length)));
|
|
866
|
+
const validation = firstStringList(task.validation, task.validators, labels.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length)));
|
|
867
|
+
const roleLabel = labels.find((label) => label.startsWith("role:"));
|
|
868
|
+
const role = typeof task.role === "string" && task.role.trim().length > 0 ? task.role.trim() : roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
869
|
+
return {
|
|
870
|
+
auto_synced: true,
|
|
871
|
+
...typeof task.title === "string" ? { title: task.title } : {},
|
|
872
|
+
...typeof task.status === "string" ? { status: task.status } : {},
|
|
873
|
+
...typeof task.description === "string" ? { description: task.description } : {},
|
|
874
|
+
...typeof task.acceptance_criteria === "string" ? { acceptance_criteria: task.acceptance_criteria } : typeof task.acceptanceCriteria === "string" ? { acceptance_criteria: task.acceptanceCriteria } : {},
|
|
875
|
+
...role ? { role } : {},
|
|
876
|
+
...scope.length > 0 ? { scope } : {},
|
|
877
|
+
...validation.length > 0 ? { validation } : {},
|
|
878
|
+
_rig: { taskSource: source }
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function firstStringList(...candidates) {
|
|
882
|
+
for (const candidate of candidates) {
|
|
883
|
+
if (!Array.isArray(candidate)) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const list = candidate.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
887
|
+
if (list.length > 0) {
|
|
888
|
+
return list;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return [];
|
|
892
|
+
}
|
|
893
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
894
|
+
const jsonPath = resolve4(projectRoot, "rig.config.json");
|
|
895
|
+
if (existsSync4(jsonPath)) {
|
|
896
|
+
try {
|
|
897
|
+
const parsed = JSON.parse(readFileSync3(jsonPath, "utf8"));
|
|
898
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
899
|
+
const taskSource = parsed.taskSource;
|
|
900
|
+
if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
|
|
901
|
+
const record = taskSource;
|
|
902
|
+
return record.kind === "files" && typeof record.path === "string" ? record.path : null;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const tsPath = resolve4(projectRoot, "rig.config.ts");
|
|
910
|
+
if (!existsSync4(tsPath)) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const source = readFileSync3(tsPath, "utf8");
|
|
915
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
916
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
917
|
+
if (kind !== "files") {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
921
|
+
} catch {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function sourceTaskConfigCandidates(projectRoot) {
|
|
926
|
+
const runtimeContext = loadRuntimeContextFromEnv();
|
|
927
|
+
return [
|
|
928
|
+
runtimeContext?.monorepoMainRoot ? resolve4(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
|
|
929
|
+
process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve4(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
|
|
930
|
+
resolve4(resolveCheckoutRoot(projectRoot), ".rig", "task-config.json")
|
|
931
|
+
].filter(Boolean);
|
|
932
|
+
}
|
|
933
|
+
export {
|
|
934
|
+
synchronizeSourceTaskConfig,
|
|
935
|
+
readValidationDescriptions,
|
|
936
|
+
readTaskConfig,
|
|
937
|
+
readSourceValidationDescriptions,
|
|
938
|
+
readSourceTaskStateMetadata,
|
|
939
|
+
readSourceTaskConfig,
|
|
940
|
+
lookupTask,
|
|
941
|
+
ensureArtifactAlias,
|
|
942
|
+
currentTaskId,
|
|
943
|
+
artifactDirForId
|
|
944
|
+
};
|