@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
package/dist/src/plugin.js
CHANGED
|
@@ -1,11 +1,3875 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/types.ts
|
|
19
|
+
function normalizeTaskLifecycleStatus(status) {
|
|
20
|
+
switch (status) {
|
|
21
|
+
case "draft":
|
|
22
|
+
case "open":
|
|
23
|
+
case "ready":
|
|
24
|
+
case "queued":
|
|
25
|
+
case "in_progress":
|
|
26
|
+
case "under_review":
|
|
27
|
+
case "blocked":
|
|
28
|
+
case "completed":
|
|
29
|
+
case "cancelled":
|
|
30
|
+
return status;
|
|
31
|
+
case "closed":
|
|
32
|
+
return "completed";
|
|
33
|
+
case "running":
|
|
34
|
+
return "in_progress";
|
|
35
|
+
case "failed":
|
|
36
|
+
return "ready";
|
|
37
|
+
default:
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function normalizeTaskStateMetadataStatus(status) {
|
|
42
|
+
if (CANONICAL_TASK_LIFECYCLE_STATUSES.has(status)) {
|
|
43
|
+
return status;
|
|
44
|
+
}
|
|
45
|
+
switch (status) {
|
|
46
|
+
case "closed":
|
|
47
|
+
return "completed";
|
|
48
|
+
case "running":
|
|
49
|
+
return "in_progress";
|
|
50
|
+
case "failed":
|
|
51
|
+
return "ready";
|
|
52
|
+
default:
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function canonicalizeTaskStateMetadata(raw) {
|
|
57
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const metadata = raw;
|
|
61
|
+
const claimId = typeof metadata.claimId === "string" && metadata.claimId.length > 0 ? metadata.claimId : undefined;
|
|
62
|
+
const status = normalizeTaskStateMetadataStatus(metadata.status);
|
|
63
|
+
if (!status) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...claimId ? { claimId } : {},
|
|
68
|
+
status,
|
|
69
|
+
...typeof metadata.ownerId === "string" && metadata.ownerId.length > 0 ? { ownerId: metadata.ownerId } : {},
|
|
70
|
+
...typeof metadata.claimedAt === "string" && metadata.claimedAt.length > 0 ? { claimedAt: metadata.claimedAt } : {},
|
|
71
|
+
...typeof metadata.lastEvidenceAt === "string" && metadata.lastEvidenceAt.length > 0 ? { lastEvidenceAt: metadata.lastEvidenceAt } : {},
|
|
72
|
+
...typeof metadata.runId === "string" && metadata.runId.length > 0 ? { runId: metadata.runId } : {},
|
|
73
|
+
...typeof metadata.branchName === "string" && metadata.branchName.length > 0 ? { branchName: metadata.branchName } : {},
|
|
74
|
+
...typeof metadata.prNumber === "number" ? { prNumber: metadata.prNumber } : {},
|
|
75
|
+
...typeof metadata.prUrl === "string" && metadata.prUrl.length > 0 ? { prUrl: metadata.prUrl } : {},
|
|
76
|
+
...typeof metadata.reviewState === "string" && metadata.reviewState.length > 0 ? { reviewState: metadata.reviewState } : {},
|
|
77
|
+
...typeof metadata.blockerReason === "string" && metadata.blockerReason.length > 0 ? { blockerReason: metadata.blockerReason } : {},
|
|
78
|
+
...typeof metadata.sourceCommit === "string" && metadata.sourceCommit.length > 0 ? { sourceCommit: metadata.sourceCommit } : {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function discardMismatchedTaskStateMetadata(input) {
|
|
82
|
+
input.taskId;
|
|
83
|
+
const canonicalMetadata = canonicalizeTaskStateMetadata(input.metadata);
|
|
84
|
+
if (!canonicalMetadata || !input.lifecycleStatus) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const metadataStatus = canonicalMetadata.status ?? null;
|
|
88
|
+
if (metadataStatus && metadataStatus !== input.lifecycleStatus) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return canonicalMetadata;
|
|
92
|
+
}
|
|
93
|
+
function readTaskStateMetadataEnvelope(raw) {
|
|
94
|
+
if (!raw || typeof raw !== "object") {
|
|
95
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
96
|
+
}
|
|
97
|
+
const envelope = raw;
|
|
98
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
99
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
100
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
101
|
+
}
|
|
102
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
103
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
104
|
+
return {
|
|
105
|
+
schemaVersion,
|
|
106
|
+
supported: true,
|
|
107
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
108
|
+
tasks
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1, CANONICAL_TASK_LIFECYCLE_STATUSES;
|
|
112
|
+
var init_types = __esm(() => {
|
|
113
|
+
CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
114
|
+
"draft",
|
|
115
|
+
"open",
|
|
116
|
+
"ready",
|
|
117
|
+
"queued",
|
|
118
|
+
"in_progress",
|
|
119
|
+
"under_review",
|
|
120
|
+
"blocked",
|
|
121
|
+
"completed",
|
|
122
|
+
"cancelled"
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// packages/task-sources-plugin/src/control-plane/native/native-git.ts
|
|
127
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
128
|
+
import { tmpdir } from "os";
|
|
129
|
+
import { dirname, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
130
|
+
import { createHash } from "crypto";
|
|
131
|
+
function nativePendingFiles(repoPath) {
|
|
132
|
+
const result = runGitNative("pending-files", [repoPath]);
|
|
133
|
+
if (!result.ok)
|
|
134
|
+
return null;
|
|
135
|
+
if ("files" in result && Array.isArray(result.files)) {
|
|
136
|
+
return result.files.map((file) => ({ path: file.path, status: file.status }));
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
141
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
142
|
+
}
|
|
143
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
144
|
+
const requestDir = resolve2(taskSourcesGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
145
|
+
mkdirSync(requestDir, { recursive: true });
|
|
146
|
+
const outputPath = resolve2(requestDir, "blob.txt");
|
|
147
|
+
try {
|
|
148
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
149
|
+
return readFileSync2(outputPath, "utf8");
|
|
150
|
+
} finally {
|
|
151
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function nativeWriteTreeCommit(repoPath, baseRef, updates, message) {
|
|
155
|
+
const requestDir = resolve2(taskSourcesGitNativeOutputDir, "requests", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
156
|
+
mkdirSync(requestDir, { recursive: true });
|
|
157
|
+
const messagePath = resolve2(requestDir, "message.txt");
|
|
158
|
+
const updatesPath = resolve2(requestDir, "updates.json");
|
|
159
|
+
try {
|
|
160
|
+
writeFileSync2(messagePath, message, "utf8");
|
|
161
|
+
writeFileSync2(updatesPath, `${JSON.stringify(serializeTreeCommitUpdates(updates), null, 2)}
|
|
162
|
+
`, "utf8");
|
|
163
|
+
return requireGitNativeString("write-tree-commit", [repoPath, baseRef, messagePath, updatesPath]);
|
|
164
|
+
} finally {
|
|
165
|
+
rmSync(requestDir, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function nativePushRefWithLease(repoPath, localOid, remoteRef, expectedOldOid, remote = "origin") {
|
|
169
|
+
return requireGitNativeString("push-ref-with-lease", [repoPath, localOid, remoteRef, expectedOldOid, remote]);
|
|
170
|
+
}
|
|
171
|
+
function runGitNative(command, args) {
|
|
172
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
173
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
174
|
+
}
|
|
175
|
+
let binaryPath;
|
|
176
|
+
try {
|
|
177
|
+
binaryPath = resolveGitBinaryPath() ?? ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
return { ok: false, error: message.includes("rig-git.zig source file not found") ? "rig-git binary not found" : message };
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
184
|
+
stdout: "pipe",
|
|
185
|
+
stderr: "pipe",
|
|
186
|
+
env: gitNativeEnv()
|
|
187
|
+
});
|
|
188
|
+
if (proc.exitCode !== 0) {
|
|
189
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
190
|
+
if (stdoutText) {
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(stdoutText);
|
|
193
|
+
if (!parsed.ok) {
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
198
|
+
const error = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
199
|
+
return { ok: false, error };
|
|
200
|
+
}
|
|
201
|
+
return JSON.parse(proc.stdout.toString().trim());
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return { ok: false, error: String(error) };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function requireGitNative(command, args) {
|
|
207
|
+
const result = runGitNative(command, args);
|
|
208
|
+
if (!result.ok) {
|
|
209
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
function requireGitNativeString(command, args) {
|
|
214
|
+
const result = requireGitNative(command, args);
|
|
215
|
+
if ("value" in result && typeof result.value === "string") {
|
|
216
|
+
return result.value;
|
|
217
|
+
}
|
|
218
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
219
|
+
}
|
|
220
|
+
function serializeTreeCommitUpdates(updates) {
|
|
221
|
+
return updates.map((update) => {
|
|
222
|
+
if (typeof update.content === "string") {
|
|
223
|
+
return { path: update.path, kind: "text", content: update.content };
|
|
224
|
+
}
|
|
225
|
+
if (!isAbsolute2(update.sourceFilePath)) {
|
|
226
|
+
throw new Error("tree commit binary updates require an absolute sourceFilePath");
|
|
227
|
+
}
|
|
228
|
+
return { path: update.path, kind: "file", sourceFilePath: update.sourceFilePath };
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function gitNativeEnv() {
|
|
232
|
+
const env = { ...process.env };
|
|
233
|
+
const token = env.GITHUB_TOKEN?.trim() || env.GH_TOKEN?.trim() || env.RIG_GITHUB_TOKEN?.trim() || "";
|
|
234
|
+
if (token) {
|
|
235
|
+
env.RIG_GITHUB_TOKEN = env.RIG_GITHUB_TOKEN || token;
|
|
236
|
+
env.GITHUB_TOKEN = env.GITHUB_TOKEN || token;
|
|
237
|
+
env.GH_TOKEN = env.GH_TOKEN || token;
|
|
238
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
239
|
+
env.GIT_CONFIG_COUNT = "2";
|
|
240
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
241
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
242
|
+
env.GIT_CONFIG_KEY_1 = "credential.helper";
|
|
243
|
+
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';
|
|
244
|
+
}
|
|
245
|
+
return env;
|
|
246
|
+
}
|
|
247
|
+
function runtimeRigGitFileName() {
|
|
248
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
249
|
+
}
|
|
250
|
+
function preferredGitBinaryOutputPath() {
|
|
251
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
252
|
+
return explicit || taskSourcesGitNativeOutputPath;
|
|
253
|
+
}
|
|
254
|
+
function resolveGitBinaryPath() {
|
|
255
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
256
|
+
if (explicit && existsSync2(explicit)) {
|
|
257
|
+
return explicit;
|
|
258
|
+
}
|
|
259
|
+
if (explicit) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
263
|
+
if (candidate && existsSync2(candidate)) {
|
|
264
|
+
return candidate;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
function ensureRigGitBinaryPathSync(outputPath = taskSourcesGitNativeOutputPath) {
|
|
270
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
271
|
+
if (explicit && existsSync2(explicit)) {
|
|
272
|
+
return explicit;
|
|
273
|
+
}
|
|
274
|
+
const sourcePath = resolveGitSourcePath();
|
|
275
|
+
if (!sourcePath) {
|
|
276
|
+
const binaryPath = resolveGitBinaryPath();
|
|
277
|
+
if (binaryPath) {
|
|
278
|
+
return binaryPath;
|
|
279
|
+
}
|
|
280
|
+
throw new Error("rig-git.zig source file not found.");
|
|
281
|
+
}
|
|
282
|
+
const zigBinary = Bun.which("zig");
|
|
283
|
+
if (!zigBinary) {
|
|
284
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
285
|
+
}
|
|
286
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
287
|
+
const buildKey = JSON.stringify({
|
|
288
|
+
version: 1,
|
|
289
|
+
zigBinary,
|
|
290
|
+
platform: process.platform,
|
|
291
|
+
arch: process.arch,
|
|
292
|
+
sourcePath,
|
|
293
|
+
sourceDigest: createHash("sha256").update(readFileSync2(sourcePath)).digest("hex")
|
|
294
|
+
});
|
|
295
|
+
const manifestPath = `${outputPath}.build-manifest.json`;
|
|
296
|
+
if (nativeBuildIsFresh(outputPath, manifestPath, buildKey)) {
|
|
297
|
+
chmodSync(outputPath, 493);
|
|
298
|
+
return outputPath;
|
|
299
|
+
}
|
|
300
|
+
const tempOutputPath = resolve2(dirname(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${process.platform === "win32" ? ".exe" : ""}`);
|
|
301
|
+
const build = Bun.spawnSync([
|
|
302
|
+
zigBinary,
|
|
303
|
+
"build-exe",
|
|
304
|
+
sourcePath,
|
|
305
|
+
"-O",
|
|
306
|
+
"ReleaseFast",
|
|
307
|
+
`-femit-bin=${tempOutputPath}`
|
|
308
|
+
], {
|
|
309
|
+
cwd: dirname(sourcePath),
|
|
310
|
+
stdout: "pipe",
|
|
311
|
+
stderr: "pipe"
|
|
312
|
+
});
|
|
313
|
+
if (build.exitCode !== 0 || !existsSync2(tempOutputPath)) {
|
|
314
|
+
const details = [build.stderr.toString().trim(), build.stdout.toString().trim()].filter(Boolean).join(`
|
|
315
|
+
`);
|
|
316
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
317
|
+
}
|
|
318
|
+
chmodSync(tempOutputPath, 493);
|
|
319
|
+
renameSync(tempOutputPath, outputPath);
|
|
320
|
+
if (!binarySupportsTrackerCommands(outputPath)) {
|
|
321
|
+
rmSync(outputPath, { force: true });
|
|
322
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
323
|
+
}
|
|
324
|
+
writeFileSync2(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
325
|
+
`, "utf8");
|
|
326
|
+
return outputPath;
|
|
327
|
+
}
|
|
328
|
+
function nativeBuildIsFresh(outputPath, manifestPath, buildKey) {
|
|
329
|
+
if (!existsSync2(outputPath) || !existsSync2(manifestPath)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
334
|
+
return manifest.version === 1 && manifest.buildKey === buildKey && binarySupportsTrackerCommands(outputPath);
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function binarySupportsTrackerCommands(binaryPath) {
|
|
340
|
+
try {
|
|
341
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
342
|
+
stdout: "pipe",
|
|
343
|
+
stderr: "pipe"
|
|
344
|
+
});
|
|
345
|
+
const stdout = probe.stdout.toString().trim();
|
|
346
|
+
const stderr = probe.stderr.toString().trim();
|
|
347
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function resolveGitSourcePath() {
|
|
356
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
357
|
+
if (candidate && existsSync2(candidate)) {
|
|
358
|
+
return candidate;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function rigGitSourceCandidates() {
|
|
364
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
365
|
+
const cwd = process.cwd()?.trim() || "";
|
|
366
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
367
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
368
|
+
return [...new Set([
|
|
369
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
370
|
+
resolve2(import.meta.dir, "../../../native/rig-git.zig"),
|
|
371
|
+
projectRoot ? resolve2(projectRoot, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
372
|
+
projectRoot ? resolve2(projectRoot, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
373
|
+
projectRoot ? resolve2(projectRoot, "packages/shared/native/rig-git.zig") : "",
|
|
374
|
+
hostProjectRoot ? resolve2(hostProjectRoot, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
375
|
+
hostProjectRoot ? resolve2(hostProjectRoot, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
376
|
+
hostProjectRoot ? resolve2(hostProjectRoot, "packages/shared/native/rig-git.zig") : "",
|
|
377
|
+
cwd ? resolve2(cwd, "packages/task-sources-plugin/native/rig-git.zig") : "",
|
|
378
|
+
cwd ? resolve2(cwd, "packages/isolation-plugin/native/rig-git.zig") : "",
|
|
379
|
+
cwd ? resolve2(cwd, "packages/shared/native/rig-git.zig") : "",
|
|
380
|
+
execDir ? resolve2(execDir, "..", "native", "rig-git.zig") : ""
|
|
381
|
+
].filter(Boolean))];
|
|
382
|
+
}
|
|
383
|
+
function rigGitBinaryCandidates() {
|
|
384
|
+
const execDir = process.execPath?.trim() ? dirname(process.execPath.trim()) : "";
|
|
385
|
+
const fileName = runtimeRigGitFileName();
|
|
386
|
+
return [...new Set([
|
|
387
|
+
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
388
|
+
execDir ? resolve2(execDir, fileName) : "",
|
|
389
|
+
execDir ? resolve2(execDir, "..", fileName) : "",
|
|
390
|
+
execDir ? resolve2(execDir, "..", "bin", fileName) : "",
|
|
391
|
+
taskSourcesGitNativeOutputPath
|
|
392
|
+
].filter(Boolean))];
|
|
393
|
+
}
|
|
394
|
+
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
395
|
+
const candidates = [];
|
|
396
|
+
let cursor = resolve2(fromDir);
|
|
397
|
+
for (let index = 0;index < 8; index += 1) {
|
|
398
|
+
candidates.push(resolve2(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve2(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve2(cursor, "native", fileName), resolve2(cursor, "native", "bin", fileName));
|
|
399
|
+
const parent = dirname(cursor);
|
|
400
|
+
if (parent === cursor)
|
|
401
|
+
break;
|
|
402
|
+
cursor = parent;
|
|
403
|
+
}
|
|
404
|
+
return candidates;
|
|
405
|
+
}
|
|
406
|
+
var taskSourcesGitNativeOutputDir, taskSourcesGitNativeOutputPath, trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
407
|
+
var init_native_git = __esm(() => {
|
|
408
|
+
taskSourcesGitNativeOutputDir = resolve2(tmpdir(), "rig-task-sources-native");
|
|
409
|
+
taskSourcesGitNativeOutputPath = resolve2(taskSourcesGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/native-git.ts
|
|
413
|
+
var init_native_git2 = __esm(() => {
|
|
414
|
+
init_native_git();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// packages/task-sources-plugin/src/control-plane/native/utils.ts
|
|
418
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
419
|
+
import { unique } from "@rig/core/exec";
|
|
420
|
+
import {
|
|
421
|
+
runCapture,
|
|
422
|
+
runCaptureAsync,
|
|
423
|
+
runInherit,
|
|
424
|
+
getValidationTimeoutMs,
|
|
425
|
+
readJsonFile,
|
|
426
|
+
nowIso,
|
|
427
|
+
unique as unique2,
|
|
428
|
+
fileLines
|
|
429
|
+
} from "@rig/core/exec";
|
|
430
|
+
import { resolveCheckoutRoot } from "@rig/core/checkout-root";
|
|
431
|
+
import { resolveHarnessPaths } from "@rig/core/harness-paths";
|
|
432
|
+
function normalizeRelativeScopePath(inputPath) {
|
|
433
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
434
|
+
const rules = getScopeRules();
|
|
435
|
+
if (rules?.stripPrefixes) {
|
|
436
|
+
for (const prefix of rules.stripPrefixes) {
|
|
437
|
+
if (normalized.startsWith(prefix)) {
|
|
438
|
+
normalized = normalized.slice(prefix.length);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return normalized;
|
|
443
|
+
}
|
|
444
|
+
function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
|
|
445
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
446
|
+
if (normalized.startsWith(projectRoot + "/")) {
|
|
447
|
+
normalized = normalized.slice(projectRoot.length + 1);
|
|
448
|
+
}
|
|
449
|
+
if (normalized.startsWith(monorepoRoot + "/")) {
|
|
450
|
+
normalized = normalized.slice(monorepoRoot.length + 1);
|
|
451
|
+
}
|
|
452
|
+
return normalizeRelativeScopePath(normalized);
|
|
453
|
+
}
|
|
454
|
+
function monorepoSearchCandidates(inputPath) {
|
|
455
|
+
const normalized = inputPath.replace(/^\.\//, "");
|
|
456
|
+
const candidates = new Set;
|
|
457
|
+
const add = (value) => {
|
|
458
|
+
const trimmed = value.replace(/^\.\//, "");
|
|
459
|
+
if (trimmed) {
|
|
460
|
+
candidates.add(trimmed);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
add(normalized);
|
|
464
|
+
const stripped = normalizeRelativeScopePath(normalized);
|
|
465
|
+
add(stripped);
|
|
466
|
+
const rules = getScopeRules();
|
|
467
|
+
if (rules?.stripPrefixes) {
|
|
468
|
+
for (const prefix of rules.stripPrefixes) {
|
|
469
|
+
if (normalized.startsWith(prefix)) {
|
|
470
|
+
add(normalized.slice(prefix.length));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (rules?.searchPrefixes) {
|
|
475
|
+
for (const rule of rules.searchPrefixes) {
|
|
476
|
+
const matchesStart = rule.matchStartsWith?.some((prefix) => stripped.startsWith(prefix)) ?? false;
|
|
477
|
+
const matchesExact = rule.matchExact?.includes(stripped) ?? false;
|
|
478
|
+
if (matchesStart || matchesExact) {
|
|
479
|
+
add(`${rule.prefix}${stripped}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return [...candidates];
|
|
484
|
+
}
|
|
485
|
+
var scopeRegexCache;
|
|
486
|
+
var init_utils = __esm(() => {
|
|
487
|
+
scopeRegexCache = new Map;
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/repo.ts
|
|
491
|
+
import { existsSync as existsSync3 } from "fs";
|
|
492
|
+
import { resolve as resolve3 } from "path";
|
|
493
|
+
import { MANAGED_REPO_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
494
|
+
import { defineCapability } from "@rig/core/capability";
|
|
495
|
+
import { getInstalledCapability } from "@rig/core/capability-loaders";
|
|
496
|
+
function resolveTrackerRepoPath(projectRoot) {
|
|
497
|
+
const monorepoRoot = resolveCheckoutRoot(projectRoot);
|
|
498
|
+
const managedRepos = getInstalledCapability(defineCapability(MANAGED_REPO_SERVICE_CAPABILITY));
|
|
499
|
+
if (managedRepos) {
|
|
500
|
+
try {
|
|
501
|
+
const layout = managedRepos.resolveMonorepoRepoLayout(projectRoot);
|
|
502
|
+
if (existsSync3(resolve3(layout.mirrorRoot, "HEAD"))) {
|
|
503
|
+
return layout.mirrorRoot;
|
|
504
|
+
}
|
|
505
|
+
} catch {}
|
|
506
|
+
}
|
|
507
|
+
return monorepoRoot;
|
|
508
|
+
}
|
|
509
|
+
var init_repo = __esm(() => {
|
|
510
|
+
init_utils();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/read.ts
|
|
514
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
515
|
+
import { resolve as resolve4 } from "path";
|
|
516
|
+
import { RUNTIME_CONTEXT_ENV } from "@rig/core/runtime-context";
|
|
517
|
+
function parseIssueStatus(rawStatus) {
|
|
518
|
+
const normalized = normalizeTaskLifecycleStatus(rawStatus);
|
|
519
|
+
return normalized ?? "unknown";
|
|
520
|
+
}
|
|
521
|
+
function parseIssueDependencies(raw) {
|
|
522
|
+
if (!Array.isArray(raw)) {
|
|
523
|
+
return [];
|
|
524
|
+
}
|
|
525
|
+
return raw.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry)).map((entry) => ({
|
|
526
|
+
issueId: typeof entry.issue_id === "string" && entry.issue_id.trim() ? entry.issue_id.trim() : null,
|
|
527
|
+
dependsOnId: typeof entry.depends_on_id === "string" && entry.depends_on_id.trim() ? entry.depends_on_id.trim() : null,
|
|
528
|
+
id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
529
|
+
type: typeof entry.type === "string" && entry.type.trim() ? entry.type.trim() : null
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
function dependencyTargetId(dependency) {
|
|
533
|
+
for (const candidate of [dependency.dependsOnId, dependency.id ?? null, dependency.issueId]) {
|
|
534
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
535
|
+
return candidate.trim();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
function parseIssuesJsonl(raw) {
|
|
541
|
+
const issues = [];
|
|
542
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
543
|
+
const trimmed = line.trim();
|
|
544
|
+
if (!trimmed) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const record = JSON.parse(trimmed);
|
|
549
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
|
|
550
|
+
if (!id) {
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const rawStatus = typeof record.status === "string" && record.status.trim() ? record.status.trim() : null;
|
|
554
|
+
issues.push({
|
|
555
|
+
id,
|
|
556
|
+
title: typeof record.title === "string" && record.title.trim() ? record.title.trim() : null,
|
|
557
|
+
description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
|
|
558
|
+
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,
|
|
559
|
+
issueType: typeof record.issue_type === "string" && record.issue_type.trim() ? record.issue_type.trim() : null,
|
|
560
|
+
status: parseIssueStatus(rawStatus),
|
|
561
|
+
rawStatus,
|
|
562
|
+
priority: typeof record.priority === "number" ? record.priority : null,
|
|
563
|
+
dependencies: parseIssueDependencies(record.dependencies)
|
|
564
|
+
});
|
|
565
|
+
} catch {}
|
|
566
|
+
}
|
|
567
|
+
return issues;
|
|
568
|
+
}
|
|
569
|
+
function parseTaskStateEnvelope(raw) {
|
|
570
|
+
if (!raw || !raw.trim()) {
|
|
571
|
+
return readTaskStateMetadataEnvelope(null);
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
return readTaskStateMetadataEnvelope(JSON.parse(raw));
|
|
575
|
+
} catch {
|
|
576
|
+
return readTaskStateMetadataEnvelope(null);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function readRemoteBlobAtRef(deps, repoPath, ref, path, options) {
|
|
580
|
+
try {
|
|
581
|
+
return deps.readBlobAtRef(repoPath, ref, path);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
if (options.required) {
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function shouldPreferLocalTrackerState(options) {
|
|
590
|
+
if (!options.allowLocalFallback) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
594
|
+
if (!runtimeWorkspace) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
if (process.env.RIG_TASK_RUNTIME_ID?.trim()) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
const runtimeContextPath = process.env[RUNTIME_CONTEXT_ENV]?.trim();
|
|
601
|
+
if (runtimeContextPath) {
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
return existsSync4(resolve4(runtimeWorkspace, ".rig", "runtime-context.json"));
|
|
605
|
+
}
|
|
606
|
+
function readLocalTrackerState(projectRoot, deps) {
|
|
607
|
+
const monorepoRoot = resolveCheckoutRoot(projectRoot);
|
|
608
|
+
const issuesPath = resolve4(monorepoRoot, ".beads", "issues.jsonl");
|
|
609
|
+
const taskStatePath = resolve4(monorepoRoot, ".beads", "task-state.json");
|
|
610
|
+
return projectSyncedTrackerSnapshot({
|
|
611
|
+
source: "local",
|
|
612
|
+
issuesBaseOid: null,
|
|
613
|
+
issuesText: deps.exists(issuesPath) ? deps.readFile(issuesPath) : "",
|
|
614
|
+
taskStateBaseOid: null,
|
|
615
|
+
taskStateText: deps.exists(taskStatePath) ? deps.readFile(taskStatePath) : null
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function projectSyncedTrackerSnapshot(input) {
|
|
619
|
+
if (input.source === "remote" && input.issuesBaseOid && input.taskStateBaseOid && input.issuesBaseOid !== input.taskStateBaseOid) {
|
|
620
|
+
throw new Error("Remote tracker files must be read from the same fetched base.");
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
source: input.source,
|
|
624
|
+
baseOid: input.issuesBaseOid ?? input.taskStateBaseOid ?? null,
|
|
625
|
+
issues: parseIssuesJsonl(input.issuesText),
|
|
626
|
+
taskState: parseTaskStateEnvelope(input.taskStateText)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function readSyncedTrackerState(projectRoot, deps = {}, options = {}) {
|
|
630
|
+
const readDeps = { ...DEFAULT_READ_DEPS, ...deps };
|
|
631
|
+
const trackerRepoPath = resolveTrackerRepoPath(projectRoot);
|
|
632
|
+
if (shouldPreferLocalTrackerState(options)) {
|
|
633
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
const baseOid = readDeps.fetchRef(trackerRepoPath, "origin", "main");
|
|
637
|
+
return projectSyncedTrackerSnapshot({
|
|
638
|
+
source: "remote",
|
|
639
|
+
issuesBaseOid: baseOid,
|
|
640
|
+
issuesText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/issues.jsonl", {
|
|
641
|
+
required: true
|
|
642
|
+
}) ?? "",
|
|
643
|
+
taskStateBaseOid: baseOid,
|
|
644
|
+
taskStateText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/task-state.json", {
|
|
645
|
+
required: false
|
|
646
|
+
})
|
|
647
|
+
});
|
|
648
|
+
} catch (error) {
|
|
649
|
+
if (!options.allowLocalFallback) {
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function listReadyTaskIdsFromTracker(snapshot) {
|
|
656
|
+
const tasks = snapshot.issues.filter((issue) => issue.issueType === "task");
|
|
657
|
+
const taskById = new Map(tasks.map((issue) => [issue.id, issue]));
|
|
658
|
+
const readyTaskIds = [];
|
|
659
|
+
for (const issue of tasks) {
|
|
660
|
+
if (issue.status === "ready") {
|
|
661
|
+
readyTaskIds.push(issue.id);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (issue.status !== "open") {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const hasOpenBlocker = issue.dependencies.some((dependency) => {
|
|
668
|
+
if (dependency.type === "parent-child") {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
const targetTaskId = dependencyTargetId(dependency);
|
|
672
|
+
if (!targetTaskId) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
const dependencyStatus = taskById.get(targetTaskId)?.status ?? "unknown";
|
|
676
|
+
return dependencyStatus !== "completed" && dependencyStatus !== "cancelled";
|
|
677
|
+
});
|
|
678
|
+
if (!hasOpenBlocker) {
|
|
679
|
+
readyTaskIds.push(issue.id);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return readyTaskIds;
|
|
683
|
+
}
|
|
684
|
+
var DEFAULT_READ_DEPS;
|
|
685
|
+
var init_read = __esm(() => {
|
|
686
|
+
init_native_git2();
|
|
687
|
+
init_utils();
|
|
688
|
+
init_repo();
|
|
689
|
+
init_types();
|
|
690
|
+
DEFAULT_READ_DEPS = {
|
|
691
|
+
fetchRef: nativeFetchRef,
|
|
692
|
+
readBlobAtRef: nativeReadBlobAtRef,
|
|
693
|
+
exists: existsSync4,
|
|
694
|
+
readFile: (path) => readFileSync3(path, "utf8")
|
|
695
|
+
};
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/reconcile.ts
|
|
699
|
+
var STALE_CLAIM_MS;
|
|
700
|
+
var init_reconcile = __esm(() => {
|
|
701
|
+
STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/write.ts
|
|
705
|
+
import { isDeepStrictEqual } from "util";
|
|
706
|
+
function parseIssuesJsonl2(raw) {
|
|
707
|
+
const rows = [];
|
|
708
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
709
|
+
const trimmed = line.trim();
|
|
710
|
+
if (!trimmed) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
const parsed = JSON.parse(trimmed);
|
|
715
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
716
|
+
rows.push(parsed);
|
|
717
|
+
}
|
|
718
|
+
} catch {}
|
|
719
|
+
}
|
|
720
|
+
return rows;
|
|
721
|
+
}
|
|
722
|
+
function parseRawTaskStateFile(raw) {
|
|
723
|
+
if (!raw || !raw.trim()) {
|
|
724
|
+
return {
|
|
725
|
+
schemaVersion: 1,
|
|
726
|
+
tasks: {}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const parsed = JSON.parse(raw);
|
|
731
|
+
return {
|
|
732
|
+
schemaVersion: typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : 1,
|
|
733
|
+
tasks: parsed.tasks && typeof parsed.tasks === "object" && !Array.isArray(parsed.tasks) ? parsed.tasks : {}
|
|
734
|
+
};
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function parseTaskStateEnvelope2(raw, baseOid) {
|
|
740
|
+
if (!raw || !raw.trim()) {
|
|
741
|
+
return {
|
|
742
|
+
schemaVersion: 1,
|
|
743
|
+
supported: true,
|
|
744
|
+
baseTrackerCommit: baseOid,
|
|
745
|
+
tasks: {}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
const parsed = readTaskStateMetadataEnvelope(JSON.parse(raw));
|
|
750
|
+
return {
|
|
751
|
+
schemaVersion: parsed.supported ? parsed.schemaVersion : 1,
|
|
752
|
+
supported: true,
|
|
753
|
+
baseTrackerCommit: parsed.baseTrackerCommit ?? baseOid,
|
|
754
|
+
tasks: parsed.supported ? parsed.tasks : {}
|
|
755
|
+
};
|
|
756
|
+
} catch {
|
|
757
|
+
return {
|
|
758
|
+
schemaVersion: 1,
|
|
759
|
+
supported: true,
|
|
760
|
+
baseTrackerCommit: baseOid,
|
|
761
|
+
tasks: {}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function sanitizeTaskStateEnvelope(issueRows, envelope) {
|
|
766
|
+
const lifecycleByTaskId = new Map;
|
|
767
|
+
for (const row of issueRows) {
|
|
768
|
+
const taskId = typeof row.id === "string" ? row.id : null;
|
|
769
|
+
if (!taskId) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
lifecycleByTaskId.set(taskId, normalizeTaskLifecycleStatus(row.status));
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
...envelope,
|
|
776
|
+
tasks: Object.fromEntries(Object.entries(envelope.tasks).map(([taskId, metadata]) => [
|
|
777
|
+
taskId,
|
|
778
|
+
discardMismatchedTaskStateMetadata({
|
|
779
|
+
taskId,
|
|
780
|
+
lifecycleStatus: lifecycleByTaskId.get(taskId) ?? null,
|
|
781
|
+
metadata
|
|
782
|
+
})
|
|
783
|
+
]).filter((entry) => entry[1] != null))
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function serializeIssuesJsonl(rows) {
|
|
787
|
+
return `${rows.map((row) => JSON.stringify(row)).join(`
|
|
788
|
+
`)}
|
|
789
|
+
`;
|
|
790
|
+
}
|
|
791
|
+
function serializeTaskStateEnvelope(envelope) {
|
|
792
|
+
return `${JSON.stringify({
|
|
793
|
+
schemaVersion: envelope.schemaVersion,
|
|
794
|
+
baseTrackerCommit: envelope.baseTrackerCommit,
|
|
795
|
+
tasks: envelope.tasks
|
|
796
|
+
}, null, 2)}
|
|
797
|
+
`;
|
|
798
|
+
}
|
|
799
|
+
function findIssueRowIndex(rows, taskId) {
|
|
800
|
+
return rows.findIndex((row) => typeof row.id === "string" && row.id === taskId);
|
|
801
|
+
}
|
|
802
|
+
function assertWritableTrackerStateBases(state) {
|
|
803
|
+
if (state.issuesBaseOid !== state.taskStateBaseOid) {
|
|
804
|
+
throw new TrackerStateMutationError("Tracker writes require issues.jsonl and task-state.json from the same remote base.", "same-base-required");
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function readWritableTrackerState(projectRoot, deps) {
|
|
808
|
+
const repoPath = resolveTrackerRepoPath(projectRoot);
|
|
809
|
+
const baseOid = deps.fetchRef(repoPath, "origin", "main");
|
|
810
|
+
const issuesText = deps.readBlobAtRef(repoPath, baseOid, ".beads/issues.jsonl");
|
|
811
|
+
const taskStateText = (() => {
|
|
812
|
+
try {
|
|
813
|
+
return deps.readBlobAtRef(repoPath, baseOid, ".beads/task-state.json");
|
|
814
|
+
} catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
})();
|
|
818
|
+
return {
|
|
819
|
+
repoPath,
|
|
820
|
+
baseOid,
|
|
821
|
+
issuesBaseOid: baseOid,
|
|
822
|
+
taskStateBaseOid: baseOid,
|
|
823
|
+
issueRows: parseIssuesJsonl2(issuesText),
|
|
824
|
+
taskStateEnvelope: sanitizeTaskStateEnvelope(parseIssuesJsonl2(issuesText), parseTaskStateEnvelope2(taskStateText, baseOid))
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function cloneWritableState(state) {
|
|
828
|
+
return {
|
|
829
|
+
...state,
|
|
830
|
+
issueRows: state.issueRows.map((row) => ({ ...row })),
|
|
831
|
+
taskStateEnvelope: {
|
|
832
|
+
...state.taskStateEnvelope,
|
|
833
|
+
tasks: Object.fromEntries(Object.entries(state.taskStateEnvelope.tasks).map(([taskId, metadata]) => [taskId, { ...metadata }]))
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function currentLifecycleStatus(state, taskId) {
|
|
838
|
+
const issueIndex = findIssueRowIndex(state.issueRows, taskId);
|
|
839
|
+
if (issueIndex === -1) {
|
|
840
|
+
throw new TrackerStateMutationError(`Task ${taskId} was not found in issues.jsonl.`, "task-not-found");
|
|
841
|
+
}
|
|
842
|
+
const status = normalizeTaskLifecycleStatus(state.issueRows[issueIndex]?.status);
|
|
843
|
+
if (!status) {
|
|
844
|
+
throw new TrackerStateMutationError(`Task ${taskId} has an invalid lifecycle status.`, "invalid-task-status");
|
|
845
|
+
}
|
|
846
|
+
return status;
|
|
847
|
+
}
|
|
848
|
+
function setLifecycleStatus(state, taskId, status) {
|
|
849
|
+
const issueIndex = findIssueRowIndex(state.issueRows, taskId);
|
|
850
|
+
if (issueIndex === -1) {
|
|
851
|
+
throw new TrackerStateMutationError(`Task ${taskId} was not found in issues.jsonl.`, "task-not-found");
|
|
852
|
+
}
|
|
853
|
+
state.issueRows[issueIndex] = {
|
|
854
|
+
...state.issueRows[issueIndex],
|
|
855
|
+
status
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function prepareLifecycleTaskMutation(state, input) {
|
|
859
|
+
assertWritableTrackerStateBases(state);
|
|
860
|
+
const next = cloneWritableState(state);
|
|
861
|
+
const lifecycleStatus = currentLifecycleStatus(next, input.taskId);
|
|
862
|
+
if (input.allowedFrom && !input.allowedFrom.includes(lifecycleStatus)) {
|
|
863
|
+
throw new TrackerStateMutationError(`Task ${input.taskId} is ${lifecycleStatus}, expected one of ${input.allowedFrom.join(", ")}.`, "status-conflict");
|
|
864
|
+
}
|
|
865
|
+
setLifecycleStatus(next, input.taskId, input.status);
|
|
866
|
+
next.taskStateEnvelope.baseTrackerCommit = next.baseOid;
|
|
867
|
+
if (input.clearMetadata || input.metadata == null) {
|
|
868
|
+
delete next.taskStateEnvelope.tasks[input.taskId];
|
|
869
|
+
} else {
|
|
870
|
+
next.taskStateEnvelope.tasks[input.taskId] = input.metadata;
|
|
871
|
+
}
|
|
872
|
+
return next;
|
|
873
|
+
}
|
|
874
|
+
function desiredTaskStateSatisfied(raw, desired) {
|
|
875
|
+
const issue = parseIssuesJsonl2(raw.issuesText).find((entry) => entry.id === desired.taskId);
|
|
876
|
+
if (!issue || issue.status !== desired.status) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
const rawTaskState = parseRawTaskStateFile(raw.taskStateText);
|
|
880
|
+
if (!rawTaskState || rawTaskState.schemaVersion !== 1) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
const rawMetadata = rawTaskState.tasks[desired.taskId] ?? null;
|
|
884
|
+
const snapshotMetadata = discardMismatchedTaskStateMetadata({
|
|
885
|
+
taskId: desired.taskId,
|
|
886
|
+
lifecycleStatus: desired.status,
|
|
887
|
+
metadata: rawMetadata
|
|
888
|
+
});
|
|
889
|
+
if (desired.metadata == null) {
|
|
890
|
+
return rawMetadata == null && snapshotMetadata == null;
|
|
891
|
+
}
|
|
892
|
+
if (rawMetadata == null || snapshotMetadata == null) {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
return isDeepStrictEqual(snapshotMetadata ?? null, desired.metadata);
|
|
896
|
+
}
|
|
897
|
+
function persistTrackerState(projectRoot, state, desired, message, deps) {
|
|
898
|
+
const updates = [
|
|
899
|
+
{ path: ".beads/issues.jsonl", content: serializeIssuesJsonl(state.issueRows) },
|
|
900
|
+
{ path: ".beads/task-state.json", content: serializeTaskStateEnvelope(state.taskStateEnvelope) }
|
|
901
|
+
];
|
|
902
|
+
const commitOid = deps.writeTreeCommit(state.repoPath, state.baseOid, updates, message);
|
|
903
|
+
try {
|
|
904
|
+
deps.pushRefWithLease(state.repoPath, commitOid, "refs/heads/main", state.baseOid);
|
|
905
|
+
return {
|
|
906
|
+
outcome: "applied",
|
|
907
|
+
snapshot: deps.readSnapshot(projectRoot),
|
|
908
|
+
commitOid
|
|
909
|
+
};
|
|
910
|
+
} catch (error) {
|
|
911
|
+
const latestBaseOid = deps.fetchRef(state.repoPath, "origin", "main");
|
|
912
|
+
const latestIssuesText = deps.readBlobAtRef(state.repoPath, latestBaseOid, ".beads/issues.jsonl");
|
|
913
|
+
const latestTaskStateText = (() => {
|
|
914
|
+
try {
|
|
915
|
+
return deps.readBlobAtRef(state.repoPath, latestBaseOid, ".beads/task-state.json");
|
|
916
|
+
} catch {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
})();
|
|
920
|
+
const latestSnapshot = deps.readSnapshot(projectRoot);
|
|
921
|
+
if (desiredTaskStateSatisfied({
|
|
922
|
+
issuesText: latestIssuesText,
|
|
923
|
+
taskStateText: latestTaskStateText
|
|
924
|
+
}, desired)) {
|
|
925
|
+
return {
|
|
926
|
+
outcome: "noop",
|
|
927
|
+
snapshot: latestSnapshot,
|
|
928
|
+
commitOid: null
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
throw new TrackerStateMutationError(error instanceof Error ? error.message : String(error), "push-conflict");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function updateRemoteTrackerTaskLifecycle(projectRoot, input, deps = {}) {
|
|
935
|
+
const writerDeps = { ...DEFAULT_WRITER_DEPS, ...deps };
|
|
936
|
+
const nextState = prepareLifecycleTaskMutation(readWritableTrackerState(projectRoot, writerDeps), input);
|
|
937
|
+
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);
|
|
938
|
+
}
|
|
939
|
+
var DEFAULT_WRITER_DEPS, TrackerStateMutationError;
|
|
940
|
+
var init_write = __esm(() => {
|
|
941
|
+
init_native_git2();
|
|
942
|
+
init_read();
|
|
943
|
+
init_reconcile();
|
|
944
|
+
init_types();
|
|
945
|
+
init_repo();
|
|
946
|
+
DEFAULT_WRITER_DEPS = {
|
|
947
|
+
fetchRef: nativeFetchRef,
|
|
948
|
+
readBlobAtRef: nativeReadBlobAtRef,
|
|
949
|
+
writeTreeCommit: nativeWriteTreeCommit,
|
|
950
|
+
pushRefWithLease: nativePushRefWithLease,
|
|
951
|
+
readSnapshot: readSyncedTrackerState,
|
|
952
|
+
createClaimId: () => `claim-${crypto.randomUUID()}`,
|
|
953
|
+
now: () => new Date().toISOString()
|
|
954
|
+
};
|
|
955
|
+
TrackerStateMutationError = class TrackerStateMutationError extends Error {
|
|
956
|
+
code;
|
|
957
|
+
constructor(message, code) {
|
|
958
|
+
super(message);
|
|
959
|
+
this.code = code;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// packages/task-sources-plugin/src/control-plane/state-sync/index.ts
|
|
965
|
+
var init_state_sync = __esm(() => {
|
|
966
|
+
init_types();
|
|
967
|
+
init_read();
|
|
968
|
+
init_reconcile();
|
|
969
|
+
init_write();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// packages/task-sources-plugin/src/control-plane/native/task-state.ts
|
|
973
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
974
|
+
import { basename as basename2, resolve as resolve5 } from "path";
|
|
975
|
+
import { assertPathInsideRoot, safePathSegment } from "@rig/core/safe-identifiers";
|
|
976
|
+
import { loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
|
|
977
|
+
function readTaskConfig(projectRoot) {
|
|
978
|
+
const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
|
|
979
|
+
return stripTaskConfigMetadata(raw);
|
|
980
|
+
}
|
|
981
|
+
function readSourceTaskConfig(projectRoot) {
|
|
982
|
+
const raw = readAndSyncSourceTaskConfig(projectRoot);
|
|
983
|
+
return stripTaskConfigMetadata(raw);
|
|
984
|
+
}
|
|
985
|
+
function readValidationDescriptions(projectRoot) {
|
|
986
|
+
const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
|
|
987
|
+
return readValidationDescriptionMap(raw);
|
|
988
|
+
}
|
|
989
|
+
function readSourceValidationDescriptions(projectRoot) {
|
|
990
|
+
const rootRaw = readJsonFile(resolve5(projectRoot, "rig", "task-config.json"), {});
|
|
991
|
+
const sourcePath = findSourceTaskConfigPath(projectRoot);
|
|
992
|
+
const sourceRaw = sourcePath ? readJsonFile(sourcePath, {}) : {};
|
|
993
|
+
const rootDescriptions = readValidationDescriptionMap(rootRaw);
|
|
994
|
+
const sourceDescriptions = readValidationDescriptionMap(sourceRaw);
|
|
995
|
+
return {
|
|
996
|
+
...rootDescriptions,
|
|
997
|
+
...sourceDescriptions
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function currentTaskId(projectRoot) {
|
|
1001
|
+
const fromEnv = (process.env.RIG_TASK_ID || "").trim();
|
|
1002
|
+
if (fromEnv) {
|
|
1003
|
+
return fromEnv;
|
|
1004
|
+
}
|
|
1005
|
+
const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
|
|
1006
|
+
if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
|
|
1007
|
+
return runtimeId.slice("task-".length);
|
|
1008
|
+
}
|
|
1009
|
+
const workspace = (process.env.RIG_TASK_WORKSPACE || "").trim();
|
|
1010
|
+
const inferredFromWorkspace = inferTaskIdFromRuntimePath(workspace);
|
|
1011
|
+
if (inferredFromWorkspace) {
|
|
1012
|
+
return inferredFromWorkspace;
|
|
1013
|
+
}
|
|
1014
|
+
const inferredFromCwd = inferTaskIdFromRuntimePath(process.cwd());
|
|
1015
|
+
if (inferredFromCwd) {
|
|
1016
|
+
return inferredFromCwd;
|
|
1017
|
+
}
|
|
1018
|
+
try {
|
|
1019
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1020
|
+
const session = readJsonFile(paths.sessionPath, {});
|
|
1021
|
+
return session.activeTaskIds?.[0] || "";
|
|
1022
|
+
} catch {
|
|
1023
|
+
return "";
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function readSourceTaskStateMetadata(projectRoot, taskId, lifecycleStatus, snapshot) {
|
|
1027
|
+
const syncedSnapshot = snapshot ?? readSyncedTrackerState(projectRoot);
|
|
1028
|
+
const syncedIssue = syncedSnapshot.issues.find((issue) => issue.id === taskId) ?? null;
|
|
1029
|
+
const syncedLifecycleStatus = syncedIssue && syncedIssue.status !== "unknown" ? syncedIssue.status : readLocalSourceTaskLifecycleStatus(projectRoot, taskId);
|
|
1030
|
+
const metadata = syncedSnapshot.taskState.tasks[taskId] ?? readLocalSourceTaskStateEnvelope(projectRoot).tasks[taskId] ?? null;
|
|
1031
|
+
return discardMismatchedTaskStateMetadata({
|
|
1032
|
+
taskId,
|
|
1033
|
+
lifecycleStatus: syncedLifecycleStatus ?? normalizeTaskLifecycleStatus(lifecycleStatus),
|
|
1034
|
+
metadata
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function readValidationDescriptionMap(raw) {
|
|
1038
|
+
return {
|
|
1039
|
+
...coerceValidationDescriptions(raw.validation_descriptions),
|
|
1040
|
+
...coerceValidationDescriptions(readValidationDescriptionsFromMeta(raw._meta))
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function stripTaskConfigMetadata(raw) {
|
|
1044
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1045
|
+
return tasks;
|
|
1046
|
+
}
|
|
1047
|
+
function coerceValidationDescriptions(candidate) {
|
|
1048
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
1049
|
+
return {};
|
|
1050
|
+
}
|
|
1051
|
+
const descriptions = {};
|
|
1052
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
1053
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1054
|
+
descriptions[key] = value;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return descriptions;
|
|
1058
|
+
}
|
|
1059
|
+
function readValidationDescriptionsFromMeta(meta) {
|
|
1060
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
return meta.validation_descriptions;
|
|
1064
|
+
}
|
|
1065
|
+
function readLocalSourceTaskStateEnvelope(projectRoot) {
|
|
1066
|
+
const taskStatePath = resolve5(resolveCheckoutRoot(projectRoot), ".beads", "task-state.json");
|
|
1067
|
+
return readTaskStateMetadataEnvelope(readJsonFile(taskStatePath, {}));
|
|
1068
|
+
}
|
|
1069
|
+
function readLocalSourceTaskLifecycleStatus(projectRoot, taskId) {
|
|
1070
|
+
const issuesPath = resolve5(resolveCheckoutRoot(projectRoot), ".beads", "issues.jsonl");
|
|
1071
|
+
if (!existsSync5(issuesPath)) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
for (const line of readFileSync4(issuesPath, "utf8").split(/\r?\n/)) {
|
|
1075
|
+
const trimmed = line.trim();
|
|
1076
|
+
if (!trimmed) {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
const parsed = JSON.parse(trimmed);
|
|
1081
|
+
if (parsed.id === taskId && parsed.issue_type === "task") {
|
|
1082
|
+
return normalizeTaskLifecycleStatus(parsed.status);
|
|
1083
|
+
}
|
|
1084
|
+
} catch {}
|
|
1085
|
+
}
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
function inferTaskIdFromRuntimePath(path) {
|
|
1089
|
+
if (!path) {
|
|
1090
|
+
return "";
|
|
1091
|
+
}
|
|
1092
|
+
const match = path.match(/\/\.rig\/runtime\/agents\/task-([^/]+)\/worktree(?:\/|$)/) || path.match(/\/\.worktrees\/([^/]+)(?:\/|$)/);
|
|
1093
|
+
const candidate = match?.[1] || "";
|
|
1094
|
+
return candidate.startsWith("bd-") ? candidate : "";
|
|
1095
|
+
}
|
|
1096
|
+
function lookupTask(projectRoot, input) {
|
|
1097
|
+
if (!input) {
|
|
1098
|
+
return "";
|
|
1099
|
+
}
|
|
1100
|
+
if (!input.startsWith("bd-")) {
|
|
1101
|
+
return input;
|
|
1102
|
+
}
|
|
1103
|
+
try {
|
|
1104
|
+
const taskConfig2 = readTaskConfig(projectRoot);
|
|
1105
|
+
if (taskConfig2[input]) {
|
|
1106
|
+
return input;
|
|
1107
|
+
}
|
|
1108
|
+
} catch {}
|
|
1109
|
+
const taskConfig = readSourceTaskConfig(projectRoot);
|
|
1110
|
+
return taskConfig[input] ? input : "";
|
|
1111
|
+
}
|
|
1112
|
+
function artifactDirForId(projectRoot, id) {
|
|
1113
|
+
const safeId = safePathSegment(id, { fallback: "task", maxLength: 96 });
|
|
1114
|
+
const workspaceDir = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
1115
|
+
if (workspaceDir) {
|
|
1116
|
+
const worktreeArtifactsRoot = resolve5(workspaceDir, "artifacts");
|
|
1117
|
+
const worktreeArtifacts = assertPathInsideRoot(worktreeArtifactsRoot, resolve5(worktreeArtifactsRoot, safeId), "artifact directory");
|
|
1118
|
+
if (existsSync5(worktreeArtifacts) || existsSync5(worktreeArtifactsRoot)) {
|
|
1119
|
+
return worktreeArtifacts;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1124
|
+
return assertPathInsideRoot(paths.artifactsDir, resolve5(paths.artifactsDir, safeId), "artifact directory");
|
|
1125
|
+
} catch {
|
|
1126
|
+
const artifactsRoot = resolve5(resolveCheckoutRoot(projectRoot), "artifacts");
|
|
1127
|
+
return assertPathInsideRoot(artifactsRoot, resolve5(artifactsRoot, safeId), "artifact directory");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function resolveTaskConfigPath(projectRoot) {
|
|
1131
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1132
|
+
if (existsSync5(paths.taskConfigPath)) {
|
|
1133
|
+
return paths.taskConfigPath;
|
|
1134
|
+
}
|
|
1135
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
1136
|
+
if (existsSync5(candidate)) {
|
|
1137
|
+
return candidate;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
|
|
1141
|
+
}
|
|
1142
|
+
function findSourceTaskConfigPath(projectRoot) {
|
|
1143
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
1144
|
+
if (existsSync5(candidate)) {
|
|
1145
|
+
return candidate;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
function readAndSyncSourceTaskConfig(projectRoot) {
|
|
1151
|
+
const sourcePath = findSourceTaskConfigPath(projectRoot);
|
|
1152
|
+
const raw = sourcePath ? readJsonFile(sourcePath, {}) : readConfiguredFileTaskConfig(projectRoot);
|
|
1153
|
+
const synced = synchronizeTaskConfigWithTracker(projectRoot, raw);
|
|
1154
|
+
if (sourcePath && synced.updated) {
|
|
1155
|
+
try {
|
|
1156
|
+
writeFileSync3(sourcePath, `${JSON.stringify(synced.config, null, 2)}
|
|
1157
|
+
`, "utf-8");
|
|
1158
|
+
} catch {}
|
|
1159
|
+
}
|
|
1160
|
+
return synced.config;
|
|
1161
|
+
}
|
|
1162
|
+
function synchronizeTaskConfigWithTracker(projectRoot, rawConfig) {
|
|
1163
|
+
const issues = readSourceIssueRecords(projectRoot);
|
|
1164
|
+
if (issues.length === 0) {
|
|
1165
|
+
return { config: rawConfig, updated: false };
|
|
1166
|
+
}
|
|
1167
|
+
const taskConfig = stripTaskConfigMetadata(rawConfig);
|
|
1168
|
+
const mergedConfig = { ...taskConfig };
|
|
1169
|
+
const validationDescriptions = coerceValidationDescriptions(rawConfig.validation_descriptions);
|
|
1170
|
+
const metaValidationDescriptions = coerceValidationDescriptions(readValidationDescriptionsFromMeta(rawConfig._meta));
|
|
1171
|
+
let updated = false;
|
|
1172
|
+
for (const issue of issues) {
|
|
1173
|
+
if (issue.issueType !== "task") {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (mergedConfig[issue.id] && !shouldRefreshAutoSyncedTaskConfigEntry(mergedConfig[issue.id])) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
mergedConfig[issue.id] = buildAutoSyncedTaskConfigEntry(issue);
|
|
1180
|
+
updated = true;
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
config: {
|
|
1184
|
+
...mergedConfig,
|
|
1185
|
+
...Object.keys(validationDescriptions).length > 0 ? { validation_descriptions: validationDescriptions } : {},
|
|
1186
|
+
...Object.keys(metaValidationDescriptions).length > 0 ? { _meta: { validation_descriptions: metaValidationDescriptions } } : {}
|
|
1187
|
+
},
|
|
1188
|
+
updated
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
function shouldRefreshAutoSyncedTaskConfigEntry(entry) {
|
|
1192
|
+
if (!entry || typeof entry !== "object") {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
const candidate = entry;
|
|
1196
|
+
if (!candidate.auto_synced) {
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
if (!Array.isArray(candidate.scope) || candidate.scope.length === 0) {
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
if (candidate.scope.some((glob) => typeof glob !== "string" || glob.trim().length === 0)) {
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
return !candidate.role;
|
|
1206
|
+
}
|
|
1207
|
+
function readSourceIssueRecords(projectRoot) {
|
|
1208
|
+
const issuesPath = resolve5(resolveCheckoutRoot(projectRoot), ".beads", "issues.jsonl");
|
|
1209
|
+
if (!existsSync5(issuesPath)) {
|
|
1210
|
+
return [];
|
|
1211
|
+
}
|
|
1212
|
+
const records = [];
|
|
1213
|
+
for (const line of readFileSync4(issuesPath, "utf-8").split(/\r?\n/)) {
|
|
1214
|
+
const trimmed = line.trim();
|
|
1215
|
+
if (!trimmed) {
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
try {
|
|
1219
|
+
const parsed = JSON.parse(trimmed);
|
|
1220
|
+
const id = typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
1221
|
+
if (!id) {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
records.push({
|
|
1225
|
+
id,
|
|
1226
|
+
title: typeof parsed.title === "string" ? parsed.title.trim() : "",
|
|
1227
|
+
issueType: typeof parsed.issue_type === "string" ? parsed.issue_type.trim() : null,
|
|
1228
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.filter((label) => typeof label === "string") : []
|
|
1229
|
+
});
|
|
1230
|
+
} catch {}
|
|
1231
|
+
}
|
|
1232
|
+
return records;
|
|
1233
|
+
}
|
|
1234
|
+
function buildAutoSyncedTaskConfigEntry(issue) {
|
|
1235
|
+
return {
|
|
1236
|
+
auto_synced: true,
|
|
1237
|
+
role: inferAutoSyncedTaskRole(issue),
|
|
1238
|
+
scope: inferAutoSyncedTaskScope(issue),
|
|
1239
|
+
validation: []
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function inferAutoSyncedTaskRole(issue) {
|
|
1243
|
+
for (const label of issue.labels) {
|
|
1244
|
+
if (label === "role:architect")
|
|
1245
|
+
return "architect";
|
|
1246
|
+
if (label === "role:extractor")
|
|
1247
|
+
return "extractor";
|
|
1248
|
+
if (label === "role:mechanic")
|
|
1249
|
+
return "mechanic";
|
|
1250
|
+
if (label === "role:verifier")
|
|
1251
|
+
return "verifier";
|
|
1252
|
+
}
|
|
1253
|
+
if (/\bDESIGN\b/i.test(issue.title)) {
|
|
1254
|
+
return "architect";
|
|
1255
|
+
}
|
|
1256
|
+
if (/\bInitialize\b/i.test(issue.title)) {
|
|
1257
|
+
return "mechanic";
|
|
1258
|
+
}
|
|
1259
|
+
return "extractor";
|
|
1260
|
+
}
|
|
1261
|
+
function inferAutoSyncedTaskScope(issue) {
|
|
1262
|
+
return [`artifacts/${issue.id}/**`];
|
|
1263
|
+
}
|
|
1264
|
+
function readConfiguredFileTaskConfig(projectRoot) {
|
|
1265
|
+
const sourcePath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1266
|
+
if (!sourcePath) {
|
|
1267
|
+
return {};
|
|
1268
|
+
}
|
|
1269
|
+
const directory = resolve5(projectRoot, sourcePath);
|
|
1270
|
+
if (!existsSync5(directory)) {
|
|
1271
|
+
return {};
|
|
1272
|
+
}
|
|
1273
|
+
const config = {};
|
|
1274
|
+
for (const name of readdirSync2(directory)) {
|
|
1275
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
1276
|
+
continue;
|
|
1277
|
+
const file = resolve5(directory, name);
|
|
1278
|
+
try {
|
|
1279
|
+
if (!statSync2(file).isFile())
|
|
1280
|
+
continue;
|
|
1281
|
+
const raw = JSON.parse(readFileSync4(file, "utf8"));
|
|
1282
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1283
|
+
continue;
|
|
1284
|
+
const record = raw;
|
|
1285
|
+
const inferredId = basename2(name).replace(FILE_TASK_PATTERN, "");
|
|
1286
|
+
const id = typeof record.id === "string" && record.id.trim().length > 0 ? record.id.trim() : inferredId;
|
|
1287
|
+
config[id] = fileTaskToConfigEntry(record, { kind: "files", path: sourcePath });
|
|
1288
|
+
} catch {}
|
|
1289
|
+
}
|
|
1290
|
+
return config;
|
|
1291
|
+
}
|
|
1292
|
+
function fileTaskToConfigEntry(task, source) {
|
|
1293
|
+
const labels = Array.isArray(task.labels) ? task.labels.filter((label) => typeof label === "string") : [];
|
|
1294
|
+
const scope = firstStringList(task.scope, labels.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length)));
|
|
1295
|
+
const validation = firstStringList(task.validation, task.validators, labels.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length)));
|
|
1296
|
+
const roleLabel = labels.find((label) => label.startsWith("role:"));
|
|
1297
|
+
const role = typeof task.role === "string" && task.role.trim().length > 0 ? task.role.trim() : roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
1298
|
+
return {
|
|
1299
|
+
auto_synced: true,
|
|
1300
|
+
...typeof task.title === "string" ? { title: task.title } : {},
|
|
1301
|
+
...typeof task.status === "string" ? { status: task.status } : {},
|
|
1302
|
+
...typeof task.description === "string" ? { description: task.description } : {},
|
|
1303
|
+
...typeof task.acceptance_criteria === "string" ? { acceptance_criteria: task.acceptance_criteria } : typeof task.acceptanceCriteria === "string" ? { acceptance_criteria: task.acceptanceCriteria } : {},
|
|
1304
|
+
...role ? { role } : {},
|
|
1305
|
+
...scope.length > 0 ? { scope } : {},
|
|
1306
|
+
...validation.length > 0 ? { validation } : {},
|
|
1307
|
+
_rig: { taskSource: source }
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function firstStringList(...candidates) {
|
|
1311
|
+
for (const candidate of candidates) {
|
|
1312
|
+
if (!Array.isArray(candidate)) {
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const list = candidate.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
1316
|
+
if (list.length > 0) {
|
|
1317
|
+
return list;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
1323
|
+
const jsonPath = resolve5(projectRoot, "rig.config.json");
|
|
1324
|
+
if (existsSync5(jsonPath)) {
|
|
1325
|
+
try {
|
|
1326
|
+
const parsed = JSON.parse(readFileSync4(jsonPath, "utf8"));
|
|
1327
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1328
|
+
const taskSource = parsed.taskSource;
|
|
1329
|
+
if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
|
|
1330
|
+
const record = taskSource;
|
|
1331
|
+
return record.kind === "files" && typeof record.path === "string" ? record.path : null;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
} catch {
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
const tsPath = resolve5(projectRoot, "rig.config.ts");
|
|
1339
|
+
if (!existsSync5(tsPath)) {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
try {
|
|
1343
|
+
const source = readFileSync4(tsPath, "utf8");
|
|
1344
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
1345
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1346
|
+
if (kind !== "files") {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
1350
|
+
} catch {
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function sourceTaskConfigCandidates(projectRoot) {
|
|
1355
|
+
const runtimeContext = loadRuntimeContextFromEnv();
|
|
1356
|
+
return [
|
|
1357
|
+
runtimeContext?.monorepoMainRoot ? resolve5(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
|
|
1358
|
+
process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve5(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
|
|
1359
|
+
resolve5(resolveCheckoutRoot(projectRoot), ".rig", "task-config.json")
|
|
1360
|
+
].filter(Boolean);
|
|
1361
|
+
}
|
|
1362
|
+
var FILE_TASK_PATTERN;
|
|
1363
|
+
var init_task_state = __esm(() => {
|
|
1364
|
+
init_state_sync();
|
|
1365
|
+
init_utils();
|
|
1366
|
+
FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
// packages/task-sources-plugin/src/control-plane/task-source-bootstrap.ts
|
|
1370
|
+
import { buildTaskSourceRegistry } from "@rig/core/plugin-host-registries";
|
|
1371
|
+
var init_task_source_bootstrap = () => {};
|
|
1372
|
+
|
|
1373
|
+
// packages/task-sources-plugin/src/control-plane/tasks/legacy-task-config-source.ts
|
|
1374
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1375
|
+
import { resolve as resolve6 } from "path";
|
|
1376
|
+
import { findTaskById } from "@rig/core/task-record-reader";
|
|
1377
|
+
function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1378
|
+
const configPath = options.configPath ?? resolve6(projectRoot, ".rig", "task-config.json");
|
|
1379
|
+
const reader = {
|
|
1380
|
+
async listTasks() {
|
|
1381
|
+
return readLegacyTaskRecords(projectRoot, configPath);
|
|
1382
|
+
},
|
|
1383
|
+
async getTask(id) {
|
|
1384
|
+
return findTaskById(reader, id);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
return reader;
|
|
1388
|
+
}
|
|
1389
|
+
function readLegacyTaskRecords(projectRoot, configPath = resolve6(projectRoot, ".rig", "task-config.json")) {
|
|
1390
|
+
if (!existsSync6(configPath)) {
|
|
1391
|
+
return [];
|
|
1392
|
+
}
|
|
1393
|
+
const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
|
|
1394
|
+
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
1395
|
+
}
|
|
1396
|
+
function readLegacyTaskConfigJson(projectRoot, configPath) {
|
|
1397
|
+
try {
|
|
1398
|
+
const parsed = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
1399
|
+
if (isPlainRecord(parsed)) {
|
|
1400
|
+
return parsed;
|
|
1401
|
+
}
|
|
1402
|
+
throw new Error("task config root must be a JSON object");
|
|
1403
|
+
} catch (cause) {
|
|
1404
|
+
throw new LegacyTaskConfigReadError({
|
|
1405
|
+
projectRoot,
|
|
1406
|
+
configPath,
|
|
1407
|
+
message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1408
|
+
cause
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function stripLegacyTaskConfigMetadata(raw) {
|
|
1413
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1414
|
+
return tasks;
|
|
1415
|
+
}
|
|
1416
|
+
function legacyTaskConfigEntryToRecord(id, entry) {
|
|
1417
|
+
if (!isPlainRecord(entry)) {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
const deps = firstStringList2(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
|
|
1421
|
+
const validation = readStringList(entry.validation);
|
|
1422
|
+
const validators = readStringList(entry.validators);
|
|
1423
|
+
const scope = readStringList(entry.scope);
|
|
1424
|
+
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
1425
|
+
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
1426
|
+
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
1427
|
+
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
1428
|
+
return {
|
|
1429
|
+
id,
|
|
1430
|
+
deps,
|
|
1431
|
+
status,
|
|
1432
|
+
source: "legacy-task-config",
|
|
1433
|
+
...title ? { title } : {},
|
|
1434
|
+
...description ? { description } : {},
|
|
1435
|
+
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
1436
|
+
...scope.length > 0 ? { scope } : {},
|
|
1437
|
+
...validation.length > 0 ? { validation } : {},
|
|
1438
|
+
...validators.length > 0 ? { validators } : {},
|
|
1439
|
+
...preservedLegacyFields(entry)
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function preservedLegacyFields(entry) {
|
|
1443
|
+
const preserved = {};
|
|
1444
|
+
for (const key of [
|
|
1445
|
+
"role",
|
|
1446
|
+
"browser",
|
|
1447
|
+
"repo_pins",
|
|
1448
|
+
"criticality",
|
|
1449
|
+
"queue_weight",
|
|
1450
|
+
"creates_repo",
|
|
1451
|
+
"auto_synced"
|
|
1452
|
+
]) {
|
|
1453
|
+
if (entry[key] !== undefined) {
|
|
1454
|
+
preserved[key] = entry[key];
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return preserved;
|
|
1458
|
+
}
|
|
1459
|
+
function firstStringList2(...candidates) {
|
|
1460
|
+
for (const candidate of candidates) {
|
|
1461
|
+
const list = readStringList(candidate);
|
|
1462
|
+
if (list.length > 0) {
|
|
1463
|
+
return list;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return [];
|
|
1467
|
+
}
|
|
1468
|
+
function readStringList(candidate) {
|
|
1469
|
+
if (!Array.isArray(candidate)) {
|
|
1470
|
+
return [];
|
|
1471
|
+
}
|
|
1472
|
+
return candidate.filter((value) => typeof value === "string");
|
|
1473
|
+
}
|
|
1474
|
+
function isPlainRecord(candidate) {
|
|
1475
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
1476
|
+
}
|
|
1477
|
+
var LegacyTaskConfigReadError;
|
|
1478
|
+
var init_legacy_task_config_source = __esm(() => {
|
|
1479
|
+
LegacyTaskConfigReadError = class LegacyTaskConfigReadError extends Error {
|
|
1480
|
+
code = "LEGACY_TASK_CONFIG_READ_FAILED";
|
|
1481
|
+
projectRoot;
|
|
1482
|
+
configPath;
|
|
1483
|
+
cause;
|
|
1484
|
+
constructor(input) {
|
|
1485
|
+
super(input.message, { cause: input.cause });
|
|
1486
|
+
this.name = "LegacyTaskConfigReadError";
|
|
1487
|
+
this.projectRoot = input.projectRoot;
|
|
1488
|
+
this.configPath = input.configPath;
|
|
1489
|
+
this.cause = input.cause;
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// packages/task-sources-plugin/src/control-plane/native/github-token-env.ts
|
|
1495
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1496
|
+
function cleanToken(value) {
|
|
1497
|
+
const trimmed = value?.trim() ?? "";
|
|
1498
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1499
|
+
}
|
|
1500
|
+
function authStateToken(env = process.env) {
|
|
1501
|
+
const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
|
|
1502
|
+
if (!file || !existsSync7(file))
|
|
1503
|
+
return null;
|
|
1504
|
+
try {
|
|
1505
|
+
const parsed = JSON.parse(readFileSync6(file, "utf8"));
|
|
1506
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
1507
|
+
} catch {
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
var init_github_token_env = () => {};
|
|
1512
|
+
|
|
1513
|
+
// packages/task-sources-plugin/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
1514
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1515
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1516
|
+
import { basename as basename3, join as join2, resolve as resolve7 } from "path";
|
|
1517
|
+
function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
|
|
1518
|
+
const configPath = options.configPath ?? resolve7(projectRoot, ".rig", "task-config.json");
|
|
1519
|
+
const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
|
|
1520
|
+
const spawnFn = options.spawn ?? spawnSync2;
|
|
1521
|
+
const ghBinary = options.ghBinary ?? "gh";
|
|
1522
|
+
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
1523
|
+
return {
|
|
1524
|
+
async listTasks() {
|
|
1525
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1526
|
+
if (!rawConfig) {
|
|
1527
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath2(projectRoot);
|
|
1528
|
+
return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
|
|
1529
|
+
}
|
|
1530
|
+
const tasks = [];
|
|
1531
|
+
const legacyTasks = await legacy.listTasks();
|
|
1532
|
+
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
1533
|
+
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
1534
|
+
if (!isPlainRecord2(rawEntry)) {
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1538
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1539
|
+
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1543
|
+
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1544
|
+
if (fileTask)
|
|
1545
|
+
tasks.push(fileTask);
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
if (!allowLocalFallback) {
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
const legacyTask = legacyById.get(id);
|
|
1552
|
+
if (legacyTask) {
|
|
1553
|
+
tasks.push(legacyTask);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return tasks;
|
|
1557
|
+
},
|
|
1558
|
+
async getTask(id) {
|
|
1559
|
+
const rawEntry = readRawTaskEntry(configPath, id);
|
|
1560
|
+
if (!rawEntry) {
|
|
1561
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath2(projectRoot);
|
|
1562
|
+
return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
|
|
1563
|
+
}
|
|
1564
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1565
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
1566
|
+
return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
|
|
1567
|
+
}
|
|
1568
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
1569
|
+
return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
1570
|
+
}
|
|
1571
|
+
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
async function readSourceAwareTaskStatus(projectRoot, taskId, options = {}) {
|
|
1576
|
+
try {
|
|
1577
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot, options).getTask(taskId);
|
|
1578
|
+
return task?.status ?? null;
|
|
1579
|
+
} catch {
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
function updateGithubIssueTaskBySourceIssueId(sourceIssueId, taskId, update, options = {}) {
|
|
1584
|
+
const parsed = sourceIssueId?.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
1585
|
+
if (!parsed || parsed[3] !== taskId) {
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync2, parsed[3], {
|
|
1589
|
+
sourceIssueId: sourceIssueId.trim(),
|
|
1590
|
+
taskSource: { kind: "github-issues", owner: parsed[1], repo: parsed[2] }
|
|
1591
|
+
}, update);
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
function updateSourceAwareTaskConfigTask(projectRoot, taskId, update, options = {}) {
|
|
1595
|
+
const configPath = options.configPath ?? resolve7(projectRoot, ".rig", "task-config.json");
|
|
1596
|
+
const rawEntry = readRawTaskEntry(configPath, taskId);
|
|
1597
|
+
if (!rawEntry) {
|
|
1598
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath2(projectRoot);
|
|
1599
|
+
return configuredFilesPath ? updateFileBackedTask(projectRoot, configuredFilesPath, taskId, update) : false;
|
|
1600
|
+
}
|
|
1601
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
1602
|
+
const source = metadata.taskSource;
|
|
1603
|
+
if (source?.kind === "github-issues") {
|
|
1604
|
+
applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync2, taskId, metadata, update);
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
if (source?.kind === "files" && source.path) {
|
|
1608
|
+
return updateFileBackedTask(projectRoot, source.path, taskId, update);
|
|
1609
|
+
}
|
|
1610
|
+
if (source?.kind && source.kind !== "files" && source.kind !== "legacy-task-config") {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
if (!source && options.allowLocalTaskConfigStatusFallback === false) {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
if (typeof update.status !== "string" || update.status.trim().length === 0) {
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1619
|
+
writeLegacyTaskStatus(configPath, taskId, update.status);
|
|
1620
|
+
return true;
|
|
1621
|
+
}
|
|
1622
|
+
function readMaterializedTaskMetadata(entry) {
|
|
1623
|
+
const rawRig = entry._rig;
|
|
1624
|
+
if (!isPlainRecord2(rawRig)) {
|
|
1625
|
+
return {};
|
|
1626
|
+
}
|
|
1627
|
+
const rawSource = rawRig.taskSource;
|
|
1628
|
+
const metadata = {};
|
|
1629
|
+
if (isPlainRecord2(rawSource)) {
|
|
1630
|
+
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
1631
|
+
if (kind.length > 0) {
|
|
1632
|
+
metadata.taskSource = {
|
|
1633
|
+
kind,
|
|
1634
|
+
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
1635
|
+
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
1636
|
+
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
1637
|
+
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
1638
|
+
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if (typeof rawRig.sourceIssueId === "string") {
|
|
1643
|
+
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
1644
|
+
}
|
|
1645
|
+
return metadata;
|
|
1646
|
+
}
|
|
1647
|
+
function readConfiguredFilesTaskSourcePath2(projectRoot) {
|
|
1648
|
+
const jsonPath = resolve7(projectRoot, "rig.config.json");
|
|
1649
|
+
if (existsSync8(jsonPath)) {
|
|
1650
|
+
try {
|
|
1651
|
+
const parsed = JSON.parse(readFileSync7(jsonPath, "utf8"));
|
|
1652
|
+
if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
|
|
1653
|
+
const source = parsed.taskSource;
|
|
1654
|
+
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
1655
|
+
}
|
|
1656
|
+
} catch {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
const tsPath = resolve7(projectRoot, "rig.config.ts");
|
|
1661
|
+
if (!existsSync8(tsPath)) {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
const source = readFileSync7(tsPath, "utf8");
|
|
1666
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
1667
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1668
|
+
if (kind !== "files") {
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
1672
|
+
} catch {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function readRawTaskEntry(configPath, taskId) {
|
|
1677
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1678
|
+
if (!rawConfig) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
|
|
1682
|
+
return isPlainRecord2(entry) ? entry : null;
|
|
1683
|
+
}
|
|
1684
|
+
function readRawTaskConfig(configPath) {
|
|
1685
|
+
if (!existsSync8(configPath)) {
|
|
1686
|
+
return null;
|
|
1687
|
+
}
|
|
1688
|
+
const parsed = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
1689
|
+
return isPlainRecord2(parsed) ? parsed : null;
|
|
1690
|
+
}
|
|
1691
|
+
function stripLegacyTaskConfigMetadata2(raw) {
|
|
1692
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1693
|
+
return tasks;
|
|
1694
|
+
}
|
|
1695
|
+
function writeLegacyTaskStatus(configPath, taskId, status) {
|
|
1696
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
1697
|
+
if (!rawConfig) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const entry = rawConfig[taskId];
|
|
1701
|
+
if (!isPlainRecord2(entry)) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
entry.status = status;
|
|
1705
|
+
writeFileSync4(configPath, `${JSON.stringify(rawConfig, null, 2)}
|
|
1706
|
+
`, "utf8");
|
|
1707
|
+
}
|
|
1708
|
+
function updateFileBackedTask(projectRoot, sourcePath, taskId, update) {
|
|
1709
|
+
const directory = resolve7(projectRoot, sourcePath);
|
|
1710
|
+
const file = findFileBackedTaskFile(directory, taskId);
|
|
1711
|
+
if (!file) {
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
1715
|
+
if (!isPlainRecord2(raw)) {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
if (update.status)
|
|
1719
|
+
raw.status = update.status;
|
|
1720
|
+
if (update.title !== undefined)
|
|
1721
|
+
raw.title = update.title;
|
|
1722
|
+
if (update.body !== undefined)
|
|
1723
|
+
raw.body = update.body;
|
|
1724
|
+
if (update.comment?.trim()) {
|
|
1725
|
+
const existing = Array.isArray(raw.comments) ? raw.comments : [];
|
|
1726
|
+
raw.comments = [
|
|
1727
|
+
...existing,
|
|
1728
|
+
{ body: update.comment, createdAt: new Date().toISOString(), source: "rig" }
|
|
1729
|
+
];
|
|
1730
|
+
}
|
|
1731
|
+
writeFileSync4(file, `${JSON.stringify(raw, null, 2)}
|
|
1732
|
+
`, "utf8");
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
function listFileBackedTasks(projectRoot, sourcePath) {
|
|
1736
|
+
const directory = resolve7(projectRoot, sourcePath);
|
|
1737
|
+
if (!existsSync8(directory)) {
|
|
1738
|
+
return [];
|
|
1739
|
+
}
|
|
1740
|
+
const tasks = [];
|
|
1741
|
+
for (const name of readdirSync3(directory)) {
|
|
1742
|
+
if (!FILE_TASK_PATTERN2.test(name))
|
|
1743
|
+
continue;
|
|
1744
|
+
const inferredId = basename3(name).replace(FILE_TASK_PATTERN2, "");
|
|
1745
|
+
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
1746
|
+
if (task)
|
|
1747
|
+
tasks.push(task);
|
|
1748
|
+
}
|
|
1749
|
+
return tasks;
|
|
1750
|
+
}
|
|
1751
|
+
function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
|
|
1752
|
+
const file = findFileBackedTaskFile(resolve7(projectRoot, sourcePath), taskId);
|
|
1753
|
+
if (!file) {
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
1757
|
+
if (!isPlainRecord2(raw)) {
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
return {
|
|
1761
|
+
id: typeof raw.id === "string" ? raw.id : taskId,
|
|
1762
|
+
deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
|
|
1763
|
+
status: typeof raw.status === "string" ? raw.status : "ready",
|
|
1764
|
+
title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
|
|
1765
|
+
...raw
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
function findFileBackedTaskFile(directory, taskId) {
|
|
1769
|
+
if (!existsSync8(directory)) {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
for (const name of readdirSync3(directory)) {
|
|
1773
|
+
if (!FILE_TASK_PATTERN2.test(name))
|
|
1774
|
+
continue;
|
|
1775
|
+
const file = join2(directory, name);
|
|
1776
|
+
try {
|
|
1777
|
+
if (!statSync3(file).isFile())
|
|
1778
|
+
continue;
|
|
1779
|
+
const raw = JSON.parse(readFileSync7(file, "utf8"));
|
|
1780
|
+
const inferredId = basename3(file).replace(FILE_TASK_PATTERN2, "");
|
|
1781
|
+
const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
|
|
1782
|
+
if (id === taskId) {
|
|
1783
|
+
return file;
|
|
1784
|
+
}
|
|
1785
|
+
} catch {}
|
|
1786
|
+
}
|
|
1787
|
+
return null;
|
|
1788
|
+
}
|
|
1789
|
+
function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
|
|
1790
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
1791
|
+
const issue = runGh2(bin, [
|
|
1792
|
+
"issue",
|
|
1793
|
+
"view",
|
|
1794
|
+
String(id),
|
|
1795
|
+
"--repo",
|
|
1796
|
+
`${source.owner}/${source.repo}`,
|
|
1797
|
+
"--json",
|
|
1798
|
+
"number,title,body,labels,state,url,assignees"
|
|
1799
|
+
], spawnFn);
|
|
1800
|
+
return githubIssueToTask(issue, source, rawEntry);
|
|
1801
|
+
}
|
|
1802
|
+
function applyGithubIssueUpdate(bin, spawnFn, id, metadata, update) {
|
|
1803
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
1804
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
1805
|
+
if (update.status) {
|
|
1806
|
+
applyGithubIssueStatus(bin, repo, spawnFn, id, update.status);
|
|
1807
|
+
}
|
|
1808
|
+
if (typeof update.comment === "string" && update.comment.trim().length > 0) {
|
|
1809
|
+
runGhVoid2(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn);
|
|
1810
|
+
}
|
|
1811
|
+
const editArgs = ["issue", "edit", String(id), "--repo", repo];
|
|
1812
|
+
if (typeof update.title === "string" && update.title.trim().length > 0) {
|
|
1813
|
+
editArgs.push("--title", update.title.trim());
|
|
1814
|
+
}
|
|
1815
|
+
if (typeof update.body === "string") {
|
|
1816
|
+
editArgs.push("--body", update.body);
|
|
1817
|
+
}
|
|
1818
|
+
if (editArgs.length > 5) {
|
|
1819
|
+
runGhVoid2(bin, editArgs, spawnFn);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function requireGithubIssueSource(metadata, id) {
|
|
1823
|
+
const source = metadata.taskSource;
|
|
1824
|
+
if (source?.kind === "github-issues" && source.owner && source.repo) {
|
|
1825
|
+
return { owner: source.owner, repo: source.repo };
|
|
1826
|
+
}
|
|
1827
|
+
const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
1828
|
+
if (parsed && parsed[3] === id) {
|
|
1829
|
+
return { owner: parsed[1], repo: parsed[2] };
|
|
1830
|
+
}
|
|
1831
|
+
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
1832
|
+
}
|
|
1833
|
+
function githubIssueToTask(issue, source, rawEntry) {
|
|
1834
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
1835
|
+
const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
|
|
1836
|
+
const roleLabel = labelNames.find((label) => label.startsWith("role:"));
|
|
1837
|
+
const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
|
|
1838
|
+
const body = issue.body ?? "";
|
|
1839
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
1840
|
+
return {
|
|
1841
|
+
id: String(issue.number),
|
|
1842
|
+
deps: parseDeps2(body),
|
|
1843
|
+
status: githubStatusFor(issue),
|
|
1844
|
+
title: issue.title,
|
|
1845
|
+
body,
|
|
1846
|
+
...scope.length > 0 ? { scope } : {},
|
|
1847
|
+
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
1848
|
+
...validators.length > 0 ? { validators } : {},
|
|
1849
|
+
...issue.url ? { url: issue.url } : {},
|
|
1850
|
+
issueType: issueTypeFor2(labelNames),
|
|
1851
|
+
sourceIssueId: `${repo}#${issue.number}`,
|
|
1852
|
+
parentChildDeps: parseParents2(body),
|
|
1853
|
+
labels: labelNames,
|
|
1854
|
+
raw: issue,
|
|
1855
|
+
source: "github-issues",
|
|
1856
|
+
_rig: {
|
|
1857
|
+
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
1858
|
+
sourceIssueId: `${repo}#${issue.number}`
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
function githubStatusFor(issue) {
|
|
1863
|
+
const state = (issue.state ?? "").toUpperCase();
|
|
1864
|
+
if (state === "CLOSED")
|
|
1865
|
+
return "closed";
|
|
1866
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
1867
|
+
if (labelNames.includes("in-progress"))
|
|
1868
|
+
return "in_progress";
|
|
1869
|
+
if (labelNames.includes("blocked"))
|
|
1870
|
+
return "blocked";
|
|
1871
|
+
if (labelNames.includes("ready"))
|
|
1872
|
+
return "ready";
|
|
1873
|
+
if (labelNames.includes("under-review"))
|
|
1874
|
+
return "under_review";
|
|
1875
|
+
if (labelNames.includes("failed"))
|
|
1876
|
+
return "failed";
|
|
1877
|
+
if (labelNames.includes("cancelled"))
|
|
1878
|
+
return "cancelled";
|
|
1879
|
+
return "open";
|
|
1880
|
+
}
|
|
1881
|
+
function applyGithubIssueStatus(bin, repo, spawnFn, id, status) {
|
|
1882
|
+
if (status === "closed") {
|
|
1883
|
+
runGhVoid2(bin, ["issue", "close", String(id), "--repo", repo], spawnFn);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const targetLabel = statusLabelFor2(status);
|
|
1887
|
+
for (const label of STATUS_LABELS2) {
|
|
1888
|
+
if (targetLabel !== null && label === targetLabel) {
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
try {
|
|
1892
|
+
runGhVoid2(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", label], spawnFn);
|
|
1893
|
+
} catch {}
|
|
1894
|
+
}
|
|
1895
|
+
if (targetLabel !== null) {
|
|
1896
|
+
try {
|
|
1897
|
+
runGhVoid2(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1900
|
+
if (!/not found/i.test(message)) {
|
|
1901
|
+
throw error;
|
|
1902
|
+
}
|
|
1903
|
+
ensureStatusLabel2(bin, repo, spawnFn, targetLabel);
|
|
1904
|
+
runGhVoid2(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function statusLabelFor2(status) {
|
|
1909
|
+
switch (status) {
|
|
1910
|
+
case "in_progress":
|
|
1911
|
+
return "in-progress";
|
|
1912
|
+
case "blocked":
|
|
1913
|
+
return "blocked";
|
|
1914
|
+
case "ready":
|
|
1915
|
+
return "ready";
|
|
1916
|
+
case "under_review":
|
|
1917
|
+
return "under-review";
|
|
1918
|
+
case "failed":
|
|
1919
|
+
return "failed";
|
|
1920
|
+
case "cancelled":
|
|
1921
|
+
return "cancelled";
|
|
1922
|
+
case "open":
|
|
1923
|
+
return null;
|
|
1924
|
+
default:
|
|
1925
|
+
throw new Error(`unsupported status: ${status}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function ensureStatusLabel2(bin, repo, spawnFn, label) {
|
|
1929
|
+
try {
|
|
1930
|
+
runGhVoid2(bin, [
|
|
1931
|
+
"label",
|
|
1932
|
+
"create",
|
|
1933
|
+
label,
|
|
1934
|
+
"--repo",
|
|
1935
|
+
repo,
|
|
1936
|
+
"--color",
|
|
1937
|
+
"6f42c1",
|
|
1938
|
+
"--description",
|
|
1939
|
+
"Task status managed by Rig"
|
|
1940
|
+
], spawnFn);
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1943
|
+
if (!/already exists/i.test(message)) {
|
|
1944
|
+
throw error;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
function selectedGitHubEnv() {
|
|
1949
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || authStateToken(process.env) || "";
|
|
1950
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
|
|
1951
|
+
}
|
|
1952
|
+
function ghSpawnOptions2() {
|
|
1953
|
+
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
1954
|
+
}
|
|
1955
|
+
function tokenDiagnostic2(value) {
|
|
1956
|
+
const clean = value?.trim() ?? "";
|
|
1957
|
+
return clean ? `present(len=${clean.length})` : "missing";
|
|
1958
|
+
}
|
|
1959
|
+
function runGh2(bin, args, spawnFn) {
|
|
1960
|
+
const options = ghSpawnOptions2();
|
|
1961
|
+
const res = spawnFn(bin, [...args], options);
|
|
1962
|
+
assertGhSuccess2(args, res, options.env);
|
|
1963
|
+
if (!res.stdout || res.stdout.trim() === "") {
|
|
1964
|
+
throw new Error(`gh ${args.join(" ")} returned empty stdout`);
|
|
1965
|
+
}
|
|
1966
|
+
return JSON.parse(res.stdout);
|
|
1967
|
+
}
|
|
1968
|
+
function runGhVoid2(bin, args, spawnFn) {
|
|
1969
|
+
const options = ghSpawnOptions2();
|
|
1970
|
+
const res = spawnFn(bin, [...args], options);
|
|
1971
|
+
assertGhSuccess2(args, res, options.env);
|
|
1972
|
+
}
|
|
1973
|
+
function assertGhSuccess2(args, res, env) {
|
|
1974
|
+
if (res.error) {
|
|
1975
|
+
const msg = res.error.message ?? String(res.error);
|
|
1976
|
+
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
1977
|
+
}
|
|
1978
|
+
if (res.status !== 0) {
|
|
1979
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
1980
|
+
[rig gh env:source-aware] GH_TOKEN=${tokenDiagnostic2(env.GH_TOKEN)} GITHUB_TOKEN=${tokenDiagnostic2(env.GITHUB_TOKEN)} RIG_GITHUB_TOKEN=${tokenDiagnostic2(env.RIG_GITHUB_TOKEN)}`);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
function parseDeps2(body) {
|
|
1984
|
+
return parseIssueRefs2(body, /^depends-on:\s*([^\n]+)/im);
|
|
1985
|
+
}
|
|
1986
|
+
function parseParents2(body) {
|
|
1987
|
+
return parseIssueRefs2(body, /^parents?:\s*([^\n]+)/im);
|
|
1988
|
+
}
|
|
1989
|
+
function parseIssueRefs2(body, pattern) {
|
|
1990
|
+
const match = body.match(pattern);
|
|
1991
|
+
if (!match)
|
|
1992
|
+
return [];
|
|
1993
|
+
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
1994
|
+
}
|
|
1995
|
+
function issueTypeFor2(labels) {
|
|
1996
|
+
const typed = labels.find((label) => label.startsWith("type:"));
|
|
1997
|
+
if (typed)
|
|
1998
|
+
return typed.slice("type:".length);
|
|
1999
|
+
if (labels.includes("epic"))
|
|
2000
|
+
return "epic";
|
|
2001
|
+
return "task";
|
|
2002
|
+
}
|
|
2003
|
+
function isPlainRecord2(candidate) {
|
|
2004
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
2005
|
+
}
|
|
2006
|
+
var STATUS_LABELS2, FILE_TASK_PATTERN2;
|
|
2007
|
+
var init_source_aware_task_config_source = __esm(() => {
|
|
2008
|
+
init_legacy_task_config_source();
|
|
2009
|
+
init_github_token_env();
|
|
2010
|
+
STATUS_LABELS2 = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
2011
|
+
FILE_TASK_PATTERN2 = /\.(task\.)?json$/;
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
// packages/task-sources-plugin/src/control-plane/tasks/source-lifecycle.ts
|
|
2015
|
+
import { resolvePluginHost } from "@rig/core/project-plugins";
|
|
2016
|
+
import { setScopeRules } from "@rig/core/scope-rules";
|
|
2017
|
+
async function resolveTaskSourceContext(projectRoot) {
|
|
2018
|
+
let resolved;
|
|
2019
|
+
try {
|
|
2020
|
+
resolved = await resolvePluginHost(projectRoot);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2023
|
+
if (msg.includes("no rig.config"))
|
|
2024
|
+
return null;
|
|
2025
|
+
throw err;
|
|
2026
|
+
}
|
|
2027
|
+
const { config, host } = resolved;
|
|
2028
|
+
setScopeRules(config.workspace.scopeNormalization);
|
|
2029
|
+
return {
|
|
2030
|
+
taskSourceRegistry: buildTaskSourceRegistry(config, host, { projectRoot })
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
function runSourceTaskIdentity(run) {
|
|
2034
|
+
const sourceTask = run.sourceTask;
|
|
2035
|
+
return sourceTask && typeof sourceTask === "object" && !Array.isArray(sourceTask) ? sourceTask : null;
|
|
2036
|
+
}
|
|
2037
|
+
function mapToTaskSourceStatus(status) {
|
|
2038
|
+
return status;
|
|
2039
|
+
}
|
|
2040
|
+
function hasRunnableTaskSource(source) {
|
|
2041
|
+
return Boolean(source && typeof source === "object" && !Array.isArray(source));
|
|
2042
|
+
}
|
|
2043
|
+
function cleanString(value) {
|
|
2044
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2045
|
+
}
|
|
2046
|
+
function taskIdFromSourceIssueId(value) {
|
|
2047
|
+
const raw = cleanString(value);
|
|
2048
|
+
if (!raw)
|
|
2049
|
+
return null;
|
|
2050
|
+
const issueNumber = raw.match(/#([^#\s]+)$/)?.[1];
|
|
2051
|
+
return issueNumber ?? raw;
|
|
2052
|
+
}
|
|
2053
|
+
function resolveSourceTaskId(taskId, sourceTask) {
|
|
2054
|
+
return cleanString(sourceTask?.id) ?? taskIdFromSourceIssueId(sourceTask?.sourceIssueId) ?? taskIdFromSourceIssueId(sourceTask?.source_issue_id) ?? taskId;
|
|
2055
|
+
}
|
|
2056
|
+
async function getPluginTask(projectRoot, taskId) {
|
|
2057
|
+
const ctx = await resolveTaskSourceContext(projectRoot);
|
|
2058
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
2059
|
+
if (!hasRunnableTaskSource(source)) {
|
|
2060
|
+
return ctx ? { configured: false, sourceKind: null, task: null } : null;
|
|
2061
|
+
}
|
|
2062
|
+
const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
|
|
2063
|
+
return {
|
|
2064
|
+
configured: true,
|
|
2065
|
+
sourceKind: source.kind,
|
|
2066
|
+
task
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
async function readConfiguredTaskSourceTask(projectRoot, taskId) {
|
|
2070
|
+
const pluginResult = await getPluginTask(projectRoot, taskId);
|
|
2071
|
+
if (pluginResult)
|
|
2072
|
+
return pluginResult;
|
|
2073
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
|
|
2074
|
+
return {
|
|
2075
|
+
configured: false,
|
|
2076
|
+
sourceKind: null,
|
|
2077
|
+
task
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
async function updatePluginTaskSourceTask(projectRoot, taskId, update) {
|
|
2081
|
+
const ctx = await resolveTaskSourceContext(projectRoot);
|
|
2082
|
+
const [source] = ctx?.taskSourceRegistry.list() ?? [];
|
|
2083
|
+
if (!hasRunnableTaskSource(source)) {
|
|
2084
|
+
return ctx ? { taskId, updated: false, source: "none", sourceKind: null, status: null } : null;
|
|
2085
|
+
}
|
|
2086
|
+
if (source.updateTask) {
|
|
2087
|
+
await source.updateTask(taskId, update);
|
|
2088
|
+
} else if (update.status && source.updateStatus) {
|
|
2089
|
+
await source.updateStatus(taskId, update.status);
|
|
2090
|
+
} else {
|
|
2091
|
+
return {
|
|
2092
|
+
taskId,
|
|
2093
|
+
updated: false,
|
|
2094
|
+
source: "plugin",
|
|
2095
|
+
sourceKind: source.kind,
|
|
2096
|
+
status: null
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
const status = source.get ? (await source.get(taskId))?.status ?? update.status ?? null : update.status ?? null;
|
|
2100
|
+
return {
|
|
2101
|
+
taskId,
|
|
2102
|
+
updated: true,
|
|
2103
|
+
source: "plugin",
|
|
2104
|
+
sourceKind: source.kind,
|
|
2105
|
+
status
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
async function updateConfiguredTaskSourceTask(projectRoot, input) {
|
|
2109
|
+
const taskId = resolveSourceTaskId(input.taskId, input.sourceTask);
|
|
2110
|
+
let pluginResult = null;
|
|
2111
|
+
try {
|
|
2112
|
+
pluginResult = await updatePluginTaskSourceTask(projectRoot, taskId, input.update);
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
const fallbackUpdated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update, {
|
|
2115
|
+
allowLocalTaskConfigStatusFallback: false
|
|
2116
|
+
});
|
|
2117
|
+
if (!fallbackUpdated) {
|
|
2118
|
+
throw error;
|
|
2119
|
+
}
|
|
2120
|
+
return {
|
|
2121
|
+
taskId,
|
|
2122
|
+
updated: true,
|
|
2123
|
+
source: "compat",
|
|
2124
|
+
sourceKind: null,
|
|
2125
|
+
status: await readSourceAwareTaskStatus(projectRoot, taskId)
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
if (pluginResult) {
|
|
2129
|
+
return pluginResult;
|
|
2130
|
+
}
|
|
2131
|
+
const updated = updateSourceAwareTaskConfigTask(projectRoot, taskId, input.update);
|
|
2132
|
+
return {
|
|
2133
|
+
taskId,
|
|
2134
|
+
updated,
|
|
2135
|
+
source: updated ? "compat" : "none",
|
|
2136
|
+
sourceKind: null,
|
|
2137
|
+
status: await readSourceAwareTaskStatus(projectRoot, taskId)
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
async function updateRunTaskSourceLifecycle(projectRoot, run, status, summary, options = {}) {
|
|
2141
|
+
if (!run.taskId) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
const sourceTask = runSourceTaskIdentity(run);
|
|
2145
|
+
return updateConfiguredTaskSourceTask(projectRoot, {
|
|
2146
|
+
taskId: run.taskId,
|
|
2147
|
+
sourceTask,
|
|
2148
|
+
update: {
|
|
2149
|
+
status: mapToTaskSourceStatus(status),
|
|
2150
|
+
comment: buildTaskRunLifecycleComment({
|
|
2151
|
+
runId: cleanString(run.runId) ?? run.taskId,
|
|
2152
|
+
status,
|
|
2153
|
+
summary,
|
|
2154
|
+
runtimeWorkspace: cleanString(run.worktreePath),
|
|
2155
|
+
logsDir: cleanString(run.logRoot),
|
|
2156
|
+
sessionDir: cleanString(run.sessionPath),
|
|
2157
|
+
errorText: options.errorText ?? cleanString(run.errorText)
|
|
2158
|
+
})
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
function buildTaskRunLifecycleComment(input) {
|
|
2163
|
+
const lines = [
|
|
2164
|
+
"<!-- rig:status-comment -->",
|
|
2165
|
+
`### Rig status: ${input.status}`,
|
|
2166
|
+
"",
|
|
2167
|
+
input.summary,
|
|
2168
|
+
"",
|
|
2169
|
+
`- Run: ${input.runId}`,
|
|
2170
|
+
`- Status: ${input.status}`
|
|
2171
|
+
];
|
|
2172
|
+
if (input.errorText?.trim()) {
|
|
2173
|
+
lines.push(`- Error: ${input.errorText.trim()}`);
|
|
2174
|
+
}
|
|
2175
|
+
if (input.runtimeWorkspace?.trim()) {
|
|
2176
|
+
lines.push(`- Runtime workspace: ${input.runtimeWorkspace.trim()}`);
|
|
2177
|
+
}
|
|
2178
|
+
if (input.logsDir?.trim()) {
|
|
2179
|
+
lines.push(`- Logs: ${input.logsDir.trim()}`);
|
|
2180
|
+
}
|
|
2181
|
+
if (input.sessionDir?.trim()) {
|
|
2182
|
+
lines.push(`- Session: ${input.sessionDir.trim()}`);
|
|
2183
|
+
}
|
|
2184
|
+
return lines.join(`
|
|
2185
|
+
`);
|
|
2186
|
+
}
|
|
2187
|
+
var init_source_lifecycle = __esm(() => {
|
|
2188
|
+
init_task_source_bootstrap();
|
|
2189
|
+
init_source_aware_task_config_source();
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
// packages/task-sources-plugin/src/control-plane/native/runtime-binary-build.ts
|
|
2193
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
2194
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve8 } from "path";
|
|
2195
|
+
async function buildRuntimeBinary(options) {
|
|
2196
|
+
await runSerializedRuntimeBinaryBuild(async () => {
|
|
2197
|
+
const entrypoint = isAbsolute3(options.sourcePath) ? options.sourcePath : resolve8(options.cwd, options.sourcePath);
|
|
2198
|
+
const outputPath = resolve8(options.outputPath);
|
|
2199
|
+
const tempBuildDir = resolve8(dirname2(outputPath), `.bun-build-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
2200
|
+
const tempOutputPath = resolve8(tempBuildDir, basename4(outputPath));
|
|
2201
|
+
mkdirSync2(tempBuildDir, { recursive: true });
|
|
2202
|
+
await withTemporaryEnv({
|
|
2203
|
+
...options.env,
|
|
2204
|
+
...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
|
|
2205
|
+
}, async () => withTemporaryCwd(tempBuildDir, async () => {
|
|
2206
|
+
const buildResult = await Bun.build({
|
|
2207
|
+
entrypoints: [entrypoint],
|
|
2208
|
+
compile: {
|
|
2209
|
+
target: currentCompileTarget(),
|
|
2210
|
+
outfile: tempOutputPath
|
|
2211
|
+
},
|
|
2212
|
+
target: "bun",
|
|
2213
|
+
format: "esm",
|
|
2214
|
+
minify: true,
|
|
2215
|
+
bytecode: true,
|
|
2216
|
+
...options.external ? { external: options.external } : {},
|
|
2217
|
+
...options.define ? { define: { __RIG_BUILD_CONFIG__: JSON.stringify(options.define) } } : {}
|
|
2218
|
+
});
|
|
2219
|
+
if (!buildResult.success) {
|
|
2220
|
+
const details = buildResult.logs.map((log) => [
|
|
2221
|
+
log.message,
|
|
2222
|
+
log.position?.file ? `${log.position.file}:${log.position.line}:${log.position.column}` : ""
|
|
2223
|
+
].filter(Boolean).join(" ")).filter(Boolean).join(`
|
|
2224
|
+
`);
|
|
2225
|
+
throw new Error(`Failed to build ${entrypoint}: ${details || "Bun.build() returned errors"}`);
|
|
2226
|
+
}
|
|
2227
|
+
if (!existsSync9(tempOutputPath)) {
|
|
2228
|
+
const emitted = buildResult.outputs.map((output) => output.path).join(", ") || "(none)";
|
|
2229
|
+
throw new Error(`Failed to build ${entrypoint}: Bun.build() did not emit ${tempOutputPath}. Emitted: ${emitted}`);
|
|
2230
|
+
}
|
|
2231
|
+
renameSync2(tempOutputPath, outputPath);
|
|
2232
|
+
chmodSync2(outputPath, 493);
|
|
2233
|
+
})).finally(() => {
|
|
2234
|
+
rmSync2(tempBuildDir, { recursive: true, force: true });
|
|
2235
|
+
});
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
function currentCompileTarget() {
|
|
2239
|
+
if (process.platform === "darwin") {
|
|
2240
|
+
return process.arch === "arm64" ? "bun-darwin-arm64" : "bun-darwin-x64";
|
|
2241
|
+
}
|
|
2242
|
+
if (process.platform === "linux") {
|
|
2243
|
+
return process.arch === "arm64" ? "bun-linux-arm64" : "bun-linux-x64";
|
|
2244
|
+
}
|
|
2245
|
+
return "bun-windows-x64";
|
|
2246
|
+
}
|
|
2247
|
+
async function runSerializedRuntimeBinaryBuild(action) {
|
|
2248
|
+
const previous = runtimeBinaryBuildQueue;
|
|
2249
|
+
let release;
|
|
2250
|
+
runtimeBinaryBuildQueue = new Promise((resolveRelease) => {
|
|
2251
|
+
release = resolveRelease;
|
|
2252
|
+
});
|
|
2253
|
+
await previous;
|
|
2254
|
+
try {
|
|
2255
|
+
return await action();
|
|
2256
|
+
} finally {
|
|
2257
|
+
release();
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
async function withTemporaryEnv(env, action) {
|
|
2261
|
+
if (!env) {
|
|
2262
|
+
return action();
|
|
2263
|
+
}
|
|
2264
|
+
const previousValues = new Map;
|
|
2265
|
+
for (const [key, value] of Object.entries(env)) {
|
|
2266
|
+
previousValues.set(key, process.env[key]);
|
|
2267
|
+
if (value === undefined) {
|
|
2268
|
+
delete process.env[key];
|
|
2269
|
+
} else {
|
|
2270
|
+
process.env[key] = value;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
try {
|
|
2274
|
+
return await action();
|
|
2275
|
+
} finally {
|
|
2276
|
+
for (const [key, value] of previousValues.entries()) {
|
|
2277
|
+
if (value === undefined) {
|
|
2278
|
+
delete process.env[key];
|
|
2279
|
+
} else {
|
|
2280
|
+
process.env[key] = value;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
async function withTemporaryCwd(cwd, action) {
|
|
2286
|
+
const previousCwd = process.cwd();
|
|
2287
|
+
process.chdir(cwd);
|
|
2288
|
+
try {
|
|
2289
|
+
return await action();
|
|
2290
|
+
} finally {
|
|
2291
|
+
process.chdir(previousCwd);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
var runtimeBinaryBuildQueue;
|
|
2295
|
+
var init_runtime_binary_build = __esm(() => {
|
|
2296
|
+
runtimeBinaryBuildQueue = Promise.resolve();
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
// packages/task-sources-plugin/src/control-plane/native/validator-binaries.ts
|
|
2300
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, rmSync as rmSync3, statSync as statSync4 } from "fs";
|
|
2301
|
+
import { dirname as dirname3, resolve as resolve9 } from "path";
|
|
2302
|
+
import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
|
|
2303
|
+
import { resolveBunBinaryPath } from "@rig/core/runtime-paths";
|
|
2304
|
+
function resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext) {
|
|
2305
|
+
if (runtimeContext) {
|
|
2306
|
+
return resolve9(runtimeContext.binDir, "validators", binaryName);
|
|
2307
|
+
}
|
|
2308
|
+
return resolve9(resolveHarnessPaths(projectRoot).binDir, "validators", binaryName);
|
|
2309
|
+
}
|
|
2310
|
+
async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
|
|
2311
|
+
const match = checkId.match(/^([a-z][\w-]*):([a-z][\w-]*)$/);
|
|
2312
|
+
if (!match) {
|
|
2313
|
+
return null;
|
|
2314
|
+
}
|
|
2315
|
+
const category = match[1];
|
|
2316
|
+
const check = match[2];
|
|
2317
|
+
if (!category || !check) {
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
const binaryName = `${category}-${check}`;
|
|
2321
|
+
const binaryPath = resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
|
|
2322
|
+
const hostProjectRoot = runtimeContext?.hostProjectRoot?.trim() || projectRoot;
|
|
2323
|
+
const sourcePath = resolve9(hostProjectRoot, "packages/validator-kit/src/validators", category, `${check}.ts`);
|
|
2324
|
+
if (!existsSync10(sourcePath)) {
|
|
2325
|
+
return null;
|
|
2326
|
+
}
|
|
2327
|
+
const sourceMtime = statSync4(sourcePath).mtimeMs;
|
|
2328
|
+
const binaryExists = existsSync10(binaryPath);
|
|
2329
|
+
const binaryMtime = binaryExists ? statSync4(binaryPath).mtimeMs : 0;
|
|
2330
|
+
if (!binaryExists || sourceMtime > binaryMtime) {
|
|
2331
|
+
if (binaryExists) {
|
|
2332
|
+
rmSync3(binaryPath, { force: true });
|
|
2333
|
+
rmSync3(`${binaryPath}.build-manifest.json`, { force: true });
|
|
2334
|
+
}
|
|
2335
|
+
mkdirSync3(dirname3(binaryPath), { recursive: true });
|
|
2336
|
+
await buildRuntimeBinary({
|
|
2337
|
+
sourcePath: `packages/validator-kit/src/validators/${category}/${check}.ts`,
|
|
2338
|
+
outputPath: binaryPath,
|
|
2339
|
+
cwd: hostProjectRoot,
|
|
2340
|
+
define: { AGENT_BUN_PATH: resolveBunBinaryPath() },
|
|
2341
|
+
env: runtimeProvisioningEnv()
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
return existsSync10(binaryPath) ? binaryPath : null;
|
|
2345
|
+
}
|
|
2346
|
+
var init_validator_binaries = __esm(() => {
|
|
2347
|
+
init_runtime_binary_build();
|
|
2348
|
+
init_utils();
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
// packages/task-sources-plugin/src/control-plane/native/validator.ts
|
|
2352
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2353
|
+
import { resolve as resolve10 } from "path";
|
|
2354
|
+
import { assertPathInsideRoot as assertPathInsideRoot2, safePathSegment as safePathSegment2 } from "@rig/core/safe-identifiers";
|
|
2355
|
+
import { resolveMonorepoRoot } from "@rig/core/layout";
|
|
2356
|
+
import { createValidatorRegistry } from "@rig/validator-kit";
|
|
2357
|
+
function isCheckId(entry) {
|
|
2358
|
+
return /^[a-z][\w-]*:[a-z][\w-]*$/.test(entry);
|
|
2359
|
+
}
|
|
2360
|
+
function stringArray(candidate) {
|
|
2361
|
+
return Array.isArray(candidate) ? candidate.filter((entry) => typeof entry === "string") : [];
|
|
2362
|
+
}
|
|
2363
|
+
function safeReadTaskConfig(projectRoot) {
|
|
2364
|
+
try {
|
|
2365
|
+
return readTaskConfig(projectRoot);
|
|
2366
|
+
} catch {
|
|
2367
|
+
return {};
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
async function readTaskSourceValidation(projectRoot, taskId) {
|
|
2371
|
+
const sourceTask = await readConfiguredTaskSourceTask(projectRoot, taskId).then((result) => result.task).catch(() => null);
|
|
2372
|
+
if (!sourceTask) {
|
|
2373
|
+
return { validation: [], scope: [], taskConfig: undefined };
|
|
2374
|
+
}
|
|
2375
|
+
const record = sourceTask;
|
|
2376
|
+
const validation = stringArray(record.validation).length > 0 ? stringArray(record.validation) : stringArray(record.validators);
|
|
2377
|
+
return {
|
|
2378
|
+
validation,
|
|
2379
|
+
scope: stringArray(record.scope),
|
|
2380
|
+
taskConfig: {
|
|
2381
|
+
...typeof record.role === "string" ? { role: record.role } : {},
|
|
2382
|
+
...stringArray(record.scope).length > 0 ? { scope: stringArray(record.scope) } : {},
|
|
2383
|
+
...validation.length > 0 ? { validation } : {}
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function resolveValidationPaths(projectRoot, taskId, runtimeContext) {
|
|
2388
|
+
const taskSegment = safePathSegment2(taskId, { fallback: "task", maxLength: 96 });
|
|
2389
|
+
if (runtimeContext) {
|
|
2390
|
+
const runtimeArtifactsRoot = resolve10(runtimeContext.workspaceDir, "artifacts");
|
|
2391
|
+
return {
|
|
2392
|
+
taskLogDir: assertPathInsideRoot2(runtimeContext.logsDir, resolve10(runtimeContext.logsDir, taskSegment), "validation log directory"),
|
|
2393
|
+
artifactDir: assertPathInsideRoot2(runtimeArtifactsRoot, resolve10(runtimeArtifactsRoot, taskSegment), "validation artifact directory")
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
2397
|
+
return {
|
|
2398
|
+
taskLogDir: assertPathInsideRoot2(paths.logsDir, resolve10(paths.logsDir, taskSegment), "validation log directory"),
|
|
2399
|
+
artifactDir: assertPathInsideRoot2(paths.artifactsDir, resolve10(paths.artifactsDir, taskSegment), "validation artifact directory")
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext) {
|
|
2403
|
+
const binaryName = checkId.replace(":", "-");
|
|
2404
|
+
const binaryPath = await ensureValidatorBinary(projectRoot, checkId, runtimeContext) ?? resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
|
|
2405
|
+
if (!existsSync11(binaryPath)) {
|
|
2406
|
+
return {
|
|
2407
|
+
result: {
|
|
2408
|
+
id: checkId,
|
|
2409
|
+
passed: false,
|
|
2410
|
+
summary: `Validator binary not found: ${binaryPath}`
|
|
2411
|
+
},
|
|
2412
|
+
exitCode: 2
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
const validatorCwd = runtimeContext?.workspaceDir || resolveMonorepoRoot(projectRoot);
|
|
2416
|
+
const runtimeShellPath = runtimeContext ? resolve10(runtimeContext.binDir, "rig-shell") : "";
|
|
2417
|
+
const monorepoMainRoot = runtimeContext?.monorepoMainRoot || process.env.MONOREPO_MAIN_ROOT?.trim() || resolveMonorepoRoot(projectRoot);
|
|
2418
|
+
const validatorEnv = {
|
|
2419
|
+
PROJECT_RIG_ROOT: runtimeContext?.hostProjectRoot || projectRoot,
|
|
2420
|
+
RIG_HOST_PROJECT_ROOT: projectRoot,
|
|
2421
|
+
RIG_TASK_WORKSPACE: validatorCwd,
|
|
2422
|
+
MONOREPO_ROOT: validatorCwd,
|
|
2423
|
+
MONOREPO_MAIN_ROOT: monorepoMainRoot,
|
|
2424
|
+
RIG_TASK_ID: taskId
|
|
2425
|
+
};
|
|
2426
|
+
if (runtimeContext) {
|
|
2427
|
+
validatorEnv.RIG_TASK_RUNTIME_ID = runtimeContext.runtimeId;
|
|
2428
|
+
validatorEnv.RIG_LOGS_DIR = runtimeContext.logsDir;
|
|
2429
|
+
validatorEnv.RIG_RUNTIME_BIN_DIR = runtimeContext.binDir;
|
|
2430
|
+
}
|
|
2431
|
+
const { exitCode, stdout, stderr } = await runCaptureAsync(runtimeShellPath && existsSync11(runtimeShellPath) ? [runtimeShellPath, "run-binary", binaryPath] : [binaryPath], validatorCwd, validatorEnv);
|
|
2432
|
+
try {
|
|
2433
|
+
const result = JSON.parse(stdout.trim());
|
|
2434
|
+
return { result, exitCode };
|
|
2435
|
+
} catch {
|
|
2436
|
+
return {
|
|
2437
|
+
result: {
|
|
2438
|
+
id: checkId,
|
|
2439
|
+
passed: false,
|
|
2440
|
+
summary: `Failed to parse validator output: ${stderr || stdout}`.slice(0, 200)
|
|
2441
|
+
},
|
|
2442
|
+
exitCode: exitCode || 2
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
async function dispatchValidator(checkId, registry, ctx, subprocessFallback) {
|
|
2447
|
+
let registered;
|
|
2448
|
+
try {
|
|
2449
|
+
registered = registry.resolve(checkId);
|
|
2450
|
+
} catch {
|
|
2451
|
+
return subprocessFallback(checkId);
|
|
2452
|
+
}
|
|
2453
|
+
const validatorResult = await registered.run(ctx);
|
|
2454
|
+
return {
|
|
2455
|
+
result: {
|
|
2456
|
+
id: validatorResult.id,
|
|
2457
|
+
passed: validatorResult.passed,
|
|
2458
|
+
summary: validatorResult.summary,
|
|
2459
|
+
...validatorResult.details !== undefined ? { details: validatorResult.details } : {}
|
|
2460
|
+
},
|
|
2461
|
+
exitCode: validatorResult.passed ? 0 : 1
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
async function validateTask(projectRoot, taskId, runtimeContext, registry, options = {}) {
|
|
2465
|
+
const resolvedContext = runtimeContext ?? null;
|
|
2466
|
+
const taskConfig = resolvedContext ? {} : options.taskConfig ?? safeReadTaskConfig(projectRoot);
|
|
2467
|
+
const sourceValidation = !resolvedContext ? await readTaskSourceValidation(projectRoot, taskId) : { validation: [], scope: [], taskConfig: undefined };
|
|
2468
|
+
const configuredValidation = stringArray(taskConfig[taskId]?.validation);
|
|
2469
|
+
const commands = resolvedContext?.validation?.length ? resolvedContext.validation : configuredValidation.length > 0 ? configuredValidation : sourceValidation.validation;
|
|
2470
|
+
const { taskLogDir, artifactDir } = resolveValidationPaths(projectRoot, taskId, resolvedContext);
|
|
2471
|
+
mkdirSync4(taskLogDir, { recursive: true });
|
|
2472
|
+
mkdirSync4(artifactDir, { recursive: true });
|
|
2473
|
+
if (commands.length === 0) {
|
|
2474
|
+
const skipped = {
|
|
2475
|
+
status: "skipped",
|
|
2476
|
+
total: 0,
|
|
2477
|
+
passed: 0,
|
|
2478
|
+
failed: 0,
|
|
2479
|
+
categories: []
|
|
2480
|
+
};
|
|
2481
|
+
writeFileSync5(assertPathInsideRoot2(artifactDir, resolve10(artifactDir, "validation-summary.json"), "validation summary file"), `${JSON.stringify(skipped, null, 2)}
|
|
2482
|
+
`, "utf-8");
|
|
2483
|
+
return skipped;
|
|
2484
|
+
}
|
|
2485
|
+
const effectiveRegistry = registry ?? createValidatorRegistry();
|
|
2486
|
+
const workspaceRoot = resolvedContext?.workspaceDir ?? resolveMonorepoRoot(projectRoot);
|
|
2487
|
+
const monorepoRoot = resolvedContext?.monorepoMainRoot ?? process.env.MONOREPO_MAIN_ROOT?.trim() ?? resolveMonorepoRoot(projectRoot);
|
|
2488
|
+
const validatorCtx = {
|
|
2489
|
+
taskId,
|
|
2490
|
+
workspaceRoot,
|
|
2491
|
+
scope: resolvedContext?.scopes ?? (stringArray(taskConfig[taskId]?.scope).length > 0 ? stringArray(taskConfig[taskId]?.scope) : sourceValidation.scope),
|
|
2492
|
+
monorepoRoot,
|
|
2493
|
+
artifactsDir: artifactDir,
|
|
2494
|
+
taskConfig: sourceValidation.taskConfig ?? taskConfig[taskId] ?? undefined
|
|
2495
|
+
};
|
|
2496
|
+
const valDescriptions = resolvedContext ? {} : options.validationDescriptions ?? (() => {
|
|
2497
|
+
try {
|
|
2498
|
+
return readValidationDescriptions(projectRoot);
|
|
2499
|
+
} catch {
|
|
2500
|
+
return {};
|
|
2501
|
+
}
|
|
2502
|
+
})();
|
|
2503
|
+
const categories = [];
|
|
2504
|
+
let passed = 0;
|
|
2505
|
+
let failed = 0;
|
|
2506
|
+
for (const cmd of commands) {
|
|
2507
|
+
const startedAt = Date.now();
|
|
2508
|
+
if (!isCheckId(cmd)) {
|
|
2509
|
+
failed += 1;
|
|
2510
|
+
categories.push({
|
|
2511
|
+
category: cmd,
|
|
2512
|
+
status: "fail",
|
|
2513
|
+
exit_code: 2,
|
|
2514
|
+
duration_seconds: 0
|
|
2515
|
+
});
|
|
2516
|
+
const logFile2 = assertPathInsideRoot2(taskLogDir, resolve10(taskLogDir, `invalid-entry-validation.log`), "validation log file");
|
|
2517
|
+
mkdirSync4(taskLogDir, { recursive: true });
|
|
2518
|
+
writeFileSync5(logFile2, `=== ${nowIso()} :: ${cmd} ===
|
|
2519
|
+
Invalid validation entry: not a check-ID. All entries must use format "category:check-name".
|
|
2520
|
+
`, "utf-8");
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
const { result, exitCode } = await dispatchValidator(cmd, effectiveRegistry, validatorCtx, (id) => runValidatorBinary(projectRoot, taskId, id, resolvedContext));
|
|
2524
|
+
const durationSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
2525
|
+
const logFile = assertPathInsideRoot2(taskLogDir, resolve10(taskLogDir, `${cmd.replace(":", "-")}-validation.log`), "validation log file");
|
|
2526
|
+
mkdirSync4(taskLogDir, { recursive: true });
|
|
2527
|
+
writeFileSync5(logFile, `=== ${nowIso()} :: ${cmd} ===
|
|
2528
|
+
${JSON.stringify(result, null, 2)}
|
|
2529
|
+
`, "utf-8");
|
|
2530
|
+
if (result.passed) {
|
|
2531
|
+
passed += 1;
|
|
2532
|
+
categories.push({ category: cmd, status: "pass", duration_seconds: durationSeconds });
|
|
2533
|
+
} else {
|
|
2534
|
+
failed += 1;
|
|
2535
|
+
categories.push({ category: cmd, status: "fail", exit_code: exitCode, duration_seconds: durationSeconds });
|
|
2536
|
+
const desc = valDescriptions[cmd];
|
|
2537
|
+
if (desc) {
|
|
2538
|
+
console.log(` What this checks (${cmd}): ${desc}`);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
const summary = {
|
|
2543
|
+
status: failed === 0 ? "pass" : "fail",
|
|
2544
|
+
total: commands.length,
|
|
2545
|
+
passed,
|
|
2546
|
+
failed,
|
|
2547
|
+
categories
|
|
2548
|
+
};
|
|
2549
|
+
mkdirSync4(artifactDir, { recursive: true });
|
|
2550
|
+
writeFileSync5(assertPathInsideRoot2(artifactDir, resolve10(artifactDir, "validation-summary.json"), "validation summary file"), `${JSON.stringify(summary, null, 2)}
|
|
2551
|
+
`, "utf-8");
|
|
2552
|
+
return summary;
|
|
2553
|
+
}
|
|
2554
|
+
var init_validator = __esm(() => {
|
|
2555
|
+
init_task_state();
|
|
2556
|
+
init_source_lifecycle();
|
|
2557
|
+
init_utils();
|
|
2558
|
+
init_validator_binaries();
|
|
2559
|
+
});
|
|
2560
|
+
|
|
2561
|
+
// packages/task-sources-plugin/src/control-plane/native/task-ops.ts
|
|
2562
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
2563
|
+
import { resolve as resolve11 } from "path";
|
|
2564
|
+
import { assertPathInsideRoot as assertPathInsideRoot3, safePathSegment as safePathSegment3 } from "@rig/core/safe-identifiers";
|
|
2565
|
+
import { readBuildConfig } from "@rig/core/build-time-config";
|
|
2566
|
+
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/core/runtime-context";
|
|
2567
|
+
import {
|
|
2568
|
+
BROWSER_CONTRACT_SERVICE_CAPABILITY,
|
|
2569
|
+
RUNTIME_INSTRUCTION,
|
|
2570
|
+
RUNTIME_FILE_TOOL_NAMES,
|
|
2571
|
+
RUNTIME_SHELL_TOOL_NAMES
|
|
2572
|
+
} from "@rig/contracts";
|
|
2573
|
+
import { defineCapability as defineCapability2 } from "@rig/core/capability";
|
|
2574
|
+
import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
2575
|
+
function hasRuntimeWorkspace() {
|
|
2576
|
+
return Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
|
|
2577
|
+
}
|
|
2578
|
+
function readTaskConfigForInvocation(projectRoot) {
|
|
2579
|
+
return hasRuntimeWorkspace() ? readTaskConfig(projectRoot) : readSourceTaskConfig(projectRoot);
|
|
2580
|
+
}
|
|
2581
|
+
function readValidationDescriptionsForInvocation(projectRoot) {
|
|
2582
|
+
return hasRuntimeWorkspace() ? readValidationDescriptions(projectRoot) : readSourceValidationDescriptions(projectRoot);
|
|
2583
|
+
}
|
|
2584
|
+
function readStringList2(candidate) {
|
|
2585
|
+
return Array.isArray(candidate) ? candidate.filter((value) => typeof value === "string") : [];
|
|
2586
|
+
}
|
|
2587
|
+
function taskConfigEntryFromSourceTask(task) {
|
|
2588
|
+
if (!task)
|
|
2589
|
+
return {};
|
|
2590
|
+
const record = task;
|
|
2591
|
+
const description = firstNonEmpty(typeof record.description === "string" ? record.description : undefined, typeof record.body === "string" ? record.body : undefined);
|
|
2592
|
+
const acceptance = firstNonEmpty(typeof record.acceptanceCriteria === "string" ? record.acceptanceCriteria : undefined, typeof record.acceptance_criteria === "string" ? record.acceptance_criteria : undefined);
|
|
2593
|
+
const validation = readStringList2(record.validation).length > 0 ? readStringList2(record.validation) : readStringList2(record.validators);
|
|
2594
|
+
const scope = readStringList2(record.scope);
|
|
2595
|
+
const browser = record.browser && typeof record.browser === "object" && !Array.isArray(record.browser) ? record.browser : undefined;
|
|
2596
|
+
return {
|
|
2597
|
+
...typeof record.role === "string" ? { role: record.role } : {},
|
|
2598
|
+
...description ? { description } : {},
|
|
2599
|
+
...acceptance ? { acceptance_criteria: acceptance } : {},
|
|
2600
|
+
...scope.length > 0 ? { scope } : {},
|
|
2601
|
+
...validation.length > 0 ? { validation } : {},
|
|
2602
|
+
...browser ? { browser } : {}
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
function taskMetadataFromSourceTask(task) {
|
|
2606
|
+
if (!task)
|
|
2607
|
+
return null;
|
|
2608
|
+
const record = task;
|
|
2609
|
+
const description = firstNonEmpty(typeof record.description === "string" ? record.description : undefined, typeof record.body === "string" ? record.body : undefined);
|
|
2610
|
+
const acceptanceCriteria = firstNonEmpty(typeof record.acceptanceCriteria === "string" ? record.acceptanceCriteria : undefined, typeof record.acceptance_criteria === "string" ? record.acceptance_criteria : undefined);
|
|
2611
|
+
return {
|
|
2612
|
+
title: typeof record.title === "string" && record.title.trim() ? record.title : task.id,
|
|
2613
|
+
...typeof record.status === "string" ? { status: record.status } : {},
|
|
2614
|
+
...typeof record.priority === "number" ? { priority: record.priority } : {},
|
|
2615
|
+
...description ? { description } : {},
|
|
2616
|
+
...acceptanceCriteria ? { acceptanceCriteria } : {}
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
function sourceTaskDependencyIds(task) {
|
|
2620
|
+
if (!task)
|
|
2621
|
+
return null;
|
|
2622
|
+
const record = task;
|
|
2623
|
+
return unique2([
|
|
2624
|
+
...readStringList2(record.deps),
|
|
2625
|
+
...readStringList2(record.dependencies)
|
|
2626
|
+
]).filter((id) => id !== task.id);
|
|
2627
|
+
}
|
|
2628
|
+
async function readTaskSourceRecordForInvocation(projectRoot, taskId) {
|
|
2629
|
+
if (hasRuntimeWorkspace()) {
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
try {
|
|
2633
|
+
const result = await readConfiguredTaskSourceTask(projectRoot, taskId);
|
|
2634
|
+
return result.task;
|
|
2635
|
+
} catch {
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function runtimeToolSummary() {
|
|
2640
|
+
const available = new Set(RUNTIME_SHELL_TOOL_NAMES);
|
|
2641
|
+
const shell = ["bash", "sh", "python3", "tee", "cp", "mv", "rm", "mkdir", "touch"].filter((tool) => available.has(tool));
|
|
2642
|
+
return { shell, file: [...RUNTIME_FILE_TOOL_NAMES] };
|
|
2643
|
+
}
|
|
2644
|
+
function providerToolReferenceLine(_provider) {
|
|
2645
|
+
return "Pi tool names: `read`, `write`, `edit`, `bash`.";
|
|
2646
|
+
}
|
|
2647
|
+
async function taskInfo(projectRoot, taskId, runtimeProviderOverride) {
|
|
2648
|
+
if (BAKED_INFO_OUTPUT) {
|
|
2649
|
+
process.stdout.write(BAKED_INFO_OUTPUT);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
2653
|
+
if (!activeTask) {
|
|
2654
|
+
throw new Error("No active task. Start one with: rig run start-serial");
|
|
2655
|
+
}
|
|
2656
|
+
const runtimeContext = loadRuntimeContextFromEnv2();
|
|
2657
|
+
const sourceTask = runtimeContext?.sourceTask ?? await readTaskSourceRecordForInvocation(projectRoot, activeTask);
|
|
2658
|
+
const tracker = loadReadonlyTaskTrackerContext(projectRoot);
|
|
2659
|
+
const taskConfig = (() => {
|
|
2660
|
+
try {
|
|
2661
|
+
return readTaskConfigForInvocation(projectRoot);
|
|
2662
|
+
} catch {
|
|
2663
|
+
return {};
|
|
2664
|
+
}
|
|
2665
|
+
})();
|
|
2666
|
+
const entry = { ...taskConfig[activeTask] || {}, ...taskConfigEntryFromSourceTask(sourceTask) };
|
|
2667
|
+
const taskMeta = taskMetadataFromSourceTask(sourceTask) ?? await readTaskMetadata(projectRoot, activeTask, tracker);
|
|
2668
|
+
const instructionService = await loadCapabilityForRoot(projectRoot, defineCapability2(RUNTIME_INSTRUCTION));
|
|
2669
|
+
const runtimeProvider = runtimeProviderOverride ?? instructionService?.normalizeProvider(process.env.RIG_RUNTIME_ADAPTER) ?? "pi";
|
|
2670
|
+
const description = firstNonEmpty(taskMeta?.description, entry.description);
|
|
2671
|
+
const acceptanceCriteria = firstNonEmpty(taskMeta?.acceptanceCriteria, entry.acceptance_criteria);
|
|
2672
|
+
const browserContractService = await loadCapabilityForRoot(projectRoot, defineCapability2(BROWSER_CONTRACT_SERVICE_CAPABILITY));
|
|
2673
|
+
const browserContext = runtimeContext?.browser ?? browserContractService?.resolveTaskBrowserContext(entry.browser, {
|
|
2674
|
+
hostProjectRoot: projectRoot
|
|
2675
|
+
});
|
|
2676
|
+
console.log(`=== Task: ${activeTask} ===`);
|
|
2677
|
+
console.log("");
|
|
2678
|
+
console.log(`Role: ${entry.role || "unassigned"}`);
|
|
2679
|
+
if (taskMeta) {
|
|
2680
|
+
console.log(`Title: ${taskMeta.title}`);
|
|
2681
|
+
if (taskMeta.status) {
|
|
2682
|
+
console.log(`Status: ${taskMeta.status.toUpperCase()}`);
|
|
2683
|
+
}
|
|
2684
|
+
if (taskMeta.claimId) {
|
|
2685
|
+
console.log(`Claim: ${taskMeta.claimId}`);
|
|
2686
|
+
}
|
|
2687
|
+
if (typeof taskMeta.priority === "number") {
|
|
2688
|
+
console.log(`Priority: P${taskMeta.priority}`);
|
|
2689
|
+
}
|
|
2690
|
+
} else {
|
|
2691
|
+
console.log("Title: (task metadata unavailable)");
|
|
2692
|
+
}
|
|
2693
|
+
if (description) {
|
|
2694
|
+
console.log(`
|
|
2695
|
+
Description:`);
|
|
2696
|
+
printIndented(description);
|
|
2697
|
+
} else {
|
|
2698
|
+
console.log('\nDescription: (not set; add one with `br update <task-id> --description "..."`)');
|
|
2699
|
+
}
|
|
2700
|
+
if (acceptanceCriteria) {
|
|
2701
|
+
console.log(`
|
|
2702
|
+
Acceptance Criteria:`);
|
|
2703
|
+
printIndented(acceptanceCriteria);
|
|
2704
|
+
}
|
|
2705
|
+
console.log(`
|
|
2706
|
+
Scope globs:`);
|
|
2707
|
+
for (const scope of entry.scope || []) {
|
|
2708
|
+
console.log(` - ${scope}`);
|
|
2709
|
+
}
|
|
2710
|
+
if (entry.creates_repo) {
|
|
2711
|
+
console.log(`
|
|
2712
|
+
NOTE: This task creates a new repository. The scope directory does not exist yet \u2014 you are scaffolding it from scratch.`);
|
|
2713
|
+
}
|
|
2714
|
+
const valDescriptions = (() => {
|
|
2715
|
+
try {
|
|
2716
|
+
return readValidationDescriptionsForInvocation(projectRoot);
|
|
2717
|
+
} catch {
|
|
2718
|
+
return {};
|
|
2719
|
+
}
|
|
2720
|
+
})();
|
|
2721
|
+
console.log(`
|
|
2722
|
+
Validation:`);
|
|
2723
|
+
for (const cmd of entry.validation || []) {
|
|
2724
|
+
const desc = valDescriptions[cmd];
|
|
2725
|
+
console.log(desc ? ` $ ${cmd} \u2014 ${desc}` : ` $ ${cmd}`);
|
|
2726
|
+
}
|
|
2727
|
+
if (browserContext?.required) {
|
|
2728
|
+
console.log(`
|
|
2729
|
+
Browser:`);
|
|
2730
|
+
for (const line of browserContractService?.buildBrowserGuidanceLines(browserContext) ?? []) {
|
|
2731
|
+
console.log(` - ${line}`);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
const runtimeTools = runtimeToolSummary();
|
|
2735
|
+
console.log(`
|
|
2736
|
+
Runtime Tools:`);
|
|
2737
|
+
console.log(" - This task runtime provides audited shell tooling and Rig-owned file tools.");
|
|
2738
|
+
console.log(" - Use the Rig-owned router tools for all read, search, edit, and write work inside the task workspace.");
|
|
2739
|
+
for (const line of instructionService?.buildRuntimeContextLines(runtimeProvider) ?? []) {
|
|
2740
|
+
console.log(` - ${line}`);
|
|
2741
|
+
}
|
|
2742
|
+
console.log(" - `write` and `edit` remain restricted to the scoped task workspace, plus the task artifact subtree at `artifacts/<taskId>/` for closeout files.");
|
|
2743
|
+
console.log(` - ${providerToolReferenceLine(runtimeProvider)}`);
|
|
2744
|
+
console.log(" - Runtime tool location: `$RIG_TASK_WORKSPACE/.rig/bin`.");
|
|
2745
|
+
if (runtimeTools.file.length > 0) {
|
|
2746
|
+
console.log(` - Runtime file binaries: ${runtimeTools.file.join(", ")}`);
|
|
2747
|
+
}
|
|
2748
|
+
if (runtimeTools.shell.length > 0) {
|
|
2749
|
+
console.log(` - Shell helpers: ${runtimeTools.shell.join(", ")}`);
|
|
2750
|
+
}
|
|
2751
|
+
console.log(" - Shell commands route through `rig-shell`; file-tool binaries enforce runtime workspace and scope boundaries directly.");
|
|
2752
|
+
console.log(`
|
|
2753
|
+
Dependencies:`);
|
|
2754
|
+
const deps = sourceTaskDependencyIds(sourceTask) ?? taskDependencies(projectRoot, activeTask, tracker);
|
|
2755
|
+
if (deps.length === 0) {
|
|
2756
|
+
console.log(" (none - root task)");
|
|
2757
|
+
} else {
|
|
2758
|
+
for (const dep of deps) {
|
|
2759
|
+
const depMeta = readTaskMetadataFromTracker(projectRoot, dep, tracker);
|
|
2760
|
+
const depStatus = depMeta?.status ? `status=${depMeta.status}` : "";
|
|
2761
|
+
console.log(` - ${dep} ${depStatus}`.trim());
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
async function taskReady(projectRoot) {
|
|
2766
|
+
const readyTaskIds = listReadyTaskIds(loadReadonlyTaskTrackerContext(projectRoot));
|
|
2767
|
+
if (readyTaskIds.length > 0) {
|
|
2768
|
+
process.stdout.write(`${readyTaskIds.join(`
|
|
2769
|
+
`)}
|
|
2770
|
+
`);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
async function taskScope(projectRoot, expandFiles, taskId) {
|
|
2774
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
2775
|
+
if (!activeTask) {
|
|
2776
|
+
throw new Error("No active task.");
|
|
2777
|
+
}
|
|
2778
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
2779
|
+
const sourceTask = loadRuntimeContextFromEnv2()?.sourceTask ?? await readTaskSourceRecordForInvocation(projectRoot, activeTask);
|
|
2780
|
+
const taskConfig = (() => {
|
|
2781
|
+
try {
|
|
2782
|
+
return readTaskConfigForInvocation(projectRoot);
|
|
2783
|
+
} catch {
|
|
2784
|
+
return {};
|
|
2785
|
+
}
|
|
2786
|
+
})();
|
|
2787
|
+
const entry = { ...taskConfig[activeTask] || {}, ...taskConfigEntryFromSourceTask(sourceTask) };
|
|
2788
|
+
const scopes = entry.scope || [];
|
|
2789
|
+
if (scopes.length === 0) {
|
|
2790
|
+
throw new Error(`No scope defined for ${activeTask}.`);
|
|
2791
|
+
}
|
|
2792
|
+
if (!expandFiles) {
|
|
2793
|
+
console.log(`Scope globs for ${activeTask}:`);
|
|
2794
|
+
for (const scope of scopes) {
|
|
2795
|
+
console.log(` ${scope}`);
|
|
2796
|
+
}
|
|
2797
|
+
console.log(`
|
|
2798
|
+
Use --files to expand globs to actual file paths.`);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
console.log(`Files matching scope for ${activeTask}:
|
|
2802
|
+
`);
|
|
2803
|
+
const files = [];
|
|
2804
|
+
for (const scope of scopes) {
|
|
2805
|
+
const normalizedScope = normalizePathToScope(projectRoot, paths.monorepoRoot, scope);
|
|
2806
|
+
const inMonorepo = /^(humanity|moongate|packages|shared-migrations|microservices|TSAPITests|hp-next)\//.test(normalizedScope);
|
|
2807
|
+
if (inMonorepo) {
|
|
2808
|
+
for (const candidate of monorepoSearchCandidates(scope)) {
|
|
2809
|
+
const result = runCapture(["git", "-C", paths.monorepoRoot, "ls-files", candidate], projectRoot);
|
|
2810
|
+
if (result.exitCode === 0) {
|
|
2811
|
+
files.push(...result.stdout.split(/\r?\n/).filter(Boolean));
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
} else {
|
|
2815
|
+
const result = runCapture(["git", "-C", projectRoot, "ls-files", scope], projectRoot);
|
|
2816
|
+
if (result.exitCode === 0) {
|
|
2817
|
+
files.push(...result.stdout.split(/\r?\n/).filter(Boolean));
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
if (files.length === 0) {
|
|
2822
|
+
const taskEntry = entry;
|
|
2823
|
+
if (taskEntry.creates_repo) {
|
|
2824
|
+
console.log("(No files yet \u2014 this is a creates_repo task. You will create these directories.)");
|
|
2825
|
+
} else {
|
|
2826
|
+
console.log("(No files match scope globs.)");
|
|
2827
|
+
}
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
for (const file of unique2(files).sort()) {
|
|
2831
|
+
console.log(file);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
async function taskDeps(projectRoot, taskId) {
|
|
2835
|
+
if (BAKED_DEPS_OUTPUT) {
|
|
2836
|
+
process.stdout.write(BAKED_DEPS_OUTPUT);
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
2840
|
+
if (!activeTask) {
|
|
2841
|
+
throw new Error("No active task.");
|
|
2842
|
+
}
|
|
2843
|
+
const sourceTask = loadRuntimeContextFromEnv2()?.sourceTask ?? await readTaskSourceRecordForInvocation(projectRoot, activeTask);
|
|
2844
|
+
const tracker = loadReadonlyTaskTrackerContext(projectRoot);
|
|
2845
|
+
const deps = sourceTaskDependencyIds(sourceTask) ?? taskDependencies(projectRoot, activeTask, tracker);
|
|
2846
|
+
if (deps.length === 0) {
|
|
2847
|
+
console.log(`No dependencies for ${activeTask}.`);
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
for (const dep of deps) {
|
|
2851
|
+
const artifactDir = artifactDirForId(projectRoot, dep);
|
|
2852
|
+
console.log(`=== ${dep} ===`);
|
|
2853
|
+
if (!existsSync12(artifactDir)) {
|
|
2854
|
+
console.log(` (no artifacts yet)
|
|
2855
|
+
`);
|
|
2856
|
+
continue;
|
|
2857
|
+
}
|
|
2858
|
+
printArtifactSection(resolve11(artifactDir, "decision-log.md"), "--- Decisions ---");
|
|
2859
|
+
printArtifactSection(resolve11(artifactDir, "next-actions.md"), "--- Next Actions (for you) ---");
|
|
2860
|
+
const changedFiles = resolve11(artifactDir, "changed-files.txt");
|
|
2861
|
+
if (existsSync12(changedFiles)) {
|
|
2862
|
+
const lines = readFileSync8(changedFiles, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
2863
|
+
console.log(`--- Changed Files (${lines.length}) ---`);
|
|
2864
|
+
for (const line of lines) {
|
|
2865
|
+
console.log(line);
|
|
2866
|
+
}
|
|
2867
|
+
console.log("");
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function taskStatus(projectRoot) {
|
|
2872
|
+
if (BAKED_STATUS_OUTPUT) {
|
|
2873
|
+
process.stdout.write(BAKED_STATUS_OUTPUT);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
console.log(`=== Project Rig Progress ===
|
|
2877
|
+
`);
|
|
2878
|
+
const tasks = readBeadsTasks(projectRoot, undefined, true);
|
|
2879
|
+
if (tasks.length === 0) {
|
|
2880
|
+
console.log("(no task tracker rows found)");
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
const counts = tasks.reduce((acc, task) => {
|
|
2884
|
+
const key = task.status || "open";
|
|
2885
|
+
acc[key] = (acc[key] || 0) + 1;
|
|
2886
|
+
return acc;
|
|
2887
|
+
}, {});
|
|
2888
|
+
const total = tasks.length;
|
|
2889
|
+
console.log(`Total tasks: ${total}`);
|
|
2890
|
+
for (const status of Object.keys(counts).sort()) {
|
|
2891
|
+
console.log(` ${status}: ${counts[status]}`);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function taskLookup(projectRoot, id) {
|
|
2895
|
+
const result = lookupTask(projectRoot, id);
|
|
2896
|
+
if (!result) {
|
|
2897
|
+
throw new Error(`Not found: ${id}`);
|
|
2898
|
+
}
|
|
2899
|
+
return result;
|
|
2900
|
+
}
|
|
2901
|
+
function taskRecord(projectRoot, type, text, taskId) {
|
|
2902
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
2903
|
+
if (!activeTask) {
|
|
2904
|
+
throw new Error("No active task.");
|
|
2905
|
+
}
|
|
2906
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
2907
|
+
mkdirSync5(paths.stateDir, { recursive: true });
|
|
2908
|
+
if (type === "decision") {
|
|
2909
|
+
const artifactDir = taskArtifactDir(projectRoot, activeTask);
|
|
2910
|
+
mkdirSync5(artifactDir, { recursive: true });
|
|
2911
|
+
const timestamp = nowIso();
|
|
2912
|
+
appendFileSync(resolve11(artifactDir, "decision-log.md"), `
|
|
2913
|
+
### ${timestamp}
|
|
2914
|
+
|
|
2915
|
+
${text}
|
|
2916
|
+
|
|
2917
|
+
`, "utf-8");
|
|
2918
|
+
console.log(`Decision recorded for ${activeTask}.`);
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
const failedPath = paths.failedApproachesPath;
|
|
2922
|
+
if (!existsSync12(failedPath)) {
|
|
2923
|
+
writeFileSync6(failedPath, `# Failed Approaches Log
|
|
2924
|
+
|
|
2925
|
+
This file records approaches that did not work.
|
|
2926
|
+
|
|
2927
|
+
`, "utf-8");
|
|
2928
|
+
}
|
|
2929
|
+
const content = readFileSync8(failedPath, "utf-8");
|
|
2930
|
+
const attempts = (content.match(new RegExp(`^## ${escapeRegExp(activeTask)}\\b`, "gm")) || []).length + 1;
|
|
2931
|
+
appendFileSync(failedPath, `
|
|
2932
|
+
## ${activeTask} - Attempt ${attempts} (${nowIso()})
|
|
2933
|
+
|
|
2934
|
+
**Reason:** ${text}
|
|
2935
|
+
|
|
2936
|
+
---
|
|
2937
|
+
`, "utf-8");
|
|
2938
|
+
console.log(`Failed approach #${attempts} recorded for ${activeTask}. Note: visible to future sessions, not re-read in this session.`);
|
|
2939
|
+
}
|
|
2940
|
+
function taskArtifacts(projectRoot, taskId) {
|
|
2941
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
2942
|
+
if (!activeTask) {
|
|
2943
|
+
throw new Error("No active task.");
|
|
2944
|
+
}
|
|
2945
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
2946
|
+
const artifactDir = taskArtifactDir(projectRoot, activeTask);
|
|
2947
|
+
mkdirSync5(artifactDir, { recursive: true });
|
|
2948
|
+
const changed = changedFilesForTask(projectRoot, activeTask, true);
|
|
2949
|
+
writeFileSync6(resolve11(artifactDir, "changed-files.txt"), `${changed.join(`
|
|
2950
|
+
`)}
|
|
2951
|
+
`, "utf-8");
|
|
2952
|
+
console.log(`changed-files.txt: ${changed.length} files`);
|
|
2953
|
+
const taskResultPath = resolve11(artifactDir, "task-result.json");
|
|
2954
|
+
if (!existsSync12(taskResultPath)) {
|
|
2955
|
+
const template = {
|
|
2956
|
+
task_id: activeTask,
|
|
2957
|
+
status: "completed",
|
|
2958
|
+
summary: "TODO: Write a one-line summary of what you did",
|
|
2959
|
+
completed_at: nowIso()
|
|
2960
|
+
};
|
|
2961
|
+
writeFileSync6(taskResultPath, `${JSON.stringify(template, null, 2)}
|
|
2962
|
+
`, "utf-8");
|
|
2963
|
+
console.log("task-result.json: created (update the summary!)");
|
|
2964
|
+
} else {
|
|
2965
|
+
console.log("task-result.json: already exists");
|
|
2966
|
+
}
|
|
2967
|
+
const decisionLogPath = resolve11(artifactDir, "decision-log.md");
|
|
2968
|
+
if (!existsSync12(decisionLogPath)) {
|
|
2969
|
+
const content = `# Decision Log: ${activeTask}
|
|
2970
|
+
|
|
2971
|
+
Record key decisions here using: rig-agent record decision "..."
|
|
2972
|
+
`;
|
|
2973
|
+
writeFileSync6(decisionLogPath, content, "utf-8");
|
|
2974
|
+
console.log("decision-log.md: created (record your decisions!)");
|
|
2975
|
+
} else {
|
|
2976
|
+
console.log("decision-log.md: already exists");
|
|
2977
|
+
}
|
|
2978
|
+
const nextActionsPath = resolve11(artifactDir, "next-actions.md");
|
|
2979
|
+
if (!existsSync12(nextActionsPath)) {
|
|
2980
|
+
const content = [
|
|
2981
|
+
`# Next Actions: ${activeTask}`,
|
|
2982
|
+
"",
|
|
2983
|
+
"## For downstream tasks",
|
|
2984
|
+
"",
|
|
2985
|
+
"### bd-<downstream-task-id>",
|
|
2986
|
+
"- **Decision impact:** What you decided that affects this downstream task",
|
|
2987
|
+
"- **Recommended approach:** What they should do differently because of your work",
|
|
2988
|
+
"- **Key files:** What files they should look at first",
|
|
2989
|
+
"",
|
|
2990
|
+
"## General notes",
|
|
2991
|
+
"",
|
|
2992
|
+
"- TODO: Replace this scaffold with real content before completion",
|
|
2993
|
+
""
|
|
2994
|
+
].join(`
|
|
2995
|
+
`);
|
|
2996
|
+
writeFileSync6(nextActionsPath, content, "utf-8");
|
|
2997
|
+
console.log("next-actions.md: created (add recommendations for downstream tasks!)");
|
|
2998
|
+
} else {
|
|
2999
|
+
console.log("next-actions.md: already exists");
|
|
3000
|
+
}
|
|
3001
|
+
const validationSummaryPath = resolve11(artifactDir, "validation-summary.json");
|
|
3002
|
+
if (existsSync12(validationSummaryPath)) {
|
|
3003
|
+
console.log("validation-summary.json: already exists");
|
|
3004
|
+
} else {
|
|
3005
|
+
console.log("validation-summary.json: not yet created (run: rig-agent validate)");
|
|
3006
|
+
}
|
|
3007
|
+
console.log(`
|
|
3008
|
+
Artifacts at: ${artifactDir}/`);
|
|
3009
|
+
}
|
|
3010
|
+
function taskArtifactDir(projectRoot, taskId) {
|
|
3011
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
3012
|
+
if (!activeTask) {
|
|
3013
|
+
throw new Error("No active task.");
|
|
3014
|
+
}
|
|
3015
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
3016
|
+
const artifactDir = resolve11(paths.artifactsDir, safePathSegment3(activeTask, { fallback: "task", maxLength: 96 }));
|
|
3017
|
+
assertPathInsideRoot3(paths.artifactsDir, artifactDir, "artifact directory");
|
|
3018
|
+
mkdirSync5(artifactDir, { recursive: true });
|
|
3019
|
+
return artifactDir;
|
|
3020
|
+
}
|
|
3021
|
+
function taskArtifactWrite(projectRoot, filename, content, taskId) {
|
|
3022
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
3023
|
+
if (!activeTask) {
|
|
3024
|
+
throw new Error("No active task.");
|
|
3025
|
+
}
|
|
3026
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
3027
|
+
const artifactDir = resolve11(paths.artifactsDir, safePathSegment3(activeTask, { fallback: "task", maxLength: 96 }));
|
|
3028
|
+
assertPathInsideRoot3(paths.artifactsDir, artifactDir, "artifact directory");
|
|
3029
|
+
mkdirSync5(artifactDir, { recursive: true });
|
|
3030
|
+
const targetPath = assertPathInsideRoot3(artifactDir, resolve11(artifactDir, filename), "artifact file");
|
|
3031
|
+
writeFileSync6(targetPath, content, "utf-8");
|
|
3032
|
+
console.log(`Wrote: ${targetPath}`);
|
|
3033
|
+
}
|
|
3034
|
+
function taskArtifactRead(projectRoot, filename, options) {
|
|
3035
|
+
const activeTask = options?.taskId || currentTaskId(projectRoot);
|
|
3036
|
+
if (!activeTask) {
|
|
3037
|
+
throw new Error("No active task.");
|
|
3038
|
+
}
|
|
3039
|
+
const maxBytes = options?.maxBytes ?? 64 * 1024;
|
|
3040
|
+
const artifactDir = taskArtifactDir(projectRoot, activeTask);
|
|
3041
|
+
const targetPath = assertPathInsideRoot3(artifactDir, resolve11(artifactDir, filename), "artifact file");
|
|
3042
|
+
if (!existsSync12(targetPath)) {
|
|
3043
|
+
throw new Error(`Artifact not found: ${targetPath}`);
|
|
3044
|
+
}
|
|
3045
|
+
const buffer = readFileSync8(targetPath);
|
|
3046
|
+
const preview = buffer.subarray(0, maxBytes);
|
|
3047
|
+
return {
|
|
3048
|
+
path: targetPath,
|
|
3049
|
+
sizeBytes: buffer.length,
|
|
3050
|
+
contents: preview.includes(0) ? "[binary content omitted]" : preview.toString("utf-8"),
|
|
3051
|
+
truncated: buffer.length > maxBytes,
|
|
3052
|
+
maxBytes
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
async function taskValidate(projectRoot, taskId, validatorRegistry) {
|
|
3056
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
3057
|
+
if (!activeTask) {
|
|
3058
|
+
throw new Error("No active task.");
|
|
3059
|
+
}
|
|
3060
|
+
console.log(`Running validation for ${activeTask}...
|
|
3061
|
+
`);
|
|
3062
|
+
const runtimeContext = loadRuntimeContextFromEnv2();
|
|
3063
|
+
const summary = await validateTask(projectRoot, activeTask, runtimeContext, validatorRegistry, runtimeContext ? {} : {
|
|
3064
|
+
taskConfig: (() => {
|
|
3065
|
+
try {
|
|
3066
|
+
return readTaskConfigForInvocation(projectRoot);
|
|
3067
|
+
} catch {
|
|
3068
|
+
return {};
|
|
3069
|
+
}
|
|
3070
|
+
})(),
|
|
3071
|
+
validationDescriptions: (() => {
|
|
3072
|
+
try {
|
|
3073
|
+
return readValidationDescriptionsForInvocation(projectRoot);
|
|
3074
|
+
} catch {
|
|
3075
|
+
return {};
|
|
3076
|
+
}
|
|
3077
|
+
})()
|
|
3078
|
+
});
|
|
3079
|
+
if (summary.status === "skipped") {
|
|
3080
|
+
console.log(`Validation skipped: no validation commands defined`);
|
|
3081
|
+
return true;
|
|
3082
|
+
}
|
|
3083
|
+
if (summary.status !== "pass") {
|
|
3084
|
+
console.log(`Validation failed: ${summary.failed}/${summary.total} failed`);
|
|
3085
|
+
return false;
|
|
3086
|
+
}
|
|
3087
|
+
console.log(`Validation passed: ${summary.passed}/${summary.total}`);
|
|
3088
|
+
return true;
|
|
3089
|
+
}
|
|
3090
|
+
function taskReopen(projectRoot, options) {
|
|
3091
|
+
const dryRun = options.dryRun === true;
|
|
3092
|
+
const tasks = readBeadsTasks(projectRoot);
|
|
3093
|
+
const summary = {
|
|
3094
|
+
mode: options.all ? "all" : "single",
|
|
3095
|
+
requestedTaskId: options.taskId || null,
|
|
3096
|
+
dryRun,
|
|
3097
|
+
closedFound: 0,
|
|
3098
|
+
reopened: [],
|
|
3099
|
+
failed: [],
|
|
3100
|
+
skipped: []
|
|
3101
|
+
};
|
|
3102
|
+
if (options.all) {
|
|
3103
|
+
const reopenable = tasks.filter((task2) => {
|
|
3104
|
+
const normalizedStatus2 = normalizeTaskLifecycleStatus(task2.status);
|
|
3105
|
+
return normalizedStatus2 != null && REOPENABLE_TASK_STATUSES.has(normalizedStatus2);
|
|
3106
|
+
}).map((task2) => task2.id);
|
|
3107
|
+
summary.closedFound = reopenable.length;
|
|
3108
|
+
if (reopenable.length === 0) {
|
|
3109
|
+
console.log("No reopenable tasks found.");
|
|
3110
|
+
return summary;
|
|
3111
|
+
}
|
|
3112
|
+
if (dryRun) {
|
|
3113
|
+
console.log(`Dry-run: would reopen ${reopenable.length} task(s):`);
|
|
3114
|
+
for (const id of reopenable) {
|
|
3115
|
+
console.log(`- ${id}`);
|
|
3116
|
+
}
|
|
3117
|
+
return summary;
|
|
3118
|
+
}
|
|
3119
|
+
for (const id of reopenable) {
|
|
3120
|
+
reopenSingleTask(projectRoot, id, summary);
|
|
3121
|
+
}
|
|
3122
|
+
printTaskReopenSummary(summary);
|
|
3123
|
+
return summary;
|
|
3124
|
+
}
|
|
3125
|
+
if (!options.taskId) {
|
|
3126
|
+
throw new Error("Missing task id. Use --task <beads-id> or --all.");
|
|
3127
|
+
}
|
|
3128
|
+
const task = tasks.find((entry) => entry.id === options.taskId);
|
|
3129
|
+
if (!task) {
|
|
3130
|
+
throw new Error(`Task not found in .beads/issues.jsonl: ${options.taskId}`);
|
|
3131
|
+
}
|
|
3132
|
+
const normalizedStatus = normalizeTaskLifecycleStatus(task.status);
|
|
3133
|
+
if (!normalizedStatus || !REOPENABLE_TASK_STATUSES.has(normalizedStatus)) {
|
|
3134
|
+
summary.skipped.push(options.taskId);
|
|
3135
|
+
console.log(`Task ${options.taskId} is already open.`);
|
|
3136
|
+
return summary;
|
|
3137
|
+
}
|
|
3138
|
+
summary.closedFound = 1;
|
|
3139
|
+
if (dryRun) {
|
|
3140
|
+
console.log(`Dry-run: would reopen task ${options.taskId}.`);
|
|
3141
|
+
return summary;
|
|
3142
|
+
}
|
|
3143
|
+
reopenSingleTask(projectRoot, options.taskId, summary);
|
|
3144
|
+
printTaskReopenSummary(summary);
|
|
3145
|
+
return summary;
|
|
3146
|
+
}
|
|
3147
|
+
function collectTaskChangedFiles(projectRoot, taskId, includeCommitted) {
|
|
3148
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
3149
|
+
const monorepoRepoRoot = resolveTaskMonorepoRoot(projectRoot);
|
|
3150
|
+
const files = [];
|
|
3151
|
+
const projectBaseline = resolveRuntimeDirtyBaseline(projectRoot, projectRoot);
|
|
3152
|
+
const monorepoBaseline = resolveRuntimeDirtyBaseline(projectRoot, monorepoRepoRoot);
|
|
3153
|
+
for (const [repo, prefix] of [
|
|
3154
|
+
[projectRoot, ""],
|
|
3155
|
+
[monorepoRepoRoot, ""]
|
|
3156
|
+
]) {
|
|
3157
|
+
if (!existsSync12(resolve11(repo, ".git"))) {
|
|
3158
|
+
continue;
|
|
3159
|
+
}
|
|
3160
|
+
if (includeCommitted && repo === monorepoRepoRoot) {
|
|
3161
|
+
for (const line of collectCommittedMonorepoFiles(projectRoot, repo)) {
|
|
3162
|
+
files.push(`${prefix}${line}`);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
const baseline = repo === paths.monorepoRoot ? monorepoBaseline : projectBaseline;
|
|
3166
|
+
for (const line of collectWorkingTreeFiles(projectRoot, repo, baseline)) {
|
|
3167
|
+
files.push(`${prefix}${line}`);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
const uniqueFiles = unique2(files).map((file) => normalizeChangedFilePath(file)).filter(Boolean);
|
|
3171
|
+
return filterTaskChangedFiles(projectRoot, taskId, uniqueFiles, false);
|
|
3172
|
+
}
|
|
3173
|
+
function filterTaskChangedFiles(projectRoot, taskId, files, scoped) {
|
|
3174
|
+
const uniqueFiles = unique2(files).map((file) => normalizeChangedFilePath(file)).filter(Boolean);
|
|
3175
|
+
if (!taskId) {
|
|
3176
|
+
return unique2(uniqueFiles).sort();
|
|
3177
|
+
}
|
|
3178
|
+
return unique2(uniqueFiles).filter((file) => !isGeneratedTaskChangePath(taskId, file)).sort();
|
|
3179
|
+
}
|
|
3180
|
+
function resolveTaskMonorepoRoot(projectRoot) {
|
|
3181
|
+
const runtimeWorkspace = loadRuntimeContextFromEnv2()?.workspaceDir || process.env.RIG_TASK_WORKSPACE?.trim();
|
|
3182
|
+
if (runtimeWorkspace && existsSync12(resolve11(runtimeWorkspace, ".git"))) {
|
|
3183
|
+
return resolve11(runtimeWorkspace);
|
|
3184
|
+
}
|
|
3185
|
+
return resolveHarnessPaths(projectRoot).monorepoRoot;
|
|
3186
|
+
}
|
|
3187
|
+
function collectCommittedMonorepoFiles(projectRoot, repo) {
|
|
3188
|
+
const baseRefCandidates = [];
|
|
3189
|
+
const originHead = runCapture(["git", "-C", repo, "rev-parse", "--abbrev-ref", "origin/HEAD"], projectRoot);
|
|
3190
|
+
if (originHead.exitCode === 0 && originHead.stdout.trim()) {
|
|
3191
|
+
baseRefCandidates.push(originHead.stdout.trim());
|
|
3192
|
+
}
|
|
3193
|
+
baseRefCandidates.push("origin/main", "origin/dev", "origin/master", "main", "dev", "master");
|
|
3194
|
+
for (const baseRef of baseRefCandidates) {
|
|
3195
|
+
const mergeBase = runCapture(["git", "-C", repo, "merge-base", "HEAD", baseRef], projectRoot);
|
|
3196
|
+
if (mergeBase.exitCode !== 0 || !mergeBase.stdout.trim())
|
|
3197
|
+
continue;
|
|
3198
|
+
const result = runCapture(["git", "-C", repo, "diff", "--name-only", `${mergeBase.stdout.trim()}..HEAD`], projectRoot);
|
|
3199
|
+
if (result.exitCode === 0) {
|
|
3200
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
const initialHeadCommit = resolveRuntimeInitialHeadCommit(projectRoot, repo);
|
|
3204
|
+
if (initialHeadCommit) {
|
|
3205
|
+
const result = runCapture(["git", "-C", repo, "diff", "--name-only", `${initialHeadCommit}..HEAD`], projectRoot);
|
|
3206
|
+
if (result.exitCode === 0) {
|
|
3207
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
const baseCommit = resolveMonorepoBaseCommit(projectRoot, repo);
|
|
3211
|
+
if (!baseCommit) {
|
|
3212
|
+
return [];
|
|
3213
|
+
}
|
|
3214
|
+
for (const revision of [`${baseCommit}...HEAD`, `${baseCommit}..HEAD`]) {
|
|
3215
|
+
const result = runCapture(["git", "-C", repo, "diff", "--name-only", revision], projectRoot);
|
|
3216
|
+
if (result.exitCode === 0) {
|
|
3217
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
return [];
|
|
3221
|
+
}
|
|
3222
|
+
function resolveRuntimeInitialHeadCommit(projectRoot, repo) {
|
|
3223
|
+
const runtimeContext = loadRuntimeContextFromEnv2();
|
|
3224
|
+
if (runtimeContext?.initialHeadCommits?.monorepo?.trim()) {
|
|
3225
|
+
const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
|
|
3226
|
+
if (resolve11(monorepoRoot) === resolve11(repo)) {
|
|
3227
|
+
return runtimeContext.initialHeadCommits.monorepo.trim();
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return "";
|
|
3231
|
+
}
|
|
3232
|
+
function resolveMonorepoBaseCommit(projectRoot, repo) {
|
|
3233
|
+
const runtimeContext = loadRuntimeContextFromEnv2();
|
|
3234
|
+
if (runtimeContext?.monorepoBaseCommit?.trim()) {
|
|
3235
|
+
const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
|
|
3236
|
+
if (resolve11(monorepoRoot) === resolve11(repo)) {
|
|
3237
|
+
return runtimeContext.monorepoBaseCommit.trim();
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return "";
|
|
3241
|
+
}
|
|
3242
|
+
function collectWorkingTreeFiles(projectRoot, repo, baseline) {
|
|
3243
|
+
const files = new Set;
|
|
3244
|
+
const nativeFiles = nativePendingFiles(repo);
|
|
3245
|
+
if (nativeFiles !== null) {
|
|
3246
|
+
for (const entry of nativeFiles) {
|
|
3247
|
+
const normalized = normalizeChangedFilePath(entry.path);
|
|
3248
|
+
if (!normalized || baseline.has(normalized)) {
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
files.add(normalized);
|
|
3252
|
+
}
|
|
3253
|
+
return [...files].sort();
|
|
3254
|
+
}
|
|
3255
|
+
for (const args of [
|
|
3256
|
+
["diff", "--name-only"],
|
|
3257
|
+
["diff", "--cached", "--name-only"],
|
|
3258
|
+
["ls-files", "--others", "--exclude-standard"]
|
|
3259
|
+
]) {
|
|
3260
|
+
const result = runCapture(["git", "-C", repo, ...args], projectRoot);
|
|
3261
|
+
if (result.exitCode !== 0) {
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
3265
|
+
const normalized = normalizeChangedFilePath(line);
|
|
3266
|
+
if (!normalized || baseline.has(normalized)) {
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
files.add(normalized);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
return [...files].sort();
|
|
3273
|
+
}
|
|
3274
|
+
function resolveRuntimeDirtyBaseline(projectRoot, repo) {
|
|
3275
|
+
const runtimeContext = loadRuntimeContextFromEnv2();
|
|
3276
|
+
const dirtyFiles = runtimeContext?.initialDirtyFiles;
|
|
3277
|
+
if (!dirtyFiles) {
|
|
3278
|
+
return new Set;
|
|
3279
|
+
}
|
|
3280
|
+
const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
|
|
3281
|
+
const selected = resolve11(repo) === resolve11(monorepoRoot) ? dirtyFiles.monorepo : resolve11(repo) === resolve11(projectRoot) ? dirtyFiles.project : undefined;
|
|
3282
|
+
return new Set((selected || []).map((file) => normalizeChangedFilePath(file)).filter(Boolean));
|
|
3283
|
+
}
|
|
3284
|
+
function normalizeChangedFilePath(file) {
|
|
3285
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3286
|
+
}
|
|
3287
|
+
function isGeneratedTaskChangePath(taskId, file) {
|
|
3288
|
+
const normalized = normalizeChangedFilePath(file);
|
|
3289
|
+
if (!normalized) {
|
|
3290
|
+
return true;
|
|
3291
|
+
}
|
|
3292
|
+
if (normalized.startsWith(".rig/") || normalized.startsWith(".beads/") || normalized.startsWith(".worktrees/")) {
|
|
3293
|
+
return true;
|
|
3294
|
+
}
|
|
3295
|
+
const safeTaskId = safePathSegment3(taskId, { fallback: "task", maxLength: 96 });
|
|
3296
|
+
const taskArtifactPrefix = `artifacts/${safeTaskId}/`;
|
|
3297
|
+
if (!normalized.startsWith(taskArtifactPrefix)) {
|
|
3298
|
+
return false;
|
|
3299
|
+
}
|
|
3300
|
+
const artifactRelativePath = normalized.slice(taskArtifactPrefix.length);
|
|
3301
|
+
return GENERATED_TASK_ARTIFACT_FILES.has(artifactRelativePath) || artifactRelativePath.startsWith("runtime-snapshots/");
|
|
3302
|
+
}
|
|
3303
|
+
async function readTaskMetadata(projectRoot, taskId, tracker) {
|
|
3304
|
+
const trackerMetadata = readTaskMetadataFromTracker(projectRoot, taskId, tracker);
|
|
3305
|
+
if (trackerMetadata) {
|
|
3306
|
+
return trackerMetadata;
|
|
3307
|
+
}
|
|
3308
|
+
try {
|
|
3309
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot).getTask(taskId);
|
|
3310
|
+
if (!task) {
|
|
3311
|
+
return null;
|
|
3312
|
+
}
|
|
3313
|
+
const record = task;
|
|
3314
|
+
const title = asNonEmptyString(record.title) || taskId;
|
|
3315
|
+
const metadata = { title };
|
|
3316
|
+
const status = asNonEmptyString(record.status);
|
|
3317
|
+
if (status) {
|
|
3318
|
+
metadata.status = normalizeTaskLifecycleStatus(status) ?? status;
|
|
3319
|
+
}
|
|
3320
|
+
const description = firstNonEmpty(asNonEmptyString(record.description), asNonEmptyString(record.body));
|
|
3321
|
+
if (description)
|
|
3322
|
+
metadata.description = description;
|
|
3323
|
+
const acceptance = firstNonEmpty(asNonEmptyString(record.acceptance_criteria), asNonEmptyString(record.acceptanceCriteria));
|
|
3324
|
+
if (acceptance)
|
|
3325
|
+
metadata.acceptanceCriteria = acceptance;
|
|
3326
|
+
const priority = record.priority;
|
|
3327
|
+
if (typeof priority === "number")
|
|
3328
|
+
metadata.priority = priority;
|
|
3329
|
+
return metadata;
|
|
3330
|
+
} catch {
|
|
3331
|
+
return null;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function readTaskMetadataFromTracker(projectRoot, taskId, tracker) {
|
|
3335
|
+
const context = tracker ?? loadTaskTrackerContext(projectRoot);
|
|
3336
|
+
const record = context.tasksById.get(taskId);
|
|
3337
|
+
if (!record?.title) {
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
const metadata = { title: record.title };
|
|
3341
|
+
const status = asNonEmptyString(record.status);
|
|
3342
|
+
if (status) {
|
|
3343
|
+
const normalizedStatus = normalizeTaskLifecycleStatus(status);
|
|
3344
|
+
metadata.status = normalizedStatus ?? status;
|
|
3345
|
+
if (normalizedStatus) {
|
|
3346
|
+
const taskState = readSourceTaskStateMetadata(projectRoot, taskId, normalizedStatus, context.snapshot);
|
|
3347
|
+
if (taskState?.claimId) {
|
|
3348
|
+
metadata.claimId = taskState.claimId;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
if (typeof record.priority === "number")
|
|
3353
|
+
metadata.priority = record.priority;
|
|
3354
|
+
const description = asNonEmptyString(record.description);
|
|
3355
|
+
if (description)
|
|
3356
|
+
metadata.description = description;
|
|
3357
|
+
const acceptance = firstNonEmpty(asNonEmptyString(record.acceptance_criteria), asNonEmptyString(record.acceptanceCriteria));
|
|
3358
|
+
if (acceptance)
|
|
3359
|
+
metadata.acceptanceCriteria = acceptance;
|
|
3360
|
+
return metadata;
|
|
3361
|
+
}
|
|
3362
|
+
function asNonEmptyString(value) {
|
|
3363
|
+
if (typeof value !== "string") {
|
|
3364
|
+
return "";
|
|
3365
|
+
}
|
|
3366
|
+
return value.trim();
|
|
3367
|
+
}
|
|
3368
|
+
function firstNonEmpty(...values) {
|
|
3369
|
+
for (const value of values) {
|
|
3370
|
+
if (typeof value === "string" && value.trim()) {
|
|
3371
|
+
return value.trim();
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
return "";
|
|
3375
|
+
}
|
|
3376
|
+
function printIndented(text) {
|
|
3377
|
+
for (const line of text.split(/\r?\n/)) {
|
|
3378
|
+
console.log(` ${line}`);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
function readLocalBeadsTasks(projectRoot) {
|
|
3382
|
+
const issuesPath = resolve11(resolveCheckoutRoot(projectRoot), ".beads/issues.jsonl");
|
|
3383
|
+
if (!existsSync12(issuesPath)) {
|
|
3384
|
+
return [];
|
|
3385
|
+
}
|
|
3386
|
+
const tasks = [];
|
|
3387
|
+
for (const line of readFileSync8(issuesPath, "utf-8").split(/\r?\n/)) {
|
|
3388
|
+
const trimmed = line.trim();
|
|
3389
|
+
if (!trimmed) {
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
try {
|
|
3393
|
+
const parsed = JSON.parse(trimmed);
|
|
3394
|
+
if (parsed.issue_type === "task" && parsed.id) {
|
|
3395
|
+
tasks.push({
|
|
3396
|
+
id: parsed.id,
|
|
3397
|
+
status: parsed.status || "open",
|
|
3398
|
+
issue_type: parsed.issue_type,
|
|
3399
|
+
title: parsed.title,
|
|
3400
|
+
priority: parsed.priority,
|
|
3401
|
+
description: parsed.description,
|
|
3402
|
+
acceptance_criteria: parsed.acceptance_criteria,
|
|
3403
|
+
acceptanceCriteria: parsed.acceptanceCriteria,
|
|
3404
|
+
dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies : []
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
} catch {}
|
|
3408
|
+
}
|
|
3409
|
+
return tasks;
|
|
3410
|
+
}
|
|
3411
|
+
function readBeadsTasks(projectRoot, tracker, allowLocalFallback = false) {
|
|
3412
|
+
return (tracker ?? loadTaskTrackerContext(projectRoot, undefined, allowLocalFallback)).tasks;
|
|
3413
|
+
}
|
|
3414
|
+
function loadTaskTrackerContext(projectRoot, snapshot, allowLocalFallback = false) {
|
|
3415
|
+
const resolvedSnapshot = snapshot ?? readSyncedTrackerState(projectRoot, {}, allowLocalFallback ? { allowLocalFallback: true } : {});
|
|
3416
|
+
const tasks = resolvedSnapshot.issues.filter((issue) => issue.issueType === "task").map(projectTaskRecordFromSyncedIssue);
|
|
3417
|
+
const seenTaskIds = new Set(tasks.map((task) => task.id));
|
|
3418
|
+
if (allowLocalFallback) {
|
|
3419
|
+
for (const localTask of readLocalBeadsTasks(projectRoot)) {
|
|
3420
|
+
if (seenTaskIds.has(localTask.id)) {
|
|
3421
|
+
continue;
|
|
3422
|
+
}
|
|
3423
|
+
tasks.push(localTask);
|
|
3424
|
+
seenTaskIds.add(localTask.id);
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return {
|
|
3428
|
+
snapshot: resolvedSnapshot,
|
|
3429
|
+
tasks,
|
|
3430
|
+
tasksById: new Map(tasks.map((task) => [task.id, task]))
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
function loadReadonlyTaskTrackerContext(projectRoot) {
|
|
3434
|
+
return loadTaskTrackerContext(projectRoot, undefined, true);
|
|
3435
|
+
}
|
|
3436
|
+
function listReadyTaskIds(context) {
|
|
3437
|
+
const readyTaskIds = [];
|
|
3438
|
+
for (const issue of context.tasks) {
|
|
3439
|
+
const normalizedStatus = normalizeTaskLifecycleStatus(issue.status) ?? issue.status;
|
|
3440
|
+
if (normalizedStatus === "ready") {
|
|
3441
|
+
readyTaskIds.push(issue.id);
|
|
3442
|
+
continue;
|
|
3443
|
+
}
|
|
3444
|
+
if (normalizedStatus !== "open") {
|
|
3445
|
+
continue;
|
|
3446
|
+
}
|
|
3447
|
+
const hasOpenBlocker = (issue.dependencies ?? []).some((dependency) => {
|
|
3448
|
+
if (dependency.type === "parent-child") {
|
|
3449
|
+
return false;
|
|
3450
|
+
}
|
|
3451
|
+
const targetTaskId = dependency.depends_on_id || dependency.issue_id || dependency.id || "";
|
|
3452
|
+
if (!targetTaskId) {
|
|
3453
|
+
return false;
|
|
3454
|
+
}
|
|
3455
|
+
const dependencyStatus = normalizeTaskLifecycleStatus(context.tasksById.get(targetTaskId)?.status) ?? "unknown";
|
|
3456
|
+
return dependencyStatus !== "completed" && dependencyStatus !== "cancelled";
|
|
3457
|
+
});
|
|
3458
|
+
if (!hasOpenBlocker) {
|
|
3459
|
+
readyTaskIds.push(issue.id);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
return readyTaskIds;
|
|
3463
|
+
}
|
|
3464
|
+
function projectTaskRecordFromSyncedIssue(issue) {
|
|
3465
|
+
const dependencies = issue.dependencies ?? [];
|
|
3466
|
+
return {
|
|
3467
|
+
id: issue.id,
|
|
3468
|
+
status: issue.status === "unknown" ? issue.rawStatus || "open" : issue.status,
|
|
3469
|
+
issue_type: "task",
|
|
3470
|
+
title: issue.title ?? undefined,
|
|
3471
|
+
priority: issue.priority ?? undefined,
|
|
3472
|
+
description: issue.description ?? undefined,
|
|
3473
|
+
acceptance_criteria: issue.acceptanceCriteria ?? undefined,
|
|
3474
|
+
acceptanceCriteria: issue.acceptanceCriteria ?? undefined,
|
|
3475
|
+
dependencies: dependencies.map((dependency) => ({
|
|
3476
|
+
type: dependency.type ?? undefined,
|
|
3477
|
+
issue_id: dependency.issueId ?? undefined,
|
|
3478
|
+
depends_on_id: dependency.dependsOnId ?? undefined,
|
|
3479
|
+
id: dependency.id ?? dependency.issueId ?? dependency.dependsOnId ?? undefined
|
|
3480
|
+
}))
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
function reopenSingleTask(projectRoot, taskId, summary) {
|
|
3484
|
+
try {
|
|
3485
|
+
updateRemoteTrackerTaskLifecycle(projectRoot, {
|
|
3486
|
+
taskId,
|
|
3487
|
+
status: "open",
|
|
3488
|
+
allowedFrom: ["completed", "cancelled", "blocked"],
|
|
3489
|
+
clearMetadata: true,
|
|
3490
|
+
reason: "reopen"
|
|
3491
|
+
});
|
|
3492
|
+
summary.reopened.push(taskId);
|
|
3493
|
+
return;
|
|
3494
|
+
} catch (error) {
|
|
3495
|
+
summary.failed.push(taskId);
|
|
3496
|
+
const details = error instanceof Error ? error.message.trim() : String(error).trim();
|
|
3497
|
+
if (details) {
|
|
3498
|
+
console.log(`WARN: failed to reopen ${taskId}: ${details}`);
|
|
3499
|
+
} else {
|
|
3500
|
+
console.log(`WARN: failed to reopen ${taskId}`);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
function printTaskReopenSummary(summary) {
|
|
3505
|
+
console.log(`Reopened: ${summary.reopened.length}`);
|
|
3506
|
+
if (summary.failed.length > 0) {
|
|
3507
|
+
console.log(`Failed: ${summary.failed.length}`);
|
|
3508
|
+
}
|
|
3509
|
+
if (summary.skipped.length > 0) {
|
|
3510
|
+
console.log(`Skipped: ${summary.skipped.length}`);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
function taskDependencyIds(projectRoot, taskId) {
|
|
3514
|
+
const tracker = loadReadonlyTaskTrackerContext(projectRoot);
|
|
3515
|
+
const canonicalTaskIds = new Set(tracker.tasks.map((task) => task.id));
|
|
3516
|
+
const record = readBeadsTasks(projectRoot, tracker).find((entry) => entry.id === taskId);
|
|
3517
|
+
if (!record?.dependencies?.length) {
|
|
3518
|
+
return [];
|
|
3519
|
+
}
|
|
3520
|
+
const ids = new Set;
|
|
3521
|
+
for (const edge of record.dependencies) {
|
|
3522
|
+
if (!edge || edge.type === "parent-child") {
|
|
3523
|
+
continue;
|
|
3524
|
+
}
|
|
3525
|
+
for (const candidate of [edge.depends_on_id, edge.id, edge.issue_id]) {
|
|
3526
|
+
if (typeof candidate !== "string" || candidate === taskId || !canonicalTaskIds.has(candidate)) {
|
|
3527
|
+
continue;
|
|
3528
|
+
}
|
|
3529
|
+
ids.add(candidate);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
return [...ids].sort();
|
|
3533
|
+
}
|
|
3534
|
+
function taskDependencies(projectRoot, taskId, tracker) {
|
|
3535
|
+
if (!tracker) {
|
|
3536
|
+
return taskDependencyIds(projectRoot, taskId);
|
|
3537
|
+
}
|
|
3538
|
+
const canonicalTaskIds = new Set(tracker.tasks.map((task) => task.id));
|
|
3539
|
+
const record = readBeadsTasks(projectRoot, tracker).find((entry) => entry.id === taskId);
|
|
3540
|
+
if (!record?.dependencies?.length) {
|
|
3541
|
+
return [];
|
|
3542
|
+
}
|
|
3543
|
+
const ids = new Set;
|
|
3544
|
+
for (const edge of record.dependencies) {
|
|
3545
|
+
if (!edge || edge.type === "parent-child") {
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
for (const candidate of [edge.depends_on_id, edge.id, edge.issue_id]) {
|
|
3549
|
+
if (typeof candidate !== "string" || candidate === taskId || !canonicalTaskIds.has(candidate)) {
|
|
3550
|
+
continue;
|
|
3551
|
+
}
|
|
3552
|
+
ids.add(candidate);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return [...ids].sort();
|
|
3556
|
+
}
|
|
3557
|
+
function printArtifactSection(path, header) {
|
|
3558
|
+
if (!existsSync12(path)) {
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
3561
|
+
console.log(header);
|
|
3562
|
+
process.stdout.write(readFileSync8(path, "utf-8"));
|
|
3563
|
+
console.log("");
|
|
3564
|
+
}
|
|
3565
|
+
function escapeRegExp(value) {
|
|
3566
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3567
|
+
}
|
|
3568
|
+
function changedFilesForTask(projectRoot, taskId, scoped) {
|
|
3569
|
+
return filterTaskChangedFiles(projectRoot, taskId, collectTaskChangedFiles(projectRoot, taskId, true), scoped);
|
|
3570
|
+
}
|
|
3571
|
+
function pendingFilesForTask(projectRoot, taskId, scoped) {
|
|
3572
|
+
return filterTaskChangedFiles(projectRoot, taskId, collectTaskChangedFiles(projectRoot, taskId, false), scoped);
|
|
3573
|
+
}
|
|
3574
|
+
var BUILD_CONFIG, BAKED_INFO_OUTPUT, BAKED_DEPS_OUTPUT, BAKED_STATUS_OUTPUT, REOPENABLE_TASK_STATUSES, GENERATED_TASK_ARTIFACT_FILES;
|
|
3575
|
+
var init_task_ops = __esm(() => {
|
|
3576
|
+
init_native_git();
|
|
3577
|
+
init_source_lifecycle();
|
|
3578
|
+
init_task_state();
|
|
3579
|
+
init_source_aware_task_config_source();
|
|
3580
|
+
init_state_sync();
|
|
3581
|
+
init_validator();
|
|
3582
|
+
init_utils();
|
|
3583
|
+
BUILD_CONFIG = readBuildConfig();
|
|
3584
|
+
BAKED_INFO_OUTPUT = BUILD_CONFIG.AGENT_INFO_OUTPUT ?? "";
|
|
3585
|
+
BAKED_DEPS_OUTPUT = BUILD_CONFIG.AGENT_DEPS_OUTPUT ?? "";
|
|
3586
|
+
BAKED_STATUS_OUTPUT = BUILD_CONFIG.AGENT_STATUS_OUTPUT ?? "";
|
|
3587
|
+
REOPENABLE_TASK_STATUSES = new Set(["completed", "cancelled", "blocked"]);
|
|
3588
|
+
GENERATED_TASK_ARTIFACT_FILES = new Set([
|
|
3589
|
+
"changed-files.txt",
|
|
3590
|
+
"decision-log.md",
|
|
3591
|
+
"next-actions.md",
|
|
3592
|
+
"task-result.json",
|
|
3593
|
+
"validation-summary.json",
|
|
3594
|
+
"review-feedback.md",
|
|
3595
|
+
"review-state.json",
|
|
3596
|
+
"review-status.txt",
|
|
3597
|
+
"review-greptile-raw.json",
|
|
3598
|
+
"pr-state.json",
|
|
3599
|
+
"git-state.txt"
|
|
3600
|
+
]);
|
|
3601
|
+
});
|
|
3602
|
+
|
|
3603
|
+
// packages/task-sources-plugin/src/control-plane/tasks/plugin-task-source.ts
|
|
3604
|
+
import { findTaskById as findTaskById2 } from "@rig/core/task-record-reader";
|
|
3605
|
+
function createPluginTaskRecordReader(context, options) {
|
|
3606
|
+
const source = selectTaskSource(context.taskSourceRegistry, options);
|
|
3607
|
+
return {
|
|
3608
|
+
async listTasks() {
|
|
3609
|
+
try {
|
|
3610
|
+
return await source.list();
|
|
3611
|
+
} catch (cause) {
|
|
3612
|
+
throw readFailedError(source, options, "list", cause);
|
|
3613
|
+
}
|
|
3614
|
+
},
|
|
3615
|
+
async getTask(id) {
|
|
3616
|
+
try {
|
|
3617
|
+
if (source.get) {
|
|
3618
|
+
return await source.get(id) ?? null;
|
|
3619
|
+
}
|
|
3620
|
+
return findTaskById2({
|
|
3621
|
+
listTasks: () => source.list(),
|
|
3622
|
+
getTask: async () => null
|
|
3623
|
+
}, id);
|
|
3624
|
+
} catch (cause) {
|
|
3625
|
+
throw readFailedError(source, options, "get", cause);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
function selectTaskSource(registry, options) {
|
|
3631
|
+
try {
|
|
3632
|
+
if (options.sourceId) {
|
|
3633
|
+
return registry.resolveById(options.sourceId);
|
|
3634
|
+
}
|
|
3635
|
+
if (options.sourceKind) {
|
|
3636
|
+
return registry.resolveByKind(options.sourceKind);
|
|
3637
|
+
}
|
|
3638
|
+
const [source] = registry.list();
|
|
3639
|
+
if (source) {
|
|
3640
|
+
return source;
|
|
3641
|
+
}
|
|
3642
|
+
} catch (cause) {
|
|
3643
|
+
throw new TaskRecordSourceError({
|
|
3644
|
+
code: "TASK_SOURCE_NOT_CONFIGURED",
|
|
3645
|
+
message: taskSourceSelectionMessage(options),
|
|
3646
|
+
projectRoot: options.projectRoot,
|
|
3647
|
+
operation: "select",
|
|
3648
|
+
sourceId: options.sourceId ?? null,
|
|
3649
|
+
sourceKind: options.sourceKind ?? null,
|
|
3650
|
+
cause
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
throw new TaskRecordSourceError({
|
|
3654
|
+
code: "TASK_SOURCE_NOT_CONFIGURED",
|
|
3655
|
+
message: taskSourceSelectionMessage(options),
|
|
3656
|
+
projectRoot: options.projectRoot,
|
|
3657
|
+
operation: "select",
|
|
3658
|
+
sourceId: options.sourceId ?? null,
|
|
3659
|
+
sourceKind: options.sourceKind ?? null
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
function taskSourceSelectionMessage(options) {
|
|
3663
|
+
const selector = options.sourceId ? `id "${options.sourceId}"` : options.sourceKind ? `kind "${options.sourceKind}"` : "the configured plugin task source";
|
|
3664
|
+
return `No task source registered for ${selector} in project ${options.projectRoot}. Check rig.config.ts taskSource and loaded plugins.`;
|
|
3665
|
+
}
|
|
3666
|
+
function readFailedError(source, options, operation, cause) {
|
|
3667
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
3668
|
+
return new TaskRecordSourceError({
|
|
3669
|
+
code: "TASK_SOURCE_READ_FAILED",
|
|
3670
|
+
message: `Task source ${source.kind} (${source.id}) failed to ${operation} tasks for project ${options.projectRoot}: ${causeMessage}`,
|
|
3671
|
+
projectRoot: options.projectRoot,
|
|
3672
|
+
operation,
|
|
3673
|
+
sourceId: source.id,
|
|
3674
|
+
sourceKind: source.kind,
|
|
3675
|
+
cause
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
var TaskRecordSourceError;
|
|
3679
|
+
var init_plugin_task_source = __esm(() => {
|
|
3680
|
+
TaskRecordSourceError = class TaskRecordSourceError extends Error {
|
|
3681
|
+
code;
|
|
3682
|
+
projectRoot;
|
|
3683
|
+
sourceId;
|
|
3684
|
+
sourceKind;
|
|
3685
|
+
operation;
|
|
3686
|
+
cause;
|
|
3687
|
+
constructor(input) {
|
|
3688
|
+
super(input.message, { cause: input.cause });
|
|
3689
|
+
this.name = "TaskRecordSourceError";
|
|
3690
|
+
this.code = input.code;
|
|
3691
|
+
this.projectRoot = input.projectRoot;
|
|
3692
|
+
this.operation = input.operation;
|
|
3693
|
+
this.sourceId = input.sourceId ?? null;
|
|
3694
|
+
this.sourceKind = input.sourceKind ?? null;
|
|
3695
|
+
this.cause = input.cause;
|
|
3696
|
+
}
|
|
3697
|
+
};
|
|
3698
|
+
});
|
|
3699
|
+
|
|
3700
|
+
// packages/task-sources-plugin/src/control-plane/task-data-service.ts
|
|
3701
|
+
var exports_task_data_service = {};
|
|
3702
|
+
__export(exports_task_data_service, {
|
|
3703
|
+
createTaskDataService: () => createTaskDataService
|
|
3704
|
+
});
|
|
3705
|
+
function createTaskDataService() {
|
|
3706
|
+
return {
|
|
3707
|
+
currentTaskId,
|
|
3708
|
+
readTaskConfig,
|
|
3709
|
+
readSourceTaskConfig,
|
|
3710
|
+
lookupTask,
|
|
3711
|
+
artifactDirForId,
|
|
3712
|
+
taskInfo,
|
|
3713
|
+
taskDeps,
|
|
3714
|
+
taskStatus,
|
|
3715
|
+
taskScope,
|
|
3716
|
+
taskReady,
|
|
3717
|
+
taskLookup,
|
|
3718
|
+
taskRecord,
|
|
3719
|
+
taskReopen,
|
|
3720
|
+
taskValidate,
|
|
3721
|
+
taskArtifacts,
|
|
3722
|
+
taskArtifactDir,
|
|
3723
|
+
taskArtifactWrite,
|
|
3724
|
+
taskArtifactRead,
|
|
3725
|
+
taskDependencyIds,
|
|
3726
|
+
changedFilesForTask,
|
|
3727
|
+
pendingFilesForTask,
|
|
3728
|
+
readSourceAwareTaskStatus,
|
|
3729
|
+
readConfiguredTaskSourceTask,
|
|
3730
|
+
updateConfiguredTaskSourceTask,
|
|
3731
|
+
buildTaskRunLifecycleComment,
|
|
3732
|
+
updateGithubIssueTaskBySourceIssueId,
|
|
3733
|
+
updateSourceAwareTaskConfigTask,
|
|
3734
|
+
updateRunTaskSourceLifecycle,
|
|
3735
|
+
createLegacyTaskConfigRecordReader,
|
|
3736
|
+
createSourceAwareTaskConfigRecordReader,
|
|
3737
|
+
createPluginTaskRecordReader,
|
|
3738
|
+
readSyncedTrackerState,
|
|
3739
|
+
listReadyTaskIdsFromTracker,
|
|
3740
|
+
normalizeTaskLifecycleStatus
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
var init_task_data_service = __esm(() => {
|
|
3744
|
+
init_task_state();
|
|
3745
|
+
init_task_ops();
|
|
3746
|
+
init_source_aware_task_config_source();
|
|
3747
|
+
init_legacy_task_config_source();
|
|
3748
|
+
init_plugin_task_source();
|
|
3749
|
+
init_source_lifecycle();
|
|
3750
|
+
init_state_sync();
|
|
3751
|
+
init_types();
|
|
3752
|
+
});
|
|
3753
|
+
|
|
3754
|
+
// packages/task-sources-plugin/src/control-plane/task-io-service.ts
|
|
3755
|
+
var exports_task_io_service = {};
|
|
3756
|
+
__export(exports_task_io_service, {
|
|
3757
|
+
createTaskIoService: () => createTaskIoService
|
|
3758
|
+
});
|
|
3759
|
+
import { resolvePluginHost as resolvePluginHost2 } from "@rig/core/project-plugins";
|
|
3760
|
+
function isRecord(value) {
|
|
3761
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3762
|
+
}
|
|
3763
|
+
function taskText(value) {
|
|
3764
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3765
|
+
}
|
|
3766
|
+
function taskUrl(record) {
|
|
3767
|
+
const metadata = record;
|
|
3768
|
+
return taskText(metadata.url) ?? taskText(metadata.html_url) ?? taskText(metadata.webUrl);
|
|
3769
|
+
}
|
|
3770
|
+
function taskBody(record) {
|
|
3771
|
+
const metadata = record;
|
|
3772
|
+
return taskText(metadata.body) ?? taskText(metadata.description);
|
|
3773
|
+
}
|
|
3774
|
+
function taskTitle(record) {
|
|
3775
|
+
const metadata = record;
|
|
3776
|
+
return taskText(metadata.title) ?? taskText(metadata.name) ?? record.id;
|
|
3777
|
+
}
|
|
3778
|
+
function taskStringList(value) {
|
|
3779
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
3780
|
+
}
|
|
3781
|
+
function taskDependencies2(record) {
|
|
3782
|
+
const raw = record;
|
|
3783
|
+
const dependencies = taskStringList(raw.dependencies);
|
|
3784
|
+
if (dependencies.length > 0)
|
|
3785
|
+
return dependencies;
|
|
3786
|
+
const deps = taskStringList(raw.deps);
|
|
3787
|
+
return deps.length > 0 ? deps : null;
|
|
3788
|
+
}
|
|
3789
|
+
function taskParentChildDeps(record) {
|
|
3790
|
+
const raw = record;
|
|
3791
|
+
const parentChildDeps = taskStringList(raw.parentChildDeps);
|
|
3792
|
+
if (parentChildDeps.length > 0)
|
|
3793
|
+
return parentChildDeps;
|
|
3794
|
+
const parents = taskStringList(raw.parents);
|
|
3795
|
+
return parents.length > 0 ? parents : null;
|
|
3796
|
+
}
|
|
3797
|
+
function taskCreateInput(task) {
|
|
3798
|
+
const deps = Array.isArray(task.dependencies) ? task.dependencies.filter((entry) => typeof entry === "string") : Array.isArray(task.deps) ? task.deps.filter((entry) => typeof entry === "string") : [];
|
|
3799
|
+
const parents = Array.isArray(task.parents) ? task.parents.filter((entry) => typeof entry === "string") : [];
|
|
3800
|
+
return {
|
|
3801
|
+
title: taskText(task.title) ?? taskText(task.name) ?? "Untitled task",
|
|
3802
|
+
body: taskText(task.body) ?? taskText(task.description) ?? "",
|
|
3803
|
+
...deps.length > 0 ? { deps } : {},
|
|
3804
|
+
...parents.length > 0 ? { parents } : {},
|
|
3805
|
+
metadata: { ...task }
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
function toRigTask(record, sourceKind) {
|
|
3809
|
+
const dependencies = taskDependencies2(record);
|
|
3810
|
+
const parentChildDeps = taskParentChildDeps(record);
|
|
3811
|
+
return {
|
|
3812
|
+
...record,
|
|
3813
|
+
id: record.id,
|
|
3814
|
+
title: taskTitle(record),
|
|
3815
|
+
status: typeof record.status === "string" ? record.status : null,
|
|
3816
|
+
source: sourceKind,
|
|
3817
|
+
url: taskUrl(record),
|
|
3818
|
+
body: taskBody(record),
|
|
3819
|
+
...dependencies ? { dependencies } : {},
|
|
3820
|
+
...parentChildDeps ? { parentChildDeps } : {}
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
async function loadTaskSource(projectRoot) {
|
|
3824
|
+
const { config, host: pluginHost } = await resolvePluginHost2(projectRoot);
|
|
3825
|
+
const factory = pluginHost.resolveTaskSourceFactoryByKind(config.taskSource.kind);
|
|
3826
|
+
if (!factory) {
|
|
3827
|
+
const kinds = pluginHost.listExecutableTaskSources().map((entry) => entry.kind).join(", ") || "none";
|
|
3828
|
+
throw new Error(`No task source factory registered for kind ${config.taskSource.kind}. Registered task sources: ${kinds}.`);
|
|
3829
|
+
}
|
|
3830
|
+
return { kind: config.taskSource.kind, source: factory.factory(config.taskSource, { projectRoot }) };
|
|
3831
|
+
}
|
|
3832
|
+
function getTaskCreator(source) {
|
|
3833
|
+
const record = source;
|
|
3834
|
+
if (typeof record.createTask === "function")
|
|
3835
|
+
return record.createTask.bind(source);
|
|
3836
|
+
if (typeof record.create === "function")
|
|
3837
|
+
return (task) => record.create?.(taskCreateInput(task));
|
|
3838
|
+
return null;
|
|
3839
|
+
}
|
|
3840
|
+
function createTaskIoService() {
|
|
3841
|
+
return {
|
|
3842
|
+
async listTasks(projectRoot) {
|
|
3843
|
+
const { kind, source } = await loadTaskSource(projectRoot);
|
|
3844
|
+
return (await source.list()).map((task) => toRigTask(task, kind));
|
|
3845
|
+
},
|
|
3846
|
+
async getTask(projectRoot, taskId) {
|
|
3847
|
+
const { kind, source } = await loadTaskSource(projectRoot);
|
|
3848
|
+
const task = source.get ? await source.get(taskId) ?? null : (await source.list()).find((entry) => entry.id === taskId) ?? null;
|
|
3849
|
+
return task ? toRigTask(task, kind) : null;
|
|
3850
|
+
},
|
|
3851
|
+
async createTask(projectRoot, task) {
|
|
3852
|
+
const { kind, source } = await loadTaskSource(projectRoot);
|
|
3853
|
+
const creator = getTaskCreator(source);
|
|
3854
|
+
if (!creator)
|
|
3855
|
+
throw new Error(`The configured ${kind} task source does not expose a task creation API.`);
|
|
3856
|
+
const result = await creator(task);
|
|
3857
|
+
const taskId = typeof result === "string" ? result : isRecord(result) && typeof result.id === "string" ? result.id : null;
|
|
3858
|
+
return { taskId, source: kind, result };
|
|
3859
|
+
}
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
var init_task_io_service = () => {};
|
|
3863
|
+
|
|
2
3864
|
// packages/task-sources-plugin/src/plugin.ts
|
|
3
|
-
import { resolve as
|
|
3865
|
+
import { resolve as resolve12 } from "path";
|
|
4
3866
|
import { definePlugin } from "@rig/core/config";
|
|
3867
|
+
import { defineCapability as defineCapability3 } from "@rig/core/capability";
|
|
3868
|
+
import { TASK_DATA_SERVICE_CAPABILITY, TASK_IO_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
5
3869
|
import {
|
|
6
3870
|
createEnvGitHubCredentialProvider,
|
|
7
3871
|
createStateGitHubCredentialProvider
|
|
8
|
-
} from "@rig/github-
|
|
3872
|
+
} from "@rig/github-lib";
|
|
9
3873
|
|
|
10
3874
|
// packages/task-sources-plugin/src/github-issues-source.ts
|
|
11
3875
|
import { spawnSync } from "child_process";
|
|
@@ -954,16 +4818,16 @@ function requireStringField(config, field, kind) {
|
|
|
954
4818
|
}
|
|
955
4819
|
return value;
|
|
956
4820
|
}
|
|
957
|
-
function
|
|
4821
|
+
function isRecord2(value) {
|
|
958
4822
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
959
4823
|
}
|
|
960
4824
|
function optionalString(value) {
|
|
961
4825
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
962
4826
|
}
|
|
963
4827
|
function parseGitHubProjectsOptions(value) {
|
|
964
|
-
if (!
|
|
4828
|
+
if (!isRecord2(value))
|
|
965
4829
|
return;
|
|
966
|
-
const statusesSource =
|
|
4830
|
+
const statusesSource = isRecord2(value.statuses) ? value.statuses : undefined;
|
|
967
4831
|
const statuses = {};
|
|
968
4832
|
for (const key of ["todo", "running", "prOpen", "ciFixing", "merging", "done", "needsAttention"]) {
|
|
969
4833
|
const status = optionalString(statusesSource?.[key]);
|
|
@@ -984,86 +4848,85 @@ function parseGitHubProjectsOptions(value) {
|
|
|
984
4848
|
return parsed;
|
|
985
4849
|
}
|
|
986
4850
|
function githubProjectsOptionsFromConfig(config, context) {
|
|
987
|
-
const rigConfig =
|
|
988
|
-
const github =
|
|
4851
|
+
const rigConfig = isRecord2(context?.rigConfig) ? context.rigConfig : undefined;
|
|
4852
|
+
const github = isRecord2(rigConfig?.github) ? rigConfig.github : undefined;
|
|
989
4853
|
return parseGitHubProjectsOptions(config.options?.projects) ?? parseGitHubProjectsOptions(github?.projects);
|
|
990
4854
|
}
|
|
991
4855
|
function booleanOption(value) {
|
|
992
4856
|
return typeof value === "boolean" ? value : undefined;
|
|
993
4857
|
}
|
|
4858
|
+
var TaskDataCap = defineCapability3(TASK_DATA_SERVICE_CAPABILITY);
|
|
4859
|
+
var TaskIoCap = defineCapability3(TASK_IO_SERVICE_CAPABILITY);
|
|
994
4860
|
function createTaskSourcesPlugin(opts = {}) {
|
|
995
4861
|
return definePlugin({
|
|
996
4862
|
name: "@rig/standard-plugin:task-sources",
|
|
997
4863
|
version: "0.1.0",
|
|
998
4864
|
contributes: {
|
|
4865
|
+
capabilities: [
|
|
4866
|
+
TaskDataCap.provide(async () => (await Promise.resolve().then(() => (init_task_data_service(), exports_task_data_service))).createTaskDataService(), {
|
|
4867
|
+
title: "Task data",
|
|
4868
|
+
description: "Task-config/state reads, task commands, artifacts, validation, and synced-tracker projection."
|
|
4869
|
+
}),
|
|
4870
|
+
TaskIoCap.provide(async () => (await Promise.resolve().then(() => (init_task_io_service(), exports_task_io_service))).createTaskIoService(), {
|
|
4871
|
+
title: "Task IO",
|
|
4872
|
+
description: "Normalized task-source list/get/create operations for CLI and cockpit consumers."
|
|
4873
|
+
})
|
|
4874
|
+
],
|
|
999
4875
|
taskSources: [
|
|
1000
4876
|
{
|
|
1001
4877
|
id: "std:github-issues",
|
|
1002
4878
|
kind: "github-issues",
|
|
1003
|
-
description: "GitHub Issues via gh CLI"
|
|
4879
|
+
description: "GitHub Issues via gh CLI",
|
|
4880
|
+
factory(config, context) {
|
|
4881
|
+
const options = {
|
|
4882
|
+
owner: requireStringField(config, "owner", "github-issues"),
|
|
4883
|
+
repo: requireStringField(config, "repo", "github-issues")
|
|
4884
|
+
};
|
|
4885
|
+
const credentialProviderOptions = context?.projectRoot ? { stateDir: resolve12(context.projectRoot, ".rig", "state") } : {};
|
|
4886
|
+
options.credentialProvider = opts.githubCredentialProvider ?? createStateGitHubCredentialProvider(credentialProviderOptions);
|
|
4887
|
+
if (opts.githubWorkspaceId)
|
|
4888
|
+
options.workspaceId = opts.githubWorkspaceId;
|
|
4889
|
+
if (opts.githubUserId)
|
|
4890
|
+
options.userId = opts.githubUserId;
|
|
4891
|
+
if (opts.githubSpawn)
|
|
4892
|
+
options.spawn = opts.githubSpawn;
|
|
4893
|
+
if (opts.onGitHubTaskChanged)
|
|
4894
|
+
options.onTaskChanged = opts.onGitHubTaskChanged;
|
|
4895
|
+
if (config.labels !== undefined)
|
|
4896
|
+
options.labels = config.labels;
|
|
4897
|
+
if (config.state !== undefined)
|
|
4898
|
+
options.state = config.state;
|
|
4899
|
+
const assignee = typeof config.options?.assignee === "string" ? config.options.assignee : process.env.RIG_GITHUB_ASSIGNEE;
|
|
4900
|
+
if (assignee?.trim())
|
|
4901
|
+
options.assignee = assignee.trim();
|
|
4902
|
+
const timeoutMs = typeof config.options?.timeoutMs === "number" ? config.options.timeoutMs : undefined;
|
|
4903
|
+
if (timeoutMs !== undefined)
|
|
4904
|
+
options.timeoutMs = timeoutMs;
|
|
4905
|
+
const listLimit = typeof config.options?.listLimit === "number" ? config.options.listLimit : undefined;
|
|
4906
|
+
if (listLimit !== undefined)
|
|
4907
|
+
options.listLimit = listLimit;
|
|
4908
|
+
const projects = githubProjectsOptionsFromConfig(config, context);
|
|
4909
|
+
if (projects)
|
|
4910
|
+
options.projects = projects;
|
|
4911
|
+
const useNativeDependencies = booleanOption(config.options?.useNativeDependencies);
|
|
4912
|
+
if (useNativeDependencies !== undefined)
|
|
4913
|
+
options.useNativeDependencies = useNativeDependencies;
|
|
4914
|
+
return createGitHubIssuesTaskSource(options);
|
|
4915
|
+
}
|
|
1004
4916
|
},
|
|
1005
4917
|
{
|
|
1006
4918
|
id: "std:files",
|
|
1007
4919
|
kind: "files",
|
|
1008
|
-
description: "JSON files in a local directory"
|
|
4920
|
+
description: "JSON files in a local directory",
|
|
4921
|
+
factory(config, context) {
|
|
4922
|
+
return createFilesTaskSource({
|
|
4923
|
+
path: requireStringField(config, "path", "files"),
|
|
4924
|
+
...context?.projectRoot ? { projectRoot: context.projectRoot } : {}
|
|
4925
|
+
});
|
|
4926
|
+
}
|
|
1009
4927
|
}
|
|
1010
4928
|
]
|
|
1011
4929
|
}
|
|
1012
|
-
}, {
|
|
1013
|
-
taskSources: [
|
|
1014
|
-
{
|
|
1015
|
-
id: "std:github-issues",
|
|
1016
|
-
kind: "github-issues",
|
|
1017
|
-
description: "GitHub Issues via gh CLI",
|
|
1018
|
-
factory(config, context) {
|
|
1019
|
-
const options = {
|
|
1020
|
-
owner: requireStringField(config, "owner", "github-issues"),
|
|
1021
|
-
repo: requireStringField(config, "repo", "github-issues")
|
|
1022
|
-
};
|
|
1023
|
-
const credentialProviderOptions = context?.projectRoot ? { stateDir: resolve2(context.projectRoot, ".rig", "state") } : {};
|
|
1024
|
-
options.credentialProvider = opts.githubCredentialProvider ?? createStateGitHubCredentialProvider(credentialProviderOptions);
|
|
1025
|
-
if (opts.githubWorkspaceId)
|
|
1026
|
-
options.workspaceId = opts.githubWorkspaceId;
|
|
1027
|
-
if (opts.githubUserId)
|
|
1028
|
-
options.userId = opts.githubUserId;
|
|
1029
|
-
if (opts.githubSpawn)
|
|
1030
|
-
options.spawn = opts.githubSpawn;
|
|
1031
|
-
if (opts.onGitHubTaskChanged)
|
|
1032
|
-
options.onTaskChanged = opts.onGitHubTaskChanged;
|
|
1033
|
-
if (config.labels !== undefined)
|
|
1034
|
-
options.labels = config.labels;
|
|
1035
|
-
if (config.state !== undefined)
|
|
1036
|
-
options.state = config.state;
|
|
1037
|
-
const assignee = typeof config.options?.assignee === "string" ? config.options.assignee : process.env.RIG_GITHUB_ASSIGNEE;
|
|
1038
|
-
if (assignee?.trim())
|
|
1039
|
-
options.assignee = assignee.trim();
|
|
1040
|
-
const timeoutMs = typeof config.options?.timeoutMs === "number" ? config.options.timeoutMs : undefined;
|
|
1041
|
-
if (timeoutMs !== undefined)
|
|
1042
|
-
options.timeoutMs = timeoutMs;
|
|
1043
|
-
const listLimit = typeof config.options?.listLimit === "number" ? config.options.listLimit : undefined;
|
|
1044
|
-
if (listLimit !== undefined)
|
|
1045
|
-
options.listLimit = listLimit;
|
|
1046
|
-
const projects = githubProjectsOptionsFromConfig(config, context);
|
|
1047
|
-
if (projects)
|
|
1048
|
-
options.projects = projects;
|
|
1049
|
-
const useNativeDependencies = booleanOption(config.options?.useNativeDependencies);
|
|
1050
|
-
if (useNativeDependencies !== undefined)
|
|
1051
|
-
options.useNativeDependencies = useNativeDependencies;
|
|
1052
|
-
return createGitHubIssuesTaskSource(options);
|
|
1053
|
-
}
|
|
1054
|
-
},
|
|
1055
|
-
{
|
|
1056
|
-
id: "std:files",
|
|
1057
|
-
kind: "files",
|
|
1058
|
-
description: "JSON files in a local directory",
|
|
1059
|
-
factory(config, context) {
|
|
1060
|
-
return createFilesTaskSource({
|
|
1061
|
-
path: requireStringField(config, "path", "files"),
|
|
1062
|
-
...context?.projectRoot ? { projectRoot: context.projectRoot } : {}
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
]
|
|
1067
4930
|
});
|
|
1068
4931
|
}
|
|
1069
4932
|
var createStandardTaskSourcesPlugin = createTaskSourcesPlugin;
|