@h-rig/runtime 0.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/bin/rig-agent-dispatch.js +9615 -0
- package/dist/bin/rig-agent.js +9512 -0
- package/dist/bin/rig-browser-tool.js +269 -0
- package/dist/src/agent-mode.js +48 -0
- package/dist/src/baked-secrets.js +121 -0
- package/dist/src/binary-build-worker.js +312 -0
- package/dist/src/binary-run.js +540 -0
- package/dist/src/boundaries.js +1 -0
- package/dist/src/build-time-config.js +25 -0
- package/dist/src/control-plane/agent-roles.js +27 -0
- package/dist/src/control-plane/agent-wrapper.js +9621 -0
- package/dist/src/control-plane/authority-files.js +582 -0
- package/dist/src/control-plane/browser-contract.js +135 -0
- package/dist/src/control-plane/controlled-bash.js +1111 -0
- package/dist/src/control-plane/errors.js +13 -0
- package/dist/src/control-plane/harness-main.js +10828 -0
- package/dist/src/control-plane/hook-materializer.js +75 -0
- package/dist/src/control-plane/hooks/audit-trail.js +353 -0
- package/dist/src/control-plane/hooks/completion-verification.js +7552 -0
- package/dist/src/control-plane/hooks/import-guard.js +890 -0
- package/dist/src/control-plane/hooks/inject-context.js +4189 -0
- package/dist/src/control-plane/hooks/post-edit-lint.js +43 -0
- package/dist/src/control-plane/hooks/safety-guard.js +910 -0
- package/dist/src/control-plane/hooks/scope-guard.js +907 -0
- package/dist/src/control-plane/hooks/shared.js +44 -0
- package/dist/src/control-plane/hooks/submodule-branch.js +7797 -0
- package/dist/src/control-plane/hooks/task-runtime-start.js +7799 -0
- package/dist/src/control-plane/hooks/test-integrity-guard.js +891 -0
- package/dist/src/control-plane/materialize-task-config.js +453 -0
- package/dist/src/control-plane/memory-sync/cli.js +2019 -0
- package/dist/src/control-plane/memory-sync/db.js +753 -0
- package/dist/src/control-plane/memory-sync/embed.js +281 -0
- package/dist/src/control-plane/memory-sync/index.js +2049 -0
- package/dist/src/control-plane/memory-sync/query.js +294 -0
- package/dist/src/control-plane/memory-sync/read.js +784 -0
- package/dist/src/control-plane/memory-sync/types.js +6 -0
- package/dist/src/control-plane/memory-sync/write.js +1547 -0
- package/dist/src/control-plane/native/git-native.js +490 -0
- package/dist/src/control-plane/native/git-ops.js +2860 -0
- package/dist/src/control-plane/native/harness-cli.js +9721 -0
- package/dist/src/control-plane/native/pr-automation.js +373 -0
- package/dist/src/control-plane/native/profile-ops.js +481 -0
- package/dist/src/control-plane/native/repo-ops.js +2342 -0
- package/dist/src/control-plane/native/root-resolver.js +66 -0
- package/dist/src/control-plane/native/run-ops.js +3281 -0
- package/dist/src/control-plane/native/runtime-native-sidecar.js +299 -0
- package/dist/src/control-plane/native/runtime-native.js +392 -0
- package/dist/src/control-plane/native/scope-rules.js +17 -0
- package/dist/src/control-plane/native/task-ops.js +6320 -0
- package/dist/src/control-plane/native/task-state.js +1512 -0
- package/dist/src/control-plane/native/utils.js +535 -0
- package/dist/src/control-plane/native/validator-binaries.js +889 -0
- package/dist/src/control-plane/native/validator.js +2197 -0
- package/dist/src/control-plane/native/verifier.js +3249 -0
- package/dist/src/control-plane/native/workspace-ops.js +1635 -0
- package/dist/src/control-plane/plugin-host-context.js +334 -0
- package/dist/src/control-plane/project-main-pre-run-sync.js +630 -0
- package/dist/src/control-plane/provider/claude-stream-records.js +158 -0
- package/dist/src/control-plane/provider/codex-app-server.js +885 -0
- package/dist/src/control-plane/provider/codex-exec-records.js +203 -0
- package/dist/src/control-plane/provider/rig-task-run-skill.js +39 -0
- package/dist/src/control-plane/provider/runtime-instructions.js +96 -0
- package/dist/src/control-plane/remote.js +854 -0
- package/dist/src/control-plane/repos/index.js +473 -0
- package/dist/src/control-plane/repos/layout.js +124 -0
- package/dist/src/control-plane/repos/mirror/bootstrap.js +268 -0
- package/dist/src/control-plane/repos/mirror/refresh.js +398 -0
- package/dist/src/control-plane/repos/mirror/state.js +167 -0
- package/dist/src/control-plane/repos/registry.js +77 -0
- package/dist/src/control-plane/repos/types.js +1 -0
- package/dist/src/control-plane/runtime/agent-mode.js +48 -0
- package/dist/src/control-plane/runtime/baked-secrets.js +120 -0
- package/dist/src/control-plane/runtime/claude-tool-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/claude-tool-router.js +520 -0
- package/dist/src/control-plane/runtime/context.js +216 -0
- package/dist/src/control-plane/runtime/events.js +218 -0
- package/dist/src/control-plane/runtime/guard-types.js +6 -0
- package/dist/src/control-plane/runtime/guard.js +880 -0
- package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +1194 -0
- package/dist/src/control-plane/runtime/image/index.js +2255 -0
- package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +1191 -0
- package/dist/src/control-plane/runtime/image.js +2255 -0
- package/dist/src/control-plane/runtime/index.js +8511 -0
- package/dist/src/control-plane/runtime/isolation/discovery.js +599 -0
- package/dist/src/control-plane/runtime/isolation/home.js +1217 -0
- package/dist/src/control-plane/runtime/isolation/index.js +8193 -0
- package/dist/src/control-plane/runtime/isolation/runner.js +2651 -0
- package/dist/src/control-plane/runtime/isolation/shared.js +501 -0
- package/dist/src/control-plane/runtime/isolation/toolchain.js +1892 -0
- package/dist/src/control-plane/runtime/isolation/types.js +1 -0
- package/dist/src/control-plane/runtime/isolation/worktree.js +509 -0
- package/dist/src/control-plane/runtime/isolation.js +8193 -0
- package/dist/src/control-plane/runtime/overlay.js +67 -0
- package/dist/src/control-plane/runtime/plugin-mode.js +41 -0
- package/dist/src/control-plane/runtime/plugins.js +1131 -0
- package/dist/src/control-plane/runtime/provisioning-env.js +220 -0
- package/dist/src/control-plane/runtime/queue.js +8358 -0
- package/dist/src/control-plane/runtime/rig-shell.js +205 -0
- package/dist/src/control-plane/runtime/rig-tools.js +182 -0
- package/dist/src/control-plane/runtime/runner-context.js +1 -0
- package/dist/src/control-plane/runtime/runtime-paths.js +184 -0
- package/dist/src/control-plane/runtime/sandbox/backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox/backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox/backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox/backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox/orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox/utils.js +137 -0
- package/dist/src/control-plane/runtime/sandbox-backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox-backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox-backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox-backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox-orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox-utils.js +137 -0
- package/dist/src/control-plane/runtime/snapshot/index.js +454 -0
- package/dist/src/control-plane/runtime/snapshot/sidecar.js +502 -0
- package/dist/src/control-plane/runtime/snapshot/task-run.js +1578 -0
- package/dist/src/control-plane/runtime/snapshot-sidecar.js +498 -0
- package/dist/src/control-plane/runtime/snapshot.js +454 -0
- package/dist/src/control-plane/runtime/task-run-snapshot.js +1578 -0
- package/dist/src/control-plane/runtime/tool-gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/browser-tools.js +32 -0
- package/dist/src/control-plane/runtime/tooling/claude-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/tooling/claude-router.js +524 -0
- package/dist/src/control-plane/runtime/tooling/file-tools.js +182 -0
- package/dist/src/control-plane/runtime/tooling/gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/index.js +1290 -0
- package/dist/src/control-plane/runtime/tooling/shell.js +205 -0
- package/dist/src/control-plane/runtime/types.js +1 -0
- package/dist/src/control-plane/setup-version.js +14 -0
- package/dist/src/control-plane/state-sync/index.js +1509 -0
- package/dist/src/control-plane/state-sync/read.js +856 -0
- package/dist/src/control-plane/state-sync/reconcile.js +260 -0
- package/dist/src/control-plane/state-sync/repo.js +302 -0
- package/dist/src/control-plane/state-sync/types.js +111 -0
- package/dist/src/control-plane/state-sync/write.js +1469 -0
- package/dist/src/control-plane/task-fields.js +38 -0
- package/dist/src/control-plane/task-source-bootstrap.js +46 -0
- package/dist/src/control-plane/task-source.js +30 -0
- package/dist/src/control-plane/tasks/legacy-task-config-source.js +130 -0
- package/dist/src/control-plane/tasks/plugin-task-source.js +103 -0
- package/dist/src/control-plane/tasks/source-aware-task-config-source.js +611 -0
- package/dist/src/control-plane/tasks/source-lifecycle.js +1093 -0
- package/dist/src/control-plane/tasks/task-record-reader.js +9 -0
- package/dist/src/control-plane/validators/boundary/public-apis.js +107 -0
- package/dist/src/control-plane/validators/integration/_shared.js +51 -0
- package/dist/src/control-plane/validators/integration/adm-audit-http.js +85 -0
- package/dist/src/control-plane/validators/integration/adm-auth-http.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-issuer-http.js +80 -0
- package/dist/src/control-plane/validators/integration/adm-migration.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-scaffold.js +78 -0
- package/dist/src/control-plane/validators/runtime-registration.js +64 -0
- package/dist/src/control-plane/validators/shared.js +683 -0
- package/dist/src/events.js +218 -0
- package/dist/src/execution.js +35 -0
- package/dist/src/index.js +1633 -0
- package/dist/src/layout.js +145 -0
- package/dist/src/local-server.js +202 -0
- package/dist/src/plugins.js +329 -0
- package/dist/src/remote-http.js +83 -0
- package/dist/src/runtime-context.js +216 -0
- package/dist/src/types.js +1 -0
- package/native/darwin-arm64/bin/rig-git +0 -0
- package/native/darwin-arm64/bin/rig-shell +0 -0
- package/native/darwin-arm64/bin/rig-tools +0 -0
- package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
- package/native/darwin-arm64/lib/runtime-native.dylib +0 -0
- package/native/darwin-arm64/manifest.json +1 -0
- package/native/linux-x64/bin/rig-git +0 -0
- package/native/linux-x64/bin/rig-shell +0 -0
- package/native/linux-x64/bin/rig-tools +0 -0
- package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
- package/native/linux-x64/lib/runtime-native.so +0 -0
- package/native/linux-x64/manifest.json +1 -0
- package/package.json +74 -0
- package/skills/rig-task-run.md +71 -0
|
@@ -0,0 +1,2342 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/runtime/src/control-plane/native/repo-ops.ts
|
|
3
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
4
|
+
import { basename as basename6, dirname as dirname9, resolve as resolve16 } from "path";
|
|
5
|
+
|
|
6
|
+
// packages/runtime/src/control-plane/state-sync/types.ts
|
|
7
|
+
var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
|
|
8
|
+
var CANONICAL_TASK_LIFECYCLE_STATUSES = new Set([
|
|
9
|
+
"draft",
|
|
10
|
+
"open",
|
|
11
|
+
"ready",
|
|
12
|
+
"queued",
|
|
13
|
+
"in_progress",
|
|
14
|
+
"under_review",
|
|
15
|
+
"blocked",
|
|
16
|
+
"completed",
|
|
17
|
+
"cancelled"
|
|
18
|
+
]);
|
|
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 readTaskStateMetadataEnvelope(raw) {
|
|
82
|
+
if (!raw || typeof raw !== "object") {
|
|
83
|
+
return { schemaVersion: SUPPORTED_TASK_STATE_SCHEMA_VERSION, supported: true, baseTrackerCommit: null, tasks: {} };
|
|
84
|
+
}
|
|
85
|
+
const envelope = raw;
|
|
86
|
+
const schemaVersion = typeof envelope.schemaVersion === "number" ? envelope.schemaVersion : SUPPORTED_TASK_STATE_SCHEMA_VERSION;
|
|
87
|
+
if (schemaVersion !== SUPPORTED_TASK_STATE_SCHEMA_VERSION) {
|
|
88
|
+
return { schemaVersion, supported: false, baseTrackerCommit: null, tasks: {} };
|
|
89
|
+
}
|
|
90
|
+
const rawTasks = envelope.tasks && typeof envelope.tasks === "object" && !Array.isArray(envelope.tasks) ? envelope.tasks : {};
|
|
91
|
+
const tasks = Object.fromEntries(Object.entries(rawTasks).map(([taskId, metadata]) => [taskId, canonicalizeTaskStateMetadata(metadata)]).filter((entry) => entry[1] != null));
|
|
92
|
+
return {
|
|
93
|
+
schemaVersion,
|
|
94
|
+
supported: true,
|
|
95
|
+
baseTrackerCommit: typeof envelope.baseTrackerCommit === "string" && envelope.baseTrackerCommit.length > 0 ? envelope.baseTrackerCommit : null,
|
|
96
|
+
tasks
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// packages/runtime/src/control-plane/runtime/context.ts
|
|
101
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
102
|
+
import { dirname, resolve } from "path";
|
|
103
|
+
var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
|
|
104
|
+
var runtimeContextStringFields = [
|
|
105
|
+
"runtimeId",
|
|
106
|
+
"taskId",
|
|
107
|
+
"role",
|
|
108
|
+
"workspaceDir",
|
|
109
|
+
"stateDir",
|
|
110
|
+
"logsDir",
|
|
111
|
+
"sessionDir",
|
|
112
|
+
"sessionFile",
|
|
113
|
+
"policyFile",
|
|
114
|
+
"binDir",
|
|
115
|
+
"createdAt"
|
|
116
|
+
];
|
|
117
|
+
var runtimeContextArrayFields = ["scopes", "validation"];
|
|
118
|
+
var runtimeContextOptionalStringFields = [
|
|
119
|
+
"artifactRoot",
|
|
120
|
+
"hostProjectRoot",
|
|
121
|
+
"monorepoMainRoot",
|
|
122
|
+
"monorepoBaseRef",
|
|
123
|
+
"monorepoBaseCommit"
|
|
124
|
+
];
|
|
125
|
+
function loadRuntimeContext(path) {
|
|
126
|
+
const absPath = resolve(path);
|
|
127
|
+
if (!existsSync(absPath)) {
|
|
128
|
+
throw new Error(`RuntimeTaskContext file not found: ${absPath}`);
|
|
129
|
+
}
|
|
130
|
+
let raw;
|
|
131
|
+
try {
|
|
132
|
+
raw = JSON.parse(readFileSync(absPath, "utf8"));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
throw new Error(`Failed to parse RuntimeTaskContext at ${absPath}: ${String(err)}`);
|
|
135
|
+
}
|
|
136
|
+
if (typeof raw !== "object" || raw === null) {
|
|
137
|
+
throw new Error(`RuntimeTaskContext at ${absPath} is not an object`);
|
|
138
|
+
}
|
|
139
|
+
const obj = raw;
|
|
140
|
+
for (const field of runtimeContextStringFields) {
|
|
141
|
+
if (typeof obj[field] !== "string" || obj[field].length === 0) {
|
|
142
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a non-empty string (at ${absPath})`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const field of runtimeContextArrayFields) {
|
|
146
|
+
if (!Array.isArray(obj[field])) {
|
|
147
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be an array (at ${absPath})`);
|
|
148
|
+
}
|
|
149
|
+
if (!obj[field].every((entry) => typeof entry === "string")) {
|
|
150
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a string[] (at ${absPath})`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const field of runtimeContextOptionalStringFields) {
|
|
154
|
+
if (field in obj && obj[field] !== undefined && typeof obj[field] !== "string") {
|
|
155
|
+
throw new Error(`RuntimeTaskContext field "${field}" must be a string when present (at ${absPath})`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (obj.browser !== undefined) {
|
|
159
|
+
if (typeof obj.browser !== "object" || obj.browser === null || Array.isArray(obj.browser)) {
|
|
160
|
+
throw new Error(`RuntimeTaskContext field "browser" must be an object when present (at ${absPath})`);
|
|
161
|
+
}
|
|
162
|
+
const browser = obj.browser;
|
|
163
|
+
for (const field of [
|
|
164
|
+
"preset",
|
|
165
|
+
"mode",
|
|
166
|
+
"stateDir",
|
|
167
|
+
"defaultProfile",
|
|
168
|
+
"effectiveProfile",
|
|
169
|
+
"defaultAttachUrl",
|
|
170
|
+
"effectiveAttachUrl",
|
|
171
|
+
"launchHelper",
|
|
172
|
+
"checkHelper",
|
|
173
|
+
"attachInfoHelper",
|
|
174
|
+
"e2eHelper",
|
|
175
|
+
"resetProfileHelper"
|
|
176
|
+
]) {
|
|
177
|
+
if (typeof browser[field] !== "string" || browser[field].length === 0) {
|
|
178
|
+
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a non-empty string (at ${absPath})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const field of ["devCommand", "launchCommand", "checkCommand", "e2eCommand"]) {
|
|
182
|
+
if (browser[field] !== undefined && typeof browser[field] !== "string") {
|
|
183
|
+
throw new Error(`RuntimeTaskContext field "browser.${field}" must be a string when present (at ${absPath})`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (typeof browser.required !== "boolean") {
|
|
187
|
+
throw new Error(`RuntimeTaskContext field "browser.required" must be a boolean (at ${absPath})`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (obj.memory !== undefined) {
|
|
191
|
+
if (typeof obj.memory !== "object" || obj.memory === null || Array.isArray(obj.memory)) {
|
|
192
|
+
throw new Error(`RuntimeTaskContext field "memory" must be an object when present (at ${absPath})`);
|
|
193
|
+
}
|
|
194
|
+
const memory = obj.memory;
|
|
195
|
+
for (const field of ["canonicalPath", "canonicalRef", "canonicalBaseOid", "hydratedPath"]) {
|
|
196
|
+
if (typeof memory[field] !== "string" || memory[field].length === 0) {
|
|
197
|
+
throw new Error(`RuntimeTaskContext field "memory.${field}" must be a non-empty string (at ${absPath})`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (typeof memory.createdFresh !== "boolean") {
|
|
201
|
+
throw new Error(`RuntimeTaskContext field "memory.createdFresh" must be a boolean (at ${absPath})`);
|
|
202
|
+
}
|
|
203
|
+
if (typeof memory.retrieval !== "object" || memory.retrieval === null || Array.isArray(memory.retrieval)) {
|
|
204
|
+
throw new Error(`RuntimeTaskContext field "memory.retrieval" must be an object (at ${absPath})`);
|
|
205
|
+
}
|
|
206
|
+
const retrieval = memory.retrieval;
|
|
207
|
+
for (const field of ["topK", "lexicalWeight", "vectorWeight", "recencyWeight", "confidenceWeight"]) {
|
|
208
|
+
if (typeof retrieval[field] !== "number" || Number.isNaN(retrieval[field])) {
|
|
209
|
+
throw new Error(`RuntimeTaskContext field "memory.retrieval.${field}" must be a number (at ${absPath})`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (obj.initialDirtyFiles !== undefined) {
|
|
214
|
+
if (typeof obj.initialDirtyFiles !== "object" || obj.initialDirtyFiles === null || Array.isArray(obj.initialDirtyFiles)) {
|
|
215
|
+
throw new Error(`RuntimeTaskContext field "initialDirtyFiles" must be an object when present (at ${absPath})`);
|
|
216
|
+
}
|
|
217
|
+
const dirtyFiles = obj.initialDirtyFiles;
|
|
218
|
+
for (const key of ["project", "monorepo"]) {
|
|
219
|
+
if (dirtyFiles[key] === undefined) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!Array.isArray(dirtyFiles[key]) || !dirtyFiles[key].every((entry) => typeof entry === "string")) {
|
|
223
|
+
throw new Error(`RuntimeTaskContext field "initialDirtyFiles.${key}" must be a string[] when present (at ${absPath})`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (obj.initialHeadCommits !== undefined) {
|
|
228
|
+
if (typeof obj.initialHeadCommits !== "object" || obj.initialHeadCommits === null || Array.isArray(obj.initialHeadCommits)) {
|
|
229
|
+
throw new Error(`RuntimeTaskContext field "initialHeadCommits" must be an object when present (at ${absPath})`);
|
|
230
|
+
}
|
|
231
|
+
const headCommits = obj.initialHeadCommits;
|
|
232
|
+
for (const key of ["project", "monorepo"]) {
|
|
233
|
+
if (headCommits[key] === undefined) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (typeof headCommits[key] !== "string") {
|
|
237
|
+
throw new Error(`RuntimeTaskContext field "initialHeadCommits.${key}" must be a string when present (at ${absPath})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return obj;
|
|
242
|
+
}
|
|
243
|
+
function loadRuntimeContextFromEnv(env = process.env) {
|
|
244
|
+
const contextFile = env[RUNTIME_CONTEXT_ENV];
|
|
245
|
+
if (contextFile) {
|
|
246
|
+
return loadRuntimeContext(contextFile);
|
|
247
|
+
}
|
|
248
|
+
const inferred = findRuntimeContextFile(process.cwd());
|
|
249
|
+
if (!inferred) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return loadRuntimeContext(inferred);
|
|
253
|
+
}
|
|
254
|
+
function findRuntimeContextFile(startPath) {
|
|
255
|
+
let current = resolve(startPath);
|
|
256
|
+
while (true) {
|
|
257
|
+
const candidate = resolve(current, "runtime-context.json");
|
|
258
|
+
if (existsSync(candidate) && isAgentRuntimeContextPath(candidate)) {
|
|
259
|
+
return candidate;
|
|
260
|
+
}
|
|
261
|
+
const parent = dirname(current);
|
|
262
|
+
if (parent === current) {
|
|
263
|
+
return "";
|
|
264
|
+
}
|
|
265
|
+
current = parent;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function isAgentRuntimeContextPath(path) {
|
|
269
|
+
const normalized = path.replace(/\\/g, "/");
|
|
270
|
+
return /\/\.rig\/runtime-context\.json$/.test(normalized);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// packages/runtime/src/control-plane/repos/registry.ts
|
|
274
|
+
var MANAGED_REPOS = new Map;
|
|
275
|
+
function getManagedRepoEntry(repoId) {
|
|
276
|
+
const entry = MANAGED_REPOS.get(repoId);
|
|
277
|
+
if (!entry) {
|
|
278
|
+
throw new Error(`managed repo not registered: ${repoId}. Plugins contribute repos via RigPlugin.contributes.repoSources; ` + `make sure a plugin declares this id and the plugin host has been initialized.`);
|
|
279
|
+
}
|
|
280
|
+
return entry;
|
|
281
|
+
}
|
|
282
|
+
function listManagedRepoEntries() {
|
|
283
|
+
return Array.from(MANAGED_REPOS.values());
|
|
284
|
+
}
|
|
285
|
+
function resolveManagedRepoIdByAlias(alias) {
|
|
286
|
+
for (const entry of MANAGED_REPOS.values()) {
|
|
287
|
+
if (entry.alias === alias) {
|
|
288
|
+
return entry.id;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
// packages/runtime/src/control-plane/repos/layout.ts
|
|
294
|
+
import { existsSync as existsSync3 } from "fs";
|
|
295
|
+
import { basename as basename2, dirname as dirname3, join, resolve as resolve3 } from "path";
|
|
296
|
+
|
|
297
|
+
// packages/runtime/src/layout.ts
|
|
298
|
+
import { existsSync as existsSync2 } from "fs";
|
|
299
|
+
import { basename, dirname as dirname2, resolve as resolve2 } from "path";
|
|
300
|
+
var RIG_DEFINITION_DIRNAME = "rig";
|
|
301
|
+
var RIG_ARTIFACTS_DIRNAME = "artifacts";
|
|
302
|
+
function resolveMonorepoRoot(projectRoot) {
|
|
303
|
+
const normalizedProjectRoot = resolve2(projectRoot);
|
|
304
|
+
const explicit = process.env.MONOREPO_ROOT?.trim();
|
|
305
|
+
if (explicit) {
|
|
306
|
+
const explicitRoot = resolve2(explicit);
|
|
307
|
+
const explicitParent = dirname2(explicitRoot);
|
|
308
|
+
if (basename(explicitParent) === ".worktrees") {
|
|
309
|
+
const owner = dirname2(explicitParent);
|
|
310
|
+
const ownerHasGit = existsSync2(resolve2(owner, ".git"));
|
|
311
|
+
const ownerHasTaskConfig = existsSync2(resolve2(owner, ".rig", "task-config.json"));
|
|
312
|
+
const ownerHasRigConfig = existsSync2(resolve2(owner, "rig.config.ts"));
|
|
313
|
+
if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
|
|
314
|
+
return owner;
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
|
|
317
|
+
}
|
|
318
|
+
if (!existsSync2(resolve2(explicitRoot, ".git"))) {
|
|
319
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
|
|
320
|
+
}
|
|
321
|
+
const hasTaskConfig = existsSync2(resolve2(explicitRoot, ".rig", "task-config.json"));
|
|
322
|
+
const hasRigConfig = existsSync2(resolve2(explicitRoot, "rig.config.ts"));
|
|
323
|
+
if (!hasTaskConfig && !hasRigConfig) {
|
|
324
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
|
|
325
|
+
}
|
|
326
|
+
return explicitRoot;
|
|
327
|
+
}
|
|
328
|
+
const projectParent = dirname2(normalizedProjectRoot);
|
|
329
|
+
if (basename(projectParent) === ".worktrees") {
|
|
330
|
+
const worktreeOwner = dirname2(projectParent);
|
|
331
|
+
const ownerHasGit = existsSync2(resolve2(worktreeOwner, ".git"));
|
|
332
|
+
const ownerHasTaskConfig = existsSync2(resolve2(worktreeOwner, ".rig", "task-config.json"));
|
|
333
|
+
const ownerHasRigConfig = existsSync2(resolve2(worktreeOwner, "rig.config.ts"));
|
|
334
|
+
if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
|
|
335
|
+
return worktreeOwner;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return normalizedProjectRoot;
|
|
339
|
+
}
|
|
340
|
+
function resolveRuntimeWorkspaceLayout(workspaceDir) {
|
|
341
|
+
const root = resolve2(workspaceDir);
|
|
342
|
+
const rigRoot = resolve2(root, ".rig");
|
|
343
|
+
const logsDir = resolve2(rigRoot, "logs");
|
|
344
|
+
const stateDir = resolve2(rigRoot, "state");
|
|
345
|
+
const runtimeDir = resolve2(rigRoot, "runtime");
|
|
346
|
+
const binDir = resolve2(rigRoot, "bin");
|
|
347
|
+
return {
|
|
348
|
+
workspaceDir: root,
|
|
349
|
+
rigRoot,
|
|
350
|
+
stateDir,
|
|
351
|
+
logsDir,
|
|
352
|
+
artifactsRoot: resolve2(root, RIG_ARTIFACTS_DIRNAME),
|
|
353
|
+
runtimeDir,
|
|
354
|
+
homeDir: resolve2(rigRoot, "home"),
|
|
355
|
+
tmpDir: resolve2(rigRoot, "tmp"),
|
|
356
|
+
cacheDir: resolve2(rigRoot, "cache"),
|
|
357
|
+
sessionDir: resolve2(rigRoot, "session"),
|
|
358
|
+
binDir,
|
|
359
|
+
distDir: resolve2(rigRoot, "dist"),
|
|
360
|
+
pluginBinDir: resolve2(binDir, "plugins"),
|
|
361
|
+
contextPath: resolve2(rigRoot, "runtime-context.json"),
|
|
362
|
+
controlPlaneEventsFile: resolve2(logsDir, "control-plane.events.jsonl")
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function resolveActiveRuntimeWorkspaceRoot(monorepoRoot) {
|
|
366
|
+
const explicit = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
367
|
+
if (!explicit) {
|
|
368
|
+
throw new Error("No active runtime workspace. Set RIG_TASK_WORKSPACE or provision a task runtime first.");
|
|
369
|
+
}
|
|
370
|
+
return resolve2(explicit);
|
|
371
|
+
}
|
|
372
|
+
function resolveRigLayout(projectRoot) {
|
|
373
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
374
|
+
const definitionRoot = resolve2(projectRoot, RIG_DEFINITION_DIRNAME);
|
|
375
|
+
const runtimeWorkspaceRoot = resolveActiveRuntimeWorkspaceRoot(monorepoRoot);
|
|
376
|
+
const runtimeLayout = resolveRuntimeWorkspaceLayout(runtimeWorkspaceRoot);
|
|
377
|
+
const policyDir = resolve2(definitionRoot, "policy");
|
|
378
|
+
return {
|
|
379
|
+
projectRoot,
|
|
380
|
+
monorepoRoot,
|
|
381
|
+
definitionRoot,
|
|
382
|
+
runtimeWorkspaceRoot,
|
|
383
|
+
stateRoot: runtimeLayout.rigRoot,
|
|
384
|
+
artifactsRoot: runtimeLayout.artifactsRoot,
|
|
385
|
+
configPath: resolve2(definitionRoot, "config.sh"),
|
|
386
|
+
taskConfigPath: resolve2(runtimeWorkspaceRoot, ".rig", "task-config.json"),
|
|
387
|
+
policyDir,
|
|
388
|
+
policyFile: resolve2(policyDir, "policy.json"),
|
|
389
|
+
pluginsDir: resolve2(definitionRoot, "plugins"),
|
|
390
|
+
hooksDir: resolve2(definitionRoot, "hooks"),
|
|
391
|
+
toolsDir: resolve2(definitionRoot, "tools"),
|
|
392
|
+
templatesDir: resolve2(definitionRoot, "templates"),
|
|
393
|
+
validationDir: resolve2(definitionRoot, "validation"),
|
|
394
|
+
stateDir: runtimeLayout.stateDir,
|
|
395
|
+
logsDir: runtimeLayout.logsDir,
|
|
396
|
+
notificationsDir: resolve2(definitionRoot, "notifications"),
|
|
397
|
+
runtimeDir: runtimeLayout.runtimeDir,
|
|
398
|
+
distDir: runtimeLayout.distDir,
|
|
399
|
+
binDir: runtimeLayout.binDir,
|
|
400
|
+
pluginBinDir: runtimeLayout.pluginBinDir,
|
|
401
|
+
keybindingsPath: resolve2(definitionRoot, "keybindings.json"),
|
|
402
|
+
controlPlaneEventsFile: runtimeLayout.controlPlaneEventsFile
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// packages/runtime/src/control-plane/repos/layout.ts
|
|
407
|
+
function resolveRepoStateDir(projectRoot) {
|
|
408
|
+
const normalizedProjectRoot = resolve3(projectRoot);
|
|
409
|
+
const projectParent = dirname3(normalizedProjectRoot);
|
|
410
|
+
if (basename2(projectParent) === ".worktrees") {
|
|
411
|
+
const ownerRoot = dirname3(projectParent);
|
|
412
|
+
const ownerHasRepoMarkers = existsSync3(resolve3(ownerRoot, ".git")) || existsSync3(resolve3(ownerRoot, ".rig", "state"));
|
|
413
|
+
if (ownerHasRepoMarkers) {
|
|
414
|
+
return resolve3(ownerRoot, ".rig", "state");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return resolve3(projectRoot, ".rig", "state");
|
|
418
|
+
}
|
|
419
|
+
function resolveManagedRepoLayout(projectRoot, repoId) {
|
|
420
|
+
const normalizedProjectRoot = resolve3(projectRoot);
|
|
421
|
+
const entry = getManagedRepoEntry(repoId);
|
|
422
|
+
const stateDir = resolveRepoStateDir(normalizedProjectRoot);
|
|
423
|
+
const metadataRelativePath = join("repos", entry.id);
|
|
424
|
+
const metadataRoot = resolve3(stateDir, metadataRelativePath);
|
|
425
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
426
|
+
const runsInsideTaskWorktree = runtimeWorkspace && resolve3(runtimeWorkspace) === normalizedProjectRoot || basename2(dirname3(normalizedProjectRoot)) === ".worktrees";
|
|
427
|
+
const isPrimaryManagedRepo = listManagedRepoEntries()[0]?.id === repoId;
|
|
428
|
+
const checkoutRoot = isPrimaryManagedRepo && runsInsideTaskWorktree ? resolveMonorepoRoot(normalizedProjectRoot) : entry.checkoutEnvVar && process.env[entry.checkoutEnvVar]?.trim() ? resolve3(process.env[entry.checkoutEnvVar].trim()) : resolve3(normalizedProjectRoot, entry.alias);
|
|
429
|
+
return {
|
|
430
|
+
projectRoot: normalizedProjectRoot,
|
|
431
|
+
repoId: entry.id,
|
|
432
|
+
alias: entry.alias,
|
|
433
|
+
defaultBranch: entry.defaultBranch,
|
|
434
|
+
remoteUrl: entry.remoteEnvVar && process.env[entry.remoteEnvVar]?.trim() ? process.env[entry.remoteEnvVar].trim() : entry.defaultRemoteUrl,
|
|
435
|
+
checkoutRoot,
|
|
436
|
+
worktreesRoot: resolve3(checkoutRoot, ".worktrees"),
|
|
437
|
+
stateDir,
|
|
438
|
+
metadataRoot,
|
|
439
|
+
metadataRelativePath,
|
|
440
|
+
mirrorRoot: resolve3(metadataRoot, "mirror.git"),
|
|
441
|
+
mirrorStatePath: resolve3(metadataRoot, "mirror-state.json"),
|
|
442
|
+
mirrorStateRelativePath: join(metadataRelativePath, "mirror-state.json")
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function resolveManagedRepoLayoutByAlias(projectRoot, alias) {
|
|
446
|
+
const repoId = resolveManagedRepoIdByAlias(alias);
|
|
447
|
+
return repoId ? resolveManagedRepoLayout(projectRoot, repoId) : null;
|
|
448
|
+
}
|
|
449
|
+
function resolveMonorepoRepoLayout(projectRoot) {
|
|
450
|
+
const entries = listManagedRepoEntries();
|
|
451
|
+
if (entries.length === 0) {
|
|
452
|
+
throw new Error("resolveMonorepoRepoLayout: no managed repos registered. Either contribute one via " + "RigPlugin.contributes.repoSources (with defaultBranch set), or avoid calling this " + "function for projects where the project root IS the monorepo.");
|
|
453
|
+
}
|
|
454
|
+
const primary = entries[0];
|
|
455
|
+
return resolveManagedRepoLayout(projectRoot, primary.id);
|
|
456
|
+
}
|
|
457
|
+
// packages/runtime/src/control-plane/repos/mirror/bootstrap.ts
|
|
458
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, realpathSync } from "fs";
|
|
459
|
+
import { resolve as resolve5 } from "path";
|
|
460
|
+
|
|
461
|
+
// packages/runtime/src/control-plane/authority-files.ts
|
|
462
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync, copyFileSync, statSync, readdirSync, chmodSync } from "fs";
|
|
463
|
+
import { dirname as dirname4, join as join2, relative, resolve as resolve4 } from "path";
|
|
464
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
465
|
+
function resolveAuthorityProjectStateDir(projectRoot) {
|
|
466
|
+
const explicit = process.env.RIG_STATE_DIR?.trim();
|
|
467
|
+
if (explicit) {
|
|
468
|
+
return resolve4(explicit);
|
|
469
|
+
}
|
|
470
|
+
return resolve4(resolve4(projectRoot), ".rig", "state");
|
|
471
|
+
}
|
|
472
|
+
function readJsonAtPath(path, fallback) {
|
|
473
|
+
if (!existsSync4(path)) {
|
|
474
|
+
return fallback;
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
478
|
+
} catch {
|
|
479
|
+
return fallback;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function writeJsonAtPath(path, value) {
|
|
483
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
484
|
+
writeFileSync2(path, `${JSON.stringify(value, null, 2)}
|
|
485
|
+
`, "utf8");
|
|
486
|
+
return path;
|
|
487
|
+
}
|
|
488
|
+
function readAuthorityProjectStateJson(projectRoot, relativePath, fallback) {
|
|
489
|
+
return readJsonAtPath(resolve4(resolveAuthorityProjectStateDir(projectRoot), relativePath), fallback);
|
|
490
|
+
}
|
|
491
|
+
function writeAuthorityProjectStateJson(projectRoot, relativePath, value) {
|
|
492
|
+
return writeJsonAtPath(resolve4(resolveAuthorityProjectStateDir(projectRoot), relativePath), value);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// packages/runtime/src/control-plane/repos/mirror/state.ts
|
|
496
|
+
var STATE_VERSION = 1;
|
|
497
|
+
function defaultMirrorState(projectRoot, repoId) {
|
|
498
|
+
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|
|
499
|
+
return {
|
|
500
|
+
version: STATE_VERSION,
|
|
501
|
+
repoId,
|
|
502
|
+
remoteUrl: layout.remoteUrl,
|
|
503
|
+
defaultBranch: layout.defaultBranch
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function readManagedRepoMirrorState(projectRoot, repoId) {
|
|
507
|
+
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|
|
508
|
+
return readAuthorityProjectStateJson(projectRoot, layout.mirrorStateRelativePath, null);
|
|
509
|
+
}
|
|
510
|
+
function writeManagedRepoMirrorState(projectRoot, repoId, patch) {
|
|
511
|
+
const current = readManagedRepoMirrorState(projectRoot, repoId) || defaultMirrorState(projectRoot, repoId);
|
|
512
|
+
const next = {
|
|
513
|
+
...current,
|
|
514
|
+
...patch,
|
|
515
|
+
version: STATE_VERSION,
|
|
516
|
+
repoId
|
|
517
|
+
};
|
|
518
|
+
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|
|
519
|
+
writeAuthorityProjectStateJson(projectRoot, layout.mirrorStateRelativePath, next);
|
|
520
|
+
return next;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// packages/runtime/src/control-plane/repos/mirror/bootstrap.ts
|
|
524
|
+
function nowIso() {
|
|
525
|
+
return new Date().toISOString();
|
|
526
|
+
}
|
|
527
|
+
function runGit(command, cwd) {
|
|
528
|
+
const result = Bun.spawnSync(command, {
|
|
529
|
+
cwd,
|
|
530
|
+
stdout: "pipe",
|
|
531
|
+
stderr: "pipe",
|
|
532
|
+
env: process.env
|
|
533
|
+
});
|
|
534
|
+
return {
|
|
535
|
+
exitCode: result.exitCode,
|
|
536
|
+
stdout: result.stdout.toString(),
|
|
537
|
+
stderr: result.stderr.toString()
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function ensureGitSuccess(result, command) {
|
|
541
|
+
if (result.exitCode !== 0) {
|
|
542
|
+
throw new Error(result.stderr || result.stdout || `git command failed: ${command.join(" ")}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function isUsableRemoteUrl(candidate, layout) {
|
|
546
|
+
return candidate.length > 0 && candidate !== layout.mirrorRoot && candidate !== layout.checkoutRoot;
|
|
547
|
+
}
|
|
548
|
+
function sameExistingPath(left, right) {
|
|
549
|
+
try {
|
|
550
|
+
return realpathSync(left) === realpathSync(right);
|
|
551
|
+
} catch {
|
|
552
|
+
return resolve5(left) === resolve5(right);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function repoLooksUsable(repoRoot, projectRoot) {
|
|
556
|
+
const probe = runGit(["git", "-C", repoRoot, "rev-parse", "--show-toplevel"], projectRoot);
|
|
557
|
+
return probe.exitCode === 0 && sameExistingPath(probe.stdout.trim(), repoRoot);
|
|
558
|
+
}
|
|
559
|
+
function checkoutLooksUsable(layout) {
|
|
560
|
+
return repoLooksUsable(layout.checkoutRoot, layout.projectRoot);
|
|
561
|
+
}
|
|
562
|
+
function resolveMirrorRemoteUrl(layout) {
|
|
563
|
+
const entry = getManagedRepoEntry(layout.repoId);
|
|
564
|
+
const explicit = entry.remoteEnvVar ? process.env[entry.remoteEnvVar]?.trim() : "";
|
|
565
|
+
if (explicit) {
|
|
566
|
+
return explicit;
|
|
567
|
+
}
|
|
568
|
+
const persisted = readManagedRepoMirrorState(layout.projectRoot, layout.repoId)?.remoteUrl?.trim();
|
|
569
|
+
if (persisted && isUsableRemoteUrl(persisted, layout)) {
|
|
570
|
+
return persisted;
|
|
571
|
+
}
|
|
572
|
+
const mirrorOrigin = runGit(["git", "--git-dir", layout.mirrorRoot, "remote", "get-url", "origin"], layout.projectRoot);
|
|
573
|
+
if (mirrorOrigin.exitCode === 0) {
|
|
574
|
+
const currentOrigin = mirrorOrigin.stdout.trim();
|
|
575
|
+
if (isUsableRemoteUrl(currentOrigin, layout)) {
|
|
576
|
+
return currentOrigin;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (repoLooksUsable(layout.projectRoot, layout.projectRoot)) {
|
|
580
|
+
const projectOrigin = runGit(["git", "-C", layout.projectRoot, "remote", "get-url", "origin"], layout.projectRoot);
|
|
581
|
+
if (projectOrigin.exitCode === 0) {
|
|
582
|
+
const currentOrigin = projectOrigin.stdout.trim();
|
|
583
|
+
if (isUsableRemoteUrl(currentOrigin, layout)) {
|
|
584
|
+
return currentOrigin;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (existsSync5(resolve5(layout.checkoutRoot, ".git")) && checkoutLooksUsable(layout)) {
|
|
589
|
+
const checkoutOrigin = runGit(["git", "-C", layout.checkoutRoot, "remote", "get-url", "origin"], layout.projectRoot);
|
|
590
|
+
if (checkoutOrigin.exitCode === 0) {
|
|
591
|
+
const currentOrigin = checkoutOrigin.stdout.trim();
|
|
592
|
+
if (isUsableRemoteUrl(currentOrigin, layout)) {
|
|
593
|
+
return currentOrigin;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return layout.remoteUrl;
|
|
598
|
+
}
|
|
599
|
+
function ensureManagedRepoMirror(projectRoot, repoId) {
|
|
600
|
+
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|
|
601
|
+
mkdirSync3(layout.metadataRoot, { recursive: true });
|
|
602
|
+
const remoteUrl = resolveMirrorRemoteUrl(layout);
|
|
603
|
+
if (!existsSync5(resolve5(layout.mirrorRoot, "HEAD"))) {
|
|
604
|
+
ensureGitSuccess(runGit(["git", "init", "--bare", layout.mirrorRoot], layout.projectRoot), ["git", "init", "--bare", layout.mirrorRoot]);
|
|
605
|
+
}
|
|
606
|
+
const getOrigin = runGit(["git", "--git-dir", layout.mirrorRoot, "remote", "get-url", "origin"], layout.projectRoot);
|
|
607
|
+
if (getOrigin.exitCode === 0) {
|
|
608
|
+
const currentOrigin = getOrigin.stdout.trim();
|
|
609
|
+
if (currentOrigin !== remoteUrl) {
|
|
610
|
+
ensureGitSuccess(runGit(["git", "--git-dir", layout.mirrorRoot, "remote", "set-url", "origin", remoteUrl], layout.projectRoot), ["git", "--git-dir", layout.mirrorRoot, "remote", "set-url", "origin", remoteUrl]);
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
ensureGitSuccess(runGit(["git", "--git-dir", layout.mirrorRoot, "remote", "add", "origin", remoteUrl], layout.projectRoot), ["git", "--git-dir", layout.mirrorRoot, "remote", "add", "origin", remoteUrl]);
|
|
614
|
+
}
|
|
615
|
+
writeManagedRepoMirrorState(projectRoot, repoId, {
|
|
616
|
+
remoteUrl,
|
|
617
|
+
defaultBranch: layout.defaultBranch,
|
|
618
|
+
initializedAt: nowIso()
|
|
619
|
+
});
|
|
620
|
+
return layout;
|
|
621
|
+
}
|
|
622
|
+
// packages/runtime/src/control-plane/repos/mirror/refresh.ts
|
|
623
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, realpathSync as realpathSync2, rmSync } from "fs";
|
|
624
|
+
import { resolve as resolve6 } from "path";
|
|
625
|
+
function nowIso2() {
|
|
626
|
+
return new Date().toISOString();
|
|
627
|
+
}
|
|
628
|
+
function runGit2(command, cwd) {
|
|
629
|
+
const result = Bun.spawnSync(command, {
|
|
630
|
+
cwd,
|
|
631
|
+
stdout: "pipe",
|
|
632
|
+
stderr: "pipe",
|
|
633
|
+
env: process.env
|
|
634
|
+
});
|
|
635
|
+
return {
|
|
636
|
+
exitCode: result.exitCode,
|
|
637
|
+
stdout: result.stdout.toString(),
|
|
638
|
+
stderr: result.stderr.toString()
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function ensureGitSuccess2(result, command) {
|
|
642
|
+
if (result.exitCode !== 0) {
|
|
643
|
+
throw new Error(result.stderr || result.stdout || `git command failed: ${command.join(" ")}`);
|
|
644
|
+
}
|
|
645
|
+
return result.stdout.trim();
|
|
646
|
+
}
|
|
647
|
+
function sameExistingPath2(left, right) {
|
|
648
|
+
try {
|
|
649
|
+
return realpathSync2(left) === realpathSync2(right);
|
|
650
|
+
} catch {
|
|
651
|
+
return resolve6(left) === resolve6(right);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function ensureMirrorHead(layout) {
|
|
655
|
+
ensureGitSuccess2(runGit2([
|
|
656
|
+
"git",
|
|
657
|
+
"--git-dir",
|
|
658
|
+
layout.mirrorRoot,
|
|
659
|
+
"fetch",
|
|
660
|
+
"--prune",
|
|
661
|
+
"origin",
|
|
662
|
+
"+refs/heads/*:refs/heads/*",
|
|
663
|
+
"+refs/tags/*:refs/tags/*"
|
|
664
|
+
], layout.projectRoot), [
|
|
665
|
+
"git",
|
|
666
|
+
"--git-dir",
|
|
667
|
+
layout.mirrorRoot,
|
|
668
|
+
"fetch",
|
|
669
|
+
"--prune",
|
|
670
|
+
"origin",
|
|
671
|
+
"+refs/heads/*:refs/heads/*",
|
|
672
|
+
"+refs/tags/*:refs/tags/*"
|
|
673
|
+
]);
|
|
674
|
+
const headRef = `refs/heads/${layout.defaultBranch}`;
|
|
675
|
+
const headCommit = ensureGitSuccess2(runGit2(["git", "--git-dir", layout.mirrorRoot, "rev-parse", headRef], layout.projectRoot), ["git", "--git-dir", layout.mirrorRoot, "rev-parse", headRef]);
|
|
676
|
+
const remoteUrl = ensureGitSuccess2(runGit2(["git", "--git-dir", layout.mirrorRoot, "remote", "get-url", "origin"], layout.projectRoot), ["git", "--git-dir", layout.mirrorRoot, "remote", "get-url", "origin"]);
|
|
677
|
+
ensureGitSuccess2(runGit2(["git", "--git-dir", layout.mirrorRoot, "symbolic-ref", "HEAD", headRef], layout.projectRoot), ["git", "--git-dir", layout.mirrorRoot, "symbolic-ref", "HEAD", headRef]);
|
|
678
|
+
writeManagedRepoMirrorState(layout.projectRoot, layout.repoId, {
|
|
679
|
+
remoteUrl,
|
|
680
|
+
defaultBranch: layout.defaultBranch,
|
|
681
|
+
lastSyncedAt: nowIso2(),
|
|
682
|
+
headRef,
|
|
683
|
+
headCommit
|
|
684
|
+
});
|
|
685
|
+
return headCommit;
|
|
686
|
+
}
|
|
687
|
+
function refreshManagedRepoMirror(projectRoot, repoId) {
|
|
688
|
+
const layout = ensureManagedRepoMirror(projectRoot, repoId);
|
|
689
|
+
const headCommit = ensureMirrorHead(layout);
|
|
690
|
+
return { layout, headCommit };
|
|
691
|
+
}
|
|
692
|
+
function checkoutLooksUsable2(layout) {
|
|
693
|
+
const probe = runGit2(["git", "-C", layout.checkoutRoot, "rev-parse", "--show-toplevel"], layout.projectRoot);
|
|
694
|
+
return probe.exitCode === 0 && sameExistingPath2(probe.stdout.trim(), layout.checkoutRoot);
|
|
695
|
+
}
|
|
696
|
+
function ensureCheckoutFromMirror(layout) {
|
|
697
|
+
mkdirSync4(resolve6(layout.checkoutRoot, ".."), { recursive: true });
|
|
698
|
+
const gitPath = resolve6(layout.checkoutRoot, ".git");
|
|
699
|
+
if (existsSync6(layout.checkoutRoot) && (!existsSync6(gitPath) || !checkoutLooksUsable2(layout))) {
|
|
700
|
+
rmSync(layout.checkoutRoot, { recursive: true, force: true });
|
|
701
|
+
}
|
|
702
|
+
if (!existsSync6(gitPath)) {
|
|
703
|
+
ensureGitSuccess2(runGit2(["git", "clone", layout.mirrorRoot, layout.checkoutRoot], layout.projectRoot), ["git", "clone", layout.mirrorRoot, layout.checkoutRoot]);
|
|
704
|
+
}
|
|
705
|
+
const getOrigin = runGit2(["git", "-C", layout.checkoutRoot, "remote", "get-url", "origin"], layout.projectRoot);
|
|
706
|
+
if (getOrigin.exitCode === 0) {
|
|
707
|
+
const currentOrigin = getOrigin.stdout.trim();
|
|
708
|
+
if (currentOrigin !== layout.mirrorRoot) {
|
|
709
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "remote", "set-url", "origin", layout.mirrorRoot], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "remote", "set-url", "origin", layout.mirrorRoot]);
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "remote", "add", "origin", layout.mirrorRoot], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "remote", "add", "origin", layout.mirrorRoot]);
|
|
713
|
+
}
|
|
714
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "fetch", "origin", layout.defaultBranch], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "fetch", "origin", layout.defaultBranch]);
|
|
715
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "reset", "--hard"], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "reset", "--hard"]);
|
|
716
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "clean", "-fd"], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "clean", "-fd"]);
|
|
717
|
+
ensureGitSuccess2(runGit2([
|
|
718
|
+
"git",
|
|
719
|
+
"-C",
|
|
720
|
+
layout.checkoutRoot,
|
|
721
|
+
"checkout",
|
|
722
|
+
"--force",
|
|
723
|
+
"-B",
|
|
724
|
+
layout.defaultBranch,
|
|
725
|
+
`origin/${layout.defaultBranch}`
|
|
726
|
+
], layout.projectRoot), [
|
|
727
|
+
"git",
|
|
728
|
+
"-C",
|
|
729
|
+
layout.checkoutRoot,
|
|
730
|
+
"checkout",
|
|
731
|
+
"--force",
|
|
732
|
+
"-B",
|
|
733
|
+
layout.defaultBranch,
|
|
734
|
+
`origin/${layout.defaultBranch}`
|
|
735
|
+
]);
|
|
736
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "reset", "--hard", `origin/${layout.defaultBranch}`], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "reset", "--hard", `origin/${layout.defaultBranch}`]);
|
|
737
|
+
ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "clean", "-fd"], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "clean", "-fd"]);
|
|
738
|
+
return ensureGitSuccess2(runGit2(["git", "-C", layout.checkoutRoot, "rev-parse", "HEAD"], layout.projectRoot), ["git", "-C", layout.checkoutRoot, "rev-parse", "HEAD"]);
|
|
739
|
+
}
|
|
740
|
+
function syncManagedRepo(projectRoot, repoId) {
|
|
741
|
+
const { layout, headCommit } = refreshManagedRepoMirror(projectRoot, repoId);
|
|
742
|
+
const checkoutCommit = ensureCheckoutFromMirror(layout);
|
|
743
|
+
if (checkoutCommit !== headCommit) {
|
|
744
|
+
throw new Error(`Managed repo checkout ${layout.alias} is out of sync with mirror: expected ${headCommit}, got ${checkoutCommit}.`);
|
|
745
|
+
}
|
|
746
|
+
return { layout, headCommit };
|
|
747
|
+
}
|
|
748
|
+
// packages/runtime/src/control-plane/native/task-ops.ts
|
|
749
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
750
|
+
import { resolve as resolve15 } from "path";
|
|
751
|
+
|
|
752
|
+
// packages/runtime/src/build-time-config.ts
|
|
753
|
+
function normalizeBuildConfig(value) {
|
|
754
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
755
|
+
return {};
|
|
756
|
+
}
|
|
757
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
758
|
+
}
|
|
759
|
+
function readBuildConfig() {
|
|
760
|
+
if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
|
|
761
|
+
return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
|
|
762
|
+
}
|
|
763
|
+
const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
|
|
764
|
+
if (!raw) {
|
|
765
|
+
return {};
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
return normalizeBuildConfig(JSON.parse(raw));
|
|
769
|
+
} catch {
|
|
770
|
+
return {};
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// packages/runtime/src/control-plane/runtime/tooling/shell.ts
|
|
775
|
+
import { tmpdir } from "os";
|
|
776
|
+
import { basename as basename3, dirname as dirname5, resolve as resolve7 } from "path";
|
|
777
|
+
var sharedNativeShellOutputDir = resolve7(tmpdir(), "rig-native");
|
|
778
|
+
var sharedNativeShellOutputPath = resolve7(sharedNativeShellOutputDir, `rig-shell-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
779
|
+
// packages/runtime/src/control-plane/runtime/tooling/file-tools.ts
|
|
780
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
781
|
+
import { basename as basename4, dirname as dirname6, resolve as resolve8 } from "path";
|
|
782
|
+
var sharedNativeToolsOutputDir = resolve8(tmpdir2(), "rig-native");
|
|
783
|
+
var sharedNativeToolsOutputPath = resolve8(sharedNativeToolsOutputDir, `rig-tools-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
784
|
+
// packages/runtime/src/control-plane/plugin-host-context.ts
|
|
785
|
+
import { createPluginHost } from "@rig/core";
|
|
786
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
787
|
+
|
|
788
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
789
|
+
var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
790
|
+
|
|
791
|
+
// packages/runtime/src/control-plane/native/task-state.ts
|
|
792
|
+
import { existsSync as existsSync12, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
793
|
+
import { basename as basename5, resolve as resolve14 } from "path";
|
|
794
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
795
|
+
import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
|
|
796
|
+
import { resolve as resolve13 } from "path";
|
|
797
|
+
|
|
798
|
+
// packages/runtime/src/control-plane/native/git-native.ts
|
|
799
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
800
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
801
|
+
import { dirname as dirname7, isAbsolute, resolve as resolve9 } from "path";
|
|
802
|
+
import { createHash } from "crypto";
|
|
803
|
+
var sharedGitNativeOutputDir = resolve9(tmpdir3(), "rig-native");
|
|
804
|
+
var sharedGitNativeOutputPath = resolve9(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
|
|
805
|
+
var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
|
|
806
|
+
function temporaryGitBinaryOutputPath(outputPath) {
|
|
807
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
808
|
+
return resolve9(dirname7(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
|
|
809
|
+
}
|
|
810
|
+
function publishGitBinary(tempOutputPath, outputPath) {
|
|
811
|
+
try {
|
|
812
|
+
renameSync(tempOutputPath, outputPath);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (process.platform === "win32" && existsSync7(outputPath)) {
|
|
815
|
+
rmSync2(outputPath, { force: true });
|
|
816
|
+
renameSync(tempOutputPath, outputPath);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
throw error;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function runtimeRigGitFileName() {
|
|
823
|
+
return `rig-git${process.platform === "win32" ? ".exe" : ""}`;
|
|
824
|
+
}
|
|
825
|
+
function rigGitSourceCandidates() {
|
|
826
|
+
const execDir = process.execPath?.trim() ? dirname7(process.execPath.trim()) : "";
|
|
827
|
+
const cwd = process.cwd()?.trim() || "";
|
|
828
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
|
|
829
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
|
|
830
|
+
const moduleRelativeSource = resolve9(import.meta.dir, "../../../native/rig-git.zig");
|
|
831
|
+
return [...new Set([
|
|
832
|
+
process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
|
|
833
|
+
moduleRelativeSource,
|
|
834
|
+
projectRoot ? resolve9(projectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
835
|
+
hostProjectRoot ? resolve9(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
|
|
836
|
+
cwd ? resolve9(cwd, "packages/runtime/native/rig-git.zig") : "",
|
|
837
|
+
execDir ? resolve9(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
|
|
838
|
+
execDir ? resolve9(execDir, "..", "native", "rig-git.zig") : ""
|
|
839
|
+
].filter(Boolean))];
|
|
840
|
+
}
|
|
841
|
+
function nativePackageBinaryCandidates(fromDir, fileName) {
|
|
842
|
+
const candidates = [];
|
|
843
|
+
let cursor = resolve9(fromDir);
|
|
844
|
+
for (let index = 0;index < 8; index += 1) {
|
|
845
|
+
candidates.push(resolve9(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve9(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve9(cursor, "native", fileName), resolve9(cursor, "native", "bin", fileName));
|
|
846
|
+
const parent = dirname7(cursor);
|
|
847
|
+
if (parent === cursor)
|
|
848
|
+
break;
|
|
849
|
+
cursor = parent;
|
|
850
|
+
}
|
|
851
|
+
return candidates;
|
|
852
|
+
}
|
|
853
|
+
function rigGitBinaryCandidates() {
|
|
854
|
+
const execDir = process.execPath?.trim() ? dirname7(process.execPath.trim()) : "";
|
|
855
|
+
const fileName = runtimeRigGitFileName();
|
|
856
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
857
|
+
return [...new Set([
|
|
858
|
+
explicit,
|
|
859
|
+
...nativePackageBinaryCandidates(import.meta.dir, fileName),
|
|
860
|
+
execDir ? resolve9(execDir, fileName) : "",
|
|
861
|
+
execDir ? resolve9(execDir, "..", fileName) : "",
|
|
862
|
+
execDir ? resolve9(execDir, "..", "bin", fileName) : "",
|
|
863
|
+
sharedGitNativeOutputPath
|
|
864
|
+
].filter(Boolean))];
|
|
865
|
+
}
|
|
866
|
+
function resolveGitSourcePath() {
|
|
867
|
+
for (const candidate of rigGitSourceCandidates()) {
|
|
868
|
+
if (candidate && existsSync7(candidate)) {
|
|
869
|
+
return candidate;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
function resolveGitBinaryPath() {
|
|
875
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
for (const candidate of rigGitBinaryCandidates()) {
|
|
879
|
+
if (candidate && existsSync7(candidate)) {
|
|
880
|
+
return candidate;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
function preferredGitBinaryOutputPath() {
|
|
886
|
+
const explicit = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
887
|
+
return explicit || sharedGitNativeOutputPath;
|
|
888
|
+
}
|
|
889
|
+
function binarySupportsTrackerCommandsSync(binaryPath) {
|
|
890
|
+
try {
|
|
891
|
+
const probe = Bun.spawnSync([binaryPath, "fetch-ref", "."], {
|
|
892
|
+
stdout: "pipe",
|
|
893
|
+
stderr: "pipe"
|
|
894
|
+
});
|
|
895
|
+
const stdout = probe.stdout.toString().trim();
|
|
896
|
+
const stderr = probe.stderr.toString().trim();
|
|
897
|
+
if (stdout.includes('"error":"unknown command"')) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
return probe.exitCode === 2 && stderr.includes(trackerCommandUsageProbe);
|
|
901
|
+
} catch {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function nativeBuildManifestPath(outputPath) {
|
|
906
|
+
return `${outputPath}.build-manifest.json`;
|
|
907
|
+
}
|
|
908
|
+
function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
|
|
909
|
+
if (!existsSync7(manifestPath)) {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
914
|
+
return manifest.version === 1 && manifest.buildKey === buildKey;
|
|
915
|
+
} catch {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function sha256FileSync(path) {
|
|
920
|
+
return createHash("sha256").update(readFileSync3(path)).digest("hex");
|
|
921
|
+
}
|
|
922
|
+
function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
|
|
923
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
924
|
+
throw new Error("Zig native git is disabled via RIG_DISABLE_ZIG_NATIVE=1");
|
|
925
|
+
}
|
|
926
|
+
const sourcePath = resolveGitSourcePath();
|
|
927
|
+
if (!sourcePath) {
|
|
928
|
+
const binaryPath = resolveGitBinaryPath();
|
|
929
|
+
if (binaryPath) {
|
|
930
|
+
return binaryPath;
|
|
931
|
+
}
|
|
932
|
+
throw new Error("rig-git.zig source file not found.");
|
|
933
|
+
}
|
|
934
|
+
const zigBinary = Bun.which("zig");
|
|
935
|
+
if (!zigBinary) {
|
|
936
|
+
throw new Error("zig is required to build native Rig git tools.");
|
|
937
|
+
}
|
|
938
|
+
mkdirSync5(dirname7(outputPath), { recursive: true });
|
|
939
|
+
const sourceDigest = sha256FileSync(sourcePath);
|
|
940
|
+
const buildKey = JSON.stringify({
|
|
941
|
+
version: 1,
|
|
942
|
+
zigBinary,
|
|
943
|
+
platform: process.platform,
|
|
944
|
+
arch: process.arch,
|
|
945
|
+
sourcePath,
|
|
946
|
+
sourceDigest
|
|
947
|
+
});
|
|
948
|
+
const manifestPath = nativeBuildManifestPath(outputPath);
|
|
949
|
+
const needsBuild = !existsSync7(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
|
|
950
|
+
if (!needsBuild) {
|
|
951
|
+
chmodSync2(outputPath, 493);
|
|
952
|
+
return outputPath;
|
|
953
|
+
}
|
|
954
|
+
const tempOutputPath = temporaryGitBinaryOutputPath(outputPath);
|
|
955
|
+
const build = Bun.spawnSync([
|
|
956
|
+
zigBinary,
|
|
957
|
+
"build-exe",
|
|
958
|
+
sourcePath,
|
|
959
|
+
"-O",
|
|
960
|
+
"ReleaseFast",
|
|
961
|
+
`-femit-bin=${tempOutputPath}`
|
|
962
|
+
], {
|
|
963
|
+
cwd: dirname7(sourcePath),
|
|
964
|
+
stdout: "pipe",
|
|
965
|
+
stderr: "pipe"
|
|
966
|
+
});
|
|
967
|
+
if (build.exitCode !== 0 || !existsSync7(tempOutputPath)) {
|
|
968
|
+
const stderr = build.stderr.toString().trim();
|
|
969
|
+
const stdout = build.stdout.toString().trim();
|
|
970
|
+
const details = [stderr, stdout].filter(Boolean).join(`
|
|
971
|
+
`);
|
|
972
|
+
throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
|
|
973
|
+
}
|
|
974
|
+
chmodSync2(tempOutputPath, 493);
|
|
975
|
+
if (existsSync7(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
|
|
976
|
+
rmSync2(tempOutputPath, { force: true });
|
|
977
|
+
chmodSync2(outputPath, 493);
|
|
978
|
+
return outputPath;
|
|
979
|
+
}
|
|
980
|
+
publishGitBinary(tempOutputPath, outputPath);
|
|
981
|
+
if (!binarySupportsTrackerCommandsSync(outputPath)) {
|
|
982
|
+
rmSync2(outputPath, { force: true });
|
|
983
|
+
throw new Error("Failed to build native Rig git tools: tracker command probe failed");
|
|
984
|
+
}
|
|
985
|
+
writeFileSync3(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
|
|
986
|
+
`, "utf8");
|
|
987
|
+
return outputPath;
|
|
988
|
+
}
|
|
989
|
+
function runGitNative(command, args) {
|
|
990
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
991
|
+
return { ok: false, error: "rig-git native disabled" };
|
|
992
|
+
}
|
|
993
|
+
const trackerCommand = command === "fetch-ref" || command === "read-blob-at-ref" || command === "write-tree-commit" || command === "push-ref-with-lease";
|
|
994
|
+
let binaryPath = null;
|
|
995
|
+
if (trackerCommand) {
|
|
996
|
+
try {
|
|
997
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
998
|
+
} catch (error) {
|
|
999
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1000
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
1001
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
1002
|
+
}
|
|
1003
|
+
return { ok: false, error: message };
|
|
1004
|
+
}
|
|
1005
|
+
} else {
|
|
1006
|
+
const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
|
|
1007
|
+
binaryPath = explicitBinaryPath && existsSync7(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
|
|
1008
|
+
if (!binaryPath) {
|
|
1009
|
+
try {
|
|
1010
|
+
binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1013
|
+
if (message.includes("rig-git.zig source file not found")) {
|
|
1014
|
+
return { ok: false, error: "rig-git binary not found" };
|
|
1015
|
+
}
|
|
1016
|
+
return { ok: false, error: message };
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const proc = Bun.spawnSync([binaryPath, command, ...args], {
|
|
1022
|
+
stdout: "pipe",
|
|
1023
|
+
stderr: "pipe",
|
|
1024
|
+
env: process.env
|
|
1025
|
+
});
|
|
1026
|
+
if (proc.exitCode !== 0) {
|
|
1027
|
+
const stdoutText = proc.stdout.toString().trim();
|
|
1028
|
+
if (stdoutText) {
|
|
1029
|
+
try {
|
|
1030
|
+
const parsed = JSON.parse(stdoutText);
|
|
1031
|
+
if (!parsed.ok) {
|
|
1032
|
+
return parsed;
|
|
1033
|
+
}
|
|
1034
|
+
} catch {}
|
|
1035
|
+
}
|
|
1036
|
+
const errText = proc.stderr.toString().trim() || `exit code ${proc.exitCode}`;
|
|
1037
|
+
return { ok: false, error: errText };
|
|
1038
|
+
}
|
|
1039
|
+
const output = proc.stdout.toString().trim();
|
|
1040
|
+
return JSON.parse(output);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
return { ok: false, error: String(err) };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function requireGitNative(command, args) {
|
|
1046
|
+
const result = runGitNative(command, args);
|
|
1047
|
+
if (!result.ok) {
|
|
1048
|
+
throw new Error(`rig-git ${command} failed: ${result.error}`);
|
|
1049
|
+
}
|
|
1050
|
+
return result;
|
|
1051
|
+
}
|
|
1052
|
+
function requireGitNativeString(command, args) {
|
|
1053
|
+
const result = requireGitNative(command, args);
|
|
1054
|
+
if ("value" in result && typeof result.value === "string") {
|
|
1055
|
+
return result.value;
|
|
1056
|
+
}
|
|
1057
|
+
throw new Error(`rig-git ${command} returned an unexpected result payload`);
|
|
1058
|
+
}
|
|
1059
|
+
function nativeFetchRef(repoPath, remote, branch) {
|
|
1060
|
+
return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
|
|
1061
|
+
}
|
|
1062
|
+
function nativeReadBlobAtRef(repoPath, ref, path) {
|
|
1063
|
+
const requestDir = resolve9(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
1064
|
+
mkdirSync5(requestDir, { recursive: true });
|
|
1065
|
+
const outputPath = resolve9(requestDir, "blob.txt");
|
|
1066
|
+
try {
|
|
1067
|
+
requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
|
|
1068
|
+
return readFileSync3(outputPath, "utf8");
|
|
1069
|
+
} finally {
|
|
1070
|
+
rmSync2(requestDir, { recursive: true, force: true });
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// packages/runtime/src/control-plane/native/utils.ts
|
|
1075
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
1076
|
+
import { resolve as resolve11 } from "path";
|
|
1077
|
+
|
|
1078
|
+
// packages/runtime/src/control-plane/native/runtime-native.ts
|
|
1079
|
+
import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
|
|
1080
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync6, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
1081
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
1082
|
+
import { dirname as dirname8, resolve as resolve10 } from "path";
|
|
1083
|
+
var sharedNativeRuntimeOutputDir = resolve10(tmpdir4(), "rig-native");
|
|
1084
|
+
var sharedNativeRuntimeOutputPath = resolve10(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
|
|
1085
|
+
var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
|
|
1086
|
+
var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
|
|
1087
|
+
async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOutputPath, options = {}) {
|
|
1088
|
+
if (await buildNativeRuntimeLibrary(outputPath, options)) {
|
|
1089
|
+
return outputPath;
|
|
1090
|
+
}
|
|
1091
|
+
return !options.force && existsSync8(outputPath) ? outputPath : null;
|
|
1092
|
+
}
|
|
1093
|
+
async function loadNativeRuntimeLibrary() {
|
|
1094
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
for (const candidate of nativeRuntimeLibraryCandidates()) {
|
|
1098
|
+
if (!candidate || !existsSync8(candidate)) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const loaded = tryDlopenNativeRuntimeLibrary(candidate);
|
|
1102
|
+
if (loaded) {
|
|
1103
|
+
return loaded;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const builtLibraryPath = await ensureNativeRuntimeLibraryPath(sharedNativeRuntimeOutputPath, { force: true });
|
|
1107
|
+
if (!builtLibraryPath) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
return tryDlopenNativeRuntimeLibrary(builtLibraryPath);
|
|
1111
|
+
}
|
|
1112
|
+
function nativePackageLibraryCandidates(fromDir, names) {
|
|
1113
|
+
const candidates = [];
|
|
1114
|
+
let cursor = resolve10(fromDir);
|
|
1115
|
+
for (let index = 0;index < 8; index += 1) {
|
|
1116
|
+
for (const name of names) {
|
|
1117
|
+
candidates.push(resolve10(cursor, "native", `${process.platform}-${process.arch}`, name), resolve10(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve10(cursor, "native", name), resolve10(cursor, "native", "lib", name));
|
|
1118
|
+
}
|
|
1119
|
+
const parent = dirname8(cursor);
|
|
1120
|
+
if (parent === cursor)
|
|
1121
|
+
break;
|
|
1122
|
+
cursor = parent;
|
|
1123
|
+
}
|
|
1124
|
+
return candidates;
|
|
1125
|
+
}
|
|
1126
|
+
function nativeRuntimeLibraryCandidates() {
|
|
1127
|
+
const explicit = process.env.RIG_NATIVE_RUNTIME_LIB?.trim() || "";
|
|
1128
|
+
const execDir = process.execPath?.trim() ? dirname8(process.execPath.trim()) : "";
|
|
1129
|
+
const platformSpecific = `runtime-native-${process.platform}-${process.arch}.${suffix}`;
|
|
1130
|
+
return [...new Set([
|
|
1131
|
+
explicit,
|
|
1132
|
+
...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
|
|
1133
|
+
execDir ? resolve10(execDir, colocatedNativeRuntimeFileName) : "",
|
|
1134
|
+
execDir ? resolve10(execDir, platformSpecific) : "",
|
|
1135
|
+
execDir ? resolve10(execDir, "..", colocatedNativeRuntimeFileName) : "",
|
|
1136
|
+
execDir ? resolve10(execDir, "..", platformSpecific) : "",
|
|
1137
|
+
execDir ? resolve10(execDir, "lib", colocatedNativeRuntimeFileName) : "",
|
|
1138
|
+
execDir ? resolve10(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
|
|
1139
|
+
sharedNativeRuntimeOutputPath
|
|
1140
|
+
].filter(Boolean))];
|
|
1141
|
+
}
|
|
1142
|
+
function resolveNativeRuntimeSourcePath() {
|
|
1143
|
+
const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
|
|
1144
|
+
if (explicit && existsSync8(explicit)) {
|
|
1145
|
+
return explicit;
|
|
1146
|
+
}
|
|
1147
|
+
const bundled = resolve10(import.meta.dir, "../../../native/snapshot.zig");
|
|
1148
|
+
return existsSync8(bundled) ? bundled : null;
|
|
1149
|
+
}
|
|
1150
|
+
async function buildNativeRuntimeLibrary(outputPath, options = {}) {
|
|
1151
|
+
if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
const zigBinary = Bun.which("zig");
|
|
1155
|
+
const sourcePath = resolveNativeRuntimeSourcePath();
|
|
1156
|
+
if (!zigBinary || !sourcePath) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
1160
|
+
try {
|
|
1161
|
+
mkdirSync6(dirname8(outputPath), { recursive: true });
|
|
1162
|
+
const needsBuild = options.force === true || !existsSync8(outputPath) || statSync2(sourcePath).mtimeMs > statSync2(outputPath).mtimeMs;
|
|
1163
|
+
if (!needsBuild) {
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
const build = Bun.spawn([
|
|
1167
|
+
zigBinary,
|
|
1168
|
+
"build-lib",
|
|
1169
|
+
sourcePath,
|
|
1170
|
+
"-dynamic",
|
|
1171
|
+
"-O",
|
|
1172
|
+
"ReleaseFast",
|
|
1173
|
+
`-femit-bin=${tempOutputPath}`
|
|
1174
|
+
], {
|
|
1175
|
+
cwd: import.meta.dir,
|
|
1176
|
+
stdout: "pipe",
|
|
1177
|
+
stderr: "pipe"
|
|
1178
|
+
});
|
|
1179
|
+
const exitCode = await build.exited;
|
|
1180
|
+
if (exitCode !== 0 || !existsSync8(tempOutputPath)) {
|
|
1181
|
+
rmSync3(tempOutputPath, { force: true });
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1184
|
+
renameSync2(tempOutputPath, outputPath);
|
|
1185
|
+
return true;
|
|
1186
|
+
} catch {
|
|
1187
|
+
rmSync3(tempOutputPath, { force: true });
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function tryDlopenNativeRuntimeLibrary(outputPath) {
|
|
1192
|
+
try {
|
|
1193
|
+
return dlopen(outputPath, {
|
|
1194
|
+
rig_scope_match: {
|
|
1195
|
+
args: ["ptr", "ptr"],
|
|
1196
|
+
returns: "u8"
|
|
1197
|
+
},
|
|
1198
|
+
snapshot_capture: {
|
|
1199
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
1200
|
+
returns: "ptr"
|
|
1201
|
+
},
|
|
1202
|
+
snapshot_delta: {
|
|
1203
|
+
args: ["ptr", "ptr"],
|
|
1204
|
+
returns: "ptr"
|
|
1205
|
+
},
|
|
1206
|
+
snapshot_store_delta: {
|
|
1207
|
+
args: ["ptr", "ptr", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
|
|
1208
|
+
returns: "ptr"
|
|
1209
|
+
},
|
|
1210
|
+
snapshot_inspect_delta: {
|
|
1211
|
+
args: ["ptr", "u64"],
|
|
1212
|
+
returns: "ptr"
|
|
1213
|
+
},
|
|
1214
|
+
snapshot_apply_delta: {
|
|
1215
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
1216
|
+
returns: "ptr"
|
|
1217
|
+
},
|
|
1218
|
+
snapshot_release: {
|
|
1219
|
+
args: ["ptr"],
|
|
1220
|
+
returns: "void"
|
|
1221
|
+
},
|
|
1222
|
+
runtime_hash_file: {
|
|
1223
|
+
args: ["ptr", "u64"],
|
|
1224
|
+
returns: "ptr"
|
|
1225
|
+
},
|
|
1226
|
+
runtime_hash_tree: {
|
|
1227
|
+
args: ["ptr", "u64"],
|
|
1228
|
+
returns: "ptr"
|
|
1229
|
+
},
|
|
1230
|
+
runtime_prepare_paths: {
|
|
1231
|
+
args: ["ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64", "ptr", "u64"],
|
|
1232
|
+
returns: "ptr"
|
|
1233
|
+
},
|
|
1234
|
+
runtime_link_dependency_layer: {
|
|
1235
|
+
args: ["ptr", "u64", "ptr", "u64"],
|
|
1236
|
+
returns: "ptr"
|
|
1237
|
+
},
|
|
1238
|
+
runtime_scan_worktrees: {
|
|
1239
|
+
args: ["ptr", "u64"],
|
|
1240
|
+
returns: "ptr"
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// packages/runtime/src/control-plane/native/utils.ts
|
|
1249
|
+
function resolveMonorepoRoot2(projectRoot) {
|
|
1250
|
+
return resolveMonorepoRoot(projectRoot);
|
|
1251
|
+
}
|
|
1252
|
+
var scopeRegexCache = new Map;
|
|
1253
|
+
function runCapture(command, cwd, env) {
|
|
1254
|
+
const result = Bun.spawnSync(command, {
|
|
1255
|
+
cwd,
|
|
1256
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
1257
|
+
stdout: "pipe",
|
|
1258
|
+
stderr: "pipe"
|
|
1259
|
+
});
|
|
1260
|
+
return {
|
|
1261
|
+
exitCode: result.exitCode,
|
|
1262
|
+
stdout: result.stdout.toString(),
|
|
1263
|
+
stderr: result.stderr.toString()
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function readJsonFile(path, fallback) {
|
|
1267
|
+
if (!existsSync9(path)) {
|
|
1268
|
+
return fallback;
|
|
1269
|
+
}
|
|
1270
|
+
try {
|
|
1271
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
1272
|
+
} catch {
|
|
1273
|
+
return fallback;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function nowIso3() {
|
|
1277
|
+
return new Date().toISOString();
|
|
1278
|
+
}
|
|
1279
|
+
function resolveHarnessPaths(projectRoot) {
|
|
1280
|
+
const hasRuntimeWorkspace = Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
|
|
1281
|
+
const monorepoRoot = resolveMonorepoRoot2(projectRoot);
|
|
1282
|
+
const harnessRoot = resolve11(projectRoot, "rig");
|
|
1283
|
+
const stateRoot = resolve11(projectRoot, ".rig");
|
|
1284
|
+
const layout = hasRuntimeWorkspace ? resolveRigLayout(projectRoot) : null;
|
|
1285
|
+
const stateDir = layout?.stateDir ?? resolve11(stateRoot, "state");
|
|
1286
|
+
const logsDir = layout?.logsDir ?? resolve11(stateRoot, "logs");
|
|
1287
|
+
const artifactsDir = layout?.artifactsRoot ?? resolve11(monorepoRoot, "artifacts");
|
|
1288
|
+
const taskConfigPath = layout?.taskConfigPath ?? resolve11(monorepoRoot, ".rig", "task-config.json");
|
|
1289
|
+
const binDir = layout?.binDir ?? resolve11(stateRoot, "bin");
|
|
1290
|
+
return {
|
|
1291
|
+
harnessRoot,
|
|
1292
|
+
stateDir: process.env.RIG_STATE_DIR || stateDir,
|
|
1293
|
+
artifactsDir,
|
|
1294
|
+
logsDir: process.env.RIG_LOGS_DIR || logsDir,
|
|
1295
|
+
binDir,
|
|
1296
|
+
hooksDir: resolve11(harnessRoot, "hooks"),
|
|
1297
|
+
validationDir: resolve11(harnessRoot, "validation"),
|
|
1298
|
+
taskConfigPath,
|
|
1299
|
+
sessionPath: process.env.RIG_SESSION_FILE || resolve11(stateRoot, "session", "session.json"),
|
|
1300
|
+
monorepoRoot,
|
|
1301
|
+
tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve11(monorepoRoot, "TSAPITests"),
|
|
1302
|
+
taskRepoCommitsPath: resolve11(stateDir, "task-repo-commits.json"),
|
|
1303
|
+
baseRepoPinsPath: resolve11(stateDir, "base-repo-pins.json"),
|
|
1304
|
+
failedApproachesPath: resolve11(stateDir, "failed_approaches.md"),
|
|
1305
|
+
agentProfilePath: resolve11(stateDir, "agent-profile.json"),
|
|
1306
|
+
reviewProfilePath: resolve11(stateDir, "review-profile.json")
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// packages/runtime/src/control-plane/state-sync/repo.ts
|
|
1311
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1312
|
+
import { resolve as resolve12 } from "path";
|
|
1313
|
+
function resolveTrackerRepoPath(projectRoot) {
|
|
1314
|
+
const monorepoRoot = resolveMonorepoRoot2(projectRoot);
|
|
1315
|
+
try {
|
|
1316
|
+
const layout = resolveMonorepoRepoLayout(projectRoot);
|
|
1317
|
+
if (existsSync10(resolve12(layout.mirrorRoot, "HEAD"))) {
|
|
1318
|
+
return layout.mirrorRoot;
|
|
1319
|
+
}
|
|
1320
|
+
} catch {}
|
|
1321
|
+
return monorepoRoot;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// packages/runtime/src/control-plane/state-sync/read.ts
|
|
1325
|
+
var DEFAULT_READ_DEPS = {
|
|
1326
|
+
fetchRef: nativeFetchRef,
|
|
1327
|
+
readBlobAtRef: nativeReadBlobAtRef,
|
|
1328
|
+
exists: existsSync11,
|
|
1329
|
+
readFile: (path) => readFileSync5(path, "utf8")
|
|
1330
|
+
};
|
|
1331
|
+
function parseIssueStatus(rawStatus) {
|
|
1332
|
+
const normalized = normalizeTaskLifecycleStatus(rawStatus);
|
|
1333
|
+
return normalized ?? "unknown";
|
|
1334
|
+
}
|
|
1335
|
+
function parseIssueDependencies(raw) {
|
|
1336
|
+
if (!Array.isArray(raw)) {
|
|
1337
|
+
return [];
|
|
1338
|
+
}
|
|
1339
|
+
return raw.filter((entry) => !!entry && typeof entry === "object" && !Array.isArray(entry)).map((entry) => ({
|
|
1340
|
+
issueId: typeof entry.issue_id === "string" && entry.issue_id.trim() ? entry.issue_id.trim() : null,
|
|
1341
|
+
dependsOnId: typeof entry.depends_on_id === "string" && entry.depends_on_id.trim() ? entry.depends_on_id.trim() : null,
|
|
1342
|
+
id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null,
|
|
1343
|
+
type: typeof entry.type === "string" && entry.type.trim() ? entry.type.trim() : null
|
|
1344
|
+
}));
|
|
1345
|
+
}
|
|
1346
|
+
function parseIssuesJsonl(raw) {
|
|
1347
|
+
const issues = [];
|
|
1348
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1349
|
+
const trimmed = line.trim();
|
|
1350
|
+
if (!trimmed) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
const record = JSON.parse(trimmed);
|
|
1355
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
|
|
1356
|
+
if (!id) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
const rawStatus = typeof record.status === "string" && record.status.trim() ? record.status.trim() : null;
|
|
1360
|
+
issues.push({
|
|
1361
|
+
id,
|
|
1362
|
+
title: typeof record.title === "string" && record.title.trim() ? record.title.trim() : null,
|
|
1363
|
+
description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
|
|
1364
|
+
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,
|
|
1365
|
+
issueType: typeof record.issue_type === "string" && record.issue_type.trim() ? record.issue_type.trim() : null,
|
|
1366
|
+
status: parseIssueStatus(rawStatus),
|
|
1367
|
+
rawStatus,
|
|
1368
|
+
priority: typeof record.priority === "number" ? record.priority : null,
|
|
1369
|
+
dependencies: parseIssueDependencies(record.dependencies)
|
|
1370
|
+
});
|
|
1371
|
+
} catch {}
|
|
1372
|
+
}
|
|
1373
|
+
return issues;
|
|
1374
|
+
}
|
|
1375
|
+
function parseTaskStateEnvelope(raw) {
|
|
1376
|
+
if (!raw || !raw.trim()) {
|
|
1377
|
+
return readTaskStateMetadataEnvelope(null);
|
|
1378
|
+
}
|
|
1379
|
+
try {
|
|
1380
|
+
return readTaskStateMetadataEnvelope(JSON.parse(raw));
|
|
1381
|
+
} catch {
|
|
1382
|
+
return readTaskStateMetadataEnvelope(null);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function readRemoteBlobAtRef(deps, repoPath, ref, path, options) {
|
|
1386
|
+
try {
|
|
1387
|
+
return deps.readBlobAtRef(repoPath, ref, path);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
if (options.required) {
|
|
1390
|
+
throw error;
|
|
1391
|
+
}
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function shouldPreferLocalTrackerState(options) {
|
|
1396
|
+
if (!options.allowLocalFallback) {
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
1400
|
+
if (!runtimeWorkspace) {
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
if (process.env.RIG_TASK_RUNTIME_ID?.trim()) {
|
|
1404
|
+
return true;
|
|
1405
|
+
}
|
|
1406
|
+
const runtimeContextPath = process.env[RUNTIME_CONTEXT_ENV]?.trim();
|
|
1407
|
+
if (runtimeContextPath) {
|
|
1408
|
+
return true;
|
|
1409
|
+
}
|
|
1410
|
+
return existsSync11(resolve13(runtimeWorkspace, ".rig", "runtime-context.json"));
|
|
1411
|
+
}
|
|
1412
|
+
function readLocalTrackerState(projectRoot, deps) {
|
|
1413
|
+
const monorepoRoot = resolveMonorepoRoot2(projectRoot);
|
|
1414
|
+
const issuesPath = resolve13(monorepoRoot, ".beads", "issues.jsonl");
|
|
1415
|
+
const taskStatePath = resolve13(monorepoRoot, ".beads", "task-state.json");
|
|
1416
|
+
return projectSyncedTrackerSnapshot({
|
|
1417
|
+
source: "local",
|
|
1418
|
+
issuesBaseOid: null,
|
|
1419
|
+
issuesText: deps.exists(issuesPath) ? deps.readFile(issuesPath) : "",
|
|
1420
|
+
taskStateBaseOid: null,
|
|
1421
|
+
taskStateText: deps.exists(taskStatePath) ? deps.readFile(taskStatePath) : null
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
function projectSyncedTrackerSnapshot(input) {
|
|
1425
|
+
if (input.source === "remote" && input.issuesBaseOid && input.taskStateBaseOid && input.issuesBaseOid !== input.taskStateBaseOid) {
|
|
1426
|
+
throw new Error("Remote tracker files must be read from the same fetched base.");
|
|
1427
|
+
}
|
|
1428
|
+
return {
|
|
1429
|
+
source: input.source,
|
|
1430
|
+
baseOid: input.issuesBaseOid ?? input.taskStateBaseOid ?? null,
|
|
1431
|
+
issues: parseIssuesJsonl(input.issuesText),
|
|
1432
|
+
taskState: parseTaskStateEnvelope(input.taskStateText)
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function readSyncedTrackerState(projectRoot, deps = {}, options = {}) {
|
|
1436
|
+
const readDeps = { ...DEFAULT_READ_DEPS, ...deps };
|
|
1437
|
+
const trackerRepoPath = resolveTrackerRepoPath(projectRoot);
|
|
1438
|
+
if (shouldPreferLocalTrackerState(options)) {
|
|
1439
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
1440
|
+
}
|
|
1441
|
+
try {
|
|
1442
|
+
const baseOid = readDeps.fetchRef(trackerRepoPath, "origin", "main");
|
|
1443
|
+
return projectSyncedTrackerSnapshot({
|
|
1444
|
+
source: "remote",
|
|
1445
|
+
issuesBaseOid: baseOid,
|
|
1446
|
+
issuesText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/issues.jsonl", {
|
|
1447
|
+
required: true
|
|
1448
|
+
}) ?? "",
|
|
1449
|
+
taskStateBaseOid: baseOid,
|
|
1450
|
+
taskStateText: readRemoteBlobAtRef(readDeps, trackerRepoPath, baseOid, ".beads/task-state.json", {
|
|
1451
|
+
required: false
|
|
1452
|
+
})
|
|
1453
|
+
});
|
|
1454
|
+
} catch (error) {
|
|
1455
|
+
if (!options.allowLocalFallback) {
|
|
1456
|
+
throw error;
|
|
1457
|
+
}
|
|
1458
|
+
return readLocalTrackerState(projectRoot, readDeps);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
// packages/runtime/src/control-plane/state-sync/reconcile.ts
|
|
1462
|
+
var STALE_CLAIM_MS = 24 * 60 * 60 * 1000;
|
|
1463
|
+
// packages/runtime/src/control-plane/native/task-state.ts
|
|
1464
|
+
function readTaskConfig(projectRoot) {
|
|
1465
|
+
const raw = readJsonFile(resolveTaskConfigPath(projectRoot), {});
|
|
1466
|
+
return stripTaskConfigMetadata(raw);
|
|
1467
|
+
}
|
|
1468
|
+
function readSourceTaskConfig(projectRoot) {
|
|
1469
|
+
const raw = readAndSyncSourceTaskConfig(projectRoot);
|
|
1470
|
+
return stripTaskConfigMetadata(raw);
|
|
1471
|
+
}
|
|
1472
|
+
function currentTaskId(projectRoot) {
|
|
1473
|
+
const fromEnv = (process.env.RIG_TASK_ID || "").trim();
|
|
1474
|
+
if (fromEnv) {
|
|
1475
|
+
return fromEnv;
|
|
1476
|
+
}
|
|
1477
|
+
const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
|
|
1478
|
+
if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
|
|
1479
|
+
return runtimeId.slice("task-".length);
|
|
1480
|
+
}
|
|
1481
|
+
const workspace = (process.env.RIG_TASK_WORKSPACE || "").trim();
|
|
1482
|
+
const inferredFromWorkspace = inferTaskIdFromRuntimePath(workspace);
|
|
1483
|
+
if (inferredFromWorkspace) {
|
|
1484
|
+
return inferredFromWorkspace;
|
|
1485
|
+
}
|
|
1486
|
+
const inferredFromCwd = inferTaskIdFromRuntimePath(process.cwd());
|
|
1487
|
+
if (inferredFromCwd) {
|
|
1488
|
+
return inferredFromCwd;
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1492
|
+
const session = readJsonFile(paths.sessionPath, {});
|
|
1493
|
+
return session.activeTaskIds?.[0] || "";
|
|
1494
|
+
} catch {
|
|
1495
|
+
return "";
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
function stripTaskConfigMetadata(raw) {
|
|
1499
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
1500
|
+
return tasks;
|
|
1501
|
+
}
|
|
1502
|
+
function coerceValidationDescriptions(candidate) {
|
|
1503
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
1504
|
+
return {};
|
|
1505
|
+
}
|
|
1506
|
+
const descriptions = {};
|
|
1507
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
1508
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1509
|
+
descriptions[key] = value;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return descriptions;
|
|
1513
|
+
}
|
|
1514
|
+
function readValidationDescriptionsFromMeta(meta) {
|
|
1515
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
return meta.validation_descriptions;
|
|
1519
|
+
}
|
|
1520
|
+
function inferTaskIdFromRuntimePath(path) {
|
|
1521
|
+
if (!path) {
|
|
1522
|
+
return "";
|
|
1523
|
+
}
|
|
1524
|
+
const match = path.match(/\/\.rig\/runtime\/agents\/task-([^/]+)\/worktree(?:\/|$)/) || path.match(/\/\.worktrees\/([^/]+)(?:\/|$)/);
|
|
1525
|
+
const candidate = match?.[1] || "";
|
|
1526
|
+
return candidate.startsWith("bd-") ? candidate : "";
|
|
1527
|
+
}
|
|
1528
|
+
function artifactDirForId(projectRoot, id) {
|
|
1529
|
+
const workspaceDir = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
1530
|
+
if (workspaceDir) {
|
|
1531
|
+
const worktreeArtifacts = resolve14(workspaceDir, "artifacts", id);
|
|
1532
|
+
if (existsSync12(worktreeArtifacts) || existsSync12(resolve14(workspaceDir, "artifacts"))) {
|
|
1533
|
+
return worktreeArtifacts;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
try {
|
|
1537
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1538
|
+
return resolve14(paths.artifactsDir, id);
|
|
1539
|
+
} catch {
|
|
1540
|
+
return resolve14(resolveMonorepoRoot2(projectRoot), "artifacts", id);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function resolveTaskConfigPath(projectRoot) {
|
|
1544
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1545
|
+
if (existsSync12(paths.taskConfigPath)) {
|
|
1546
|
+
return paths.taskConfigPath;
|
|
1547
|
+
}
|
|
1548
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
1549
|
+
if (existsSync12(candidate)) {
|
|
1550
|
+
return candidate;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
throw new Error(`Task config missing at ${paths.taskConfigPath}.`);
|
|
1554
|
+
}
|
|
1555
|
+
function findSourceTaskConfigPath(projectRoot) {
|
|
1556
|
+
for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
|
|
1557
|
+
if (existsSync12(candidate)) {
|
|
1558
|
+
return candidate;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return null;
|
|
1562
|
+
}
|
|
1563
|
+
var FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
1564
|
+
function readAndSyncSourceTaskConfig(projectRoot) {
|
|
1565
|
+
const sourcePath = findSourceTaskConfigPath(projectRoot);
|
|
1566
|
+
const raw = sourcePath ? readJsonFile(sourcePath, {}) : readConfiguredFileTaskConfig(projectRoot);
|
|
1567
|
+
const synced = synchronizeTaskConfigWithTracker(projectRoot, raw);
|
|
1568
|
+
if (sourcePath && synced.updated) {
|
|
1569
|
+
try {
|
|
1570
|
+
writeFileSync4(sourcePath, `${JSON.stringify(synced.config, null, 2)}
|
|
1571
|
+
`, "utf-8");
|
|
1572
|
+
} catch {}
|
|
1573
|
+
}
|
|
1574
|
+
return synced.config;
|
|
1575
|
+
}
|
|
1576
|
+
function synchronizeTaskConfigWithTracker(projectRoot, rawConfig) {
|
|
1577
|
+
const issues = readSourceIssueRecords(projectRoot);
|
|
1578
|
+
if (issues.length === 0) {
|
|
1579
|
+
return { config: rawConfig, updated: false };
|
|
1580
|
+
}
|
|
1581
|
+
const taskConfig = stripTaskConfigMetadata(rawConfig);
|
|
1582
|
+
const mergedConfig = { ...taskConfig };
|
|
1583
|
+
const validationDescriptions = coerceValidationDescriptions(rawConfig.validation_descriptions);
|
|
1584
|
+
const metaValidationDescriptions = coerceValidationDescriptions(readValidationDescriptionsFromMeta(rawConfig._meta));
|
|
1585
|
+
let updated = false;
|
|
1586
|
+
for (const issue of issues) {
|
|
1587
|
+
if (issue.issueType !== "task") {
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (mergedConfig[issue.id] && !shouldRefreshAutoSyncedTaskConfigEntry(mergedConfig[issue.id])) {
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
mergedConfig[issue.id] = buildAutoSyncedTaskConfigEntry(issue);
|
|
1594
|
+
updated = true;
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
config: {
|
|
1598
|
+
...mergedConfig,
|
|
1599
|
+
...Object.keys(validationDescriptions).length > 0 ? { validation_descriptions: validationDescriptions } : {},
|
|
1600
|
+
...Object.keys(metaValidationDescriptions).length > 0 ? { _meta: { validation_descriptions: metaValidationDescriptions } } : {}
|
|
1601
|
+
},
|
|
1602
|
+
updated
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function shouldRefreshAutoSyncedTaskConfigEntry(entry) {
|
|
1606
|
+
if (!entry || typeof entry !== "object") {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
const candidate = entry;
|
|
1610
|
+
if (!candidate.auto_synced) {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
if (!Array.isArray(candidate.scope) || candidate.scope.length === 0) {
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
if (candidate.scope.some((glob) => typeof glob !== "string" || glob.trim().length === 0)) {
|
|
1617
|
+
return true;
|
|
1618
|
+
}
|
|
1619
|
+
return !candidate.role;
|
|
1620
|
+
}
|
|
1621
|
+
function readSourceIssueRecords(projectRoot) {
|
|
1622
|
+
const issuesPath = resolve14(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
|
|
1623
|
+
if (!existsSync12(issuesPath)) {
|
|
1624
|
+
return [];
|
|
1625
|
+
}
|
|
1626
|
+
const records = [];
|
|
1627
|
+
for (const line of readFileSync6(issuesPath, "utf-8").split(/\r?\n/)) {
|
|
1628
|
+
const trimmed = line.trim();
|
|
1629
|
+
if (!trimmed) {
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
const parsed = JSON.parse(trimmed);
|
|
1634
|
+
const id = typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
1635
|
+
if (!id) {
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
records.push({
|
|
1639
|
+
id,
|
|
1640
|
+
title: typeof parsed.title === "string" ? parsed.title.trim() : "",
|
|
1641
|
+
issueType: typeof parsed.issue_type === "string" ? parsed.issue_type.trim() : null,
|
|
1642
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.filter((label) => typeof label === "string") : []
|
|
1643
|
+
});
|
|
1644
|
+
} catch {}
|
|
1645
|
+
}
|
|
1646
|
+
return records;
|
|
1647
|
+
}
|
|
1648
|
+
function buildAutoSyncedTaskConfigEntry(issue) {
|
|
1649
|
+
return {
|
|
1650
|
+
auto_synced: true,
|
|
1651
|
+
role: inferAutoSyncedTaskRole(issue),
|
|
1652
|
+
scope: inferAutoSyncedTaskScope(issue),
|
|
1653
|
+
validation: []
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function inferAutoSyncedTaskRole(issue) {
|
|
1657
|
+
for (const label of issue.labels) {
|
|
1658
|
+
if (label === "role:architect")
|
|
1659
|
+
return "architect";
|
|
1660
|
+
if (label === "role:extractor")
|
|
1661
|
+
return "extractor";
|
|
1662
|
+
if (label === "role:mechanic")
|
|
1663
|
+
return "mechanic";
|
|
1664
|
+
if (label === "role:verifier")
|
|
1665
|
+
return "verifier";
|
|
1666
|
+
}
|
|
1667
|
+
if (/\bDESIGN\b/i.test(issue.title)) {
|
|
1668
|
+
return "architect";
|
|
1669
|
+
}
|
|
1670
|
+
if (/\bInitialize\b/i.test(issue.title)) {
|
|
1671
|
+
return "mechanic";
|
|
1672
|
+
}
|
|
1673
|
+
return "extractor";
|
|
1674
|
+
}
|
|
1675
|
+
function inferAutoSyncedTaskScope(issue) {
|
|
1676
|
+
return [`artifacts/${issue.id}/**`];
|
|
1677
|
+
}
|
|
1678
|
+
function readConfiguredFileTaskConfig(projectRoot) {
|
|
1679
|
+
const sourcePath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
1680
|
+
if (!sourcePath) {
|
|
1681
|
+
return {};
|
|
1682
|
+
}
|
|
1683
|
+
const directory = resolve14(projectRoot, sourcePath);
|
|
1684
|
+
if (!existsSync12(directory)) {
|
|
1685
|
+
return {};
|
|
1686
|
+
}
|
|
1687
|
+
const config = {};
|
|
1688
|
+
for (const name of readdirSync2(directory)) {
|
|
1689
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
1690
|
+
continue;
|
|
1691
|
+
const file = resolve14(directory, name);
|
|
1692
|
+
try {
|
|
1693
|
+
if (!statSync3(file).isFile())
|
|
1694
|
+
continue;
|
|
1695
|
+
const raw = JSON.parse(readFileSync6(file, "utf8"));
|
|
1696
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1697
|
+
continue;
|
|
1698
|
+
const record = raw;
|
|
1699
|
+
const inferredId = basename5(name).replace(FILE_TASK_PATTERN, "");
|
|
1700
|
+
const id = typeof record.id === "string" && record.id.trim().length > 0 ? record.id.trim() : inferredId;
|
|
1701
|
+
config[id] = fileTaskToConfigEntry(record, { kind: "files", path: sourcePath });
|
|
1702
|
+
} catch {}
|
|
1703
|
+
}
|
|
1704
|
+
return config;
|
|
1705
|
+
}
|
|
1706
|
+
function fileTaskToConfigEntry(task, source) {
|
|
1707
|
+
const labels = Array.isArray(task.labels) ? task.labels.filter((label) => typeof label === "string") : [];
|
|
1708
|
+
const scope = firstStringList(task.scope, labels.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length)));
|
|
1709
|
+
const validation = firstStringList(task.validation, task.validators, labels.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length)));
|
|
1710
|
+
const roleLabel = labels.find((label) => label.startsWith("role:"));
|
|
1711
|
+
const role = typeof task.role === "string" && task.role.trim().length > 0 ? task.role.trim() : roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
1712
|
+
return {
|
|
1713
|
+
auto_synced: true,
|
|
1714
|
+
...typeof task.title === "string" ? { title: task.title } : {},
|
|
1715
|
+
...typeof task.status === "string" ? { status: task.status } : {},
|
|
1716
|
+
...typeof task.description === "string" ? { description: task.description } : {},
|
|
1717
|
+
...typeof task.acceptance_criteria === "string" ? { acceptance_criteria: task.acceptance_criteria } : typeof task.acceptanceCriteria === "string" ? { acceptance_criteria: task.acceptanceCriteria } : {},
|
|
1718
|
+
...role ? { role } : {},
|
|
1719
|
+
...scope.length > 0 ? { scope } : {},
|
|
1720
|
+
...validation.length > 0 ? { validation } : {},
|
|
1721
|
+
_rig: { taskSource: source }
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function firstStringList(...candidates) {
|
|
1725
|
+
for (const candidate of candidates) {
|
|
1726
|
+
if (!Array.isArray(candidate)) {
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
const list = candidate.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
1730
|
+
if (list.length > 0) {
|
|
1731
|
+
return list;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return [];
|
|
1735
|
+
}
|
|
1736
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
1737
|
+
const jsonPath = resolve14(projectRoot, "rig.config.json");
|
|
1738
|
+
if (existsSync12(jsonPath)) {
|
|
1739
|
+
try {
|
|
1740
|
+
const parsed = JSON.parse(readFileSync6(jsonPath, "utf8"));
|
|
1741
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1742
|
+
const taskSource = parsed.taskSource;
|
|
1743
|
+
if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
|
|
1744
|
+
const record = taskSource;
|
|
1745
|
+
return record.kind === "files" && typeof record.path === "string" ? record.path : null;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
} catch {
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
const tsPath = resolve14(projectRoot, "rig.config.ts");
|
|
1753
|
+
if (!existsSync12(tsPath)) {
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
try {
|
|
1757
|
+
const source = readFileSync6(tsPath, "utf8");
|
|
1758
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
1759
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1760
|
+
if (kind !== "files") {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
1764
|
+
} catch {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function sourceTaskConfigCandidates(projectRoot) {
|
|
1769
|
+
const runtimeContext = loadRuntimeContextFromEnv();
|
|
1770
|
+
return [
|
|
1771
|
+
runtimeContext?.monorepoMainRoot ? resolve14(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
|
|
1772
|
+
process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve14(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
|
|
1773
|
+
resolve14(resolveMonorepoRoot2(projectRoot), ".rig", "task-config.json")
|
|
1774
|
+
].filter(Boolean);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// packages/runtime/src/binary-run.ts
|
|
1778
|
+
var runtimeBinaryBuildQueue = Promise.resolve();
|
|
1779
|
+
// packages/runtime/src/control-plane/native/git-ops.ts
|
|
1780
|
+
var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
|
|
1781
|
+
"changed-files.txt",
|
|
1782
|
+
"contract-changes.md",
|
|
1783
|
+
"decision-log.md",
|
|
1784
|
+
"git-state.txt",
|
|
1785
|
+
"next-actions.md",
|
|
1786
|
+
"pr-state.json",
|
|
1787
|
+
"task-result.json",
|
|
1788
|
+
"validation-summary.json"
|
|
1789
|
+
]);
|
|
1790
|
+
|
|
1791
|
+
// packages/runtime/src/control-plane/provider/runtime-instructions.ts
|
|
1792
|
+
var CLAUDE_ROUTER_TOOL_NAMES = [
|
|
1793
|
+
"`mcp__rig_runtime_tools__read`",
|
|
1794
|
+
"`mcp__rig_runtime_tools__write`",
|
|
1795
|
+
"`mcp__rig_runtime_tools__edit`",
|
|
1796
|
+
"`mcp__rig_runtime_tools__glob`",
|
|
1797
|
+
"`mcp__rig_runtime_tools__grep`"
|
|
1798
|
+
].join(", ");
|
|
1799
|
+
var CODEX_DYNAMIC_TOOL_NAMES = [
|
|
1800
|
+
"`shell`",
|
|
1801
|
+
"`read`",
|
|
1802
|
+
"`write`",
|
|
1803
|
+
"`edit`",
|
|
1804
|
+
"`glob`",
|
|
1805
|
+
"`grep`"
|
|
1806
|
+
].join(", ");
|
|
1807
|
+
|
|
1808
|
+
// packages/runtime/src/control-plane/native/task-ops.ts
|
|
1809
|
+
var BUILD_CONFIG = readBuildConfig();
|
|
1810
|
+
var BAKED_INFO_OUTPUT = BUILD_CONFIG.AGENT_INFO_OUTPUT ?? "";
|
|
1811
|
+
var BAKED_DEPS_OUTPUT = BUILD_CONFIG.AGENT_DEPS_OUTPUT ?? "";
|
|
1812
|
+
var BAKED_STATUS_OUTPUT = BUILD_CONFIG.AGENT_STATUS_OUTPUT ?? "";
|
|
1813
|
+
var REOPENABLE_TASK_STATUSES = new Set(["completed", "cancelled", "blocked"]);
|
|
1814
|
+
var GENERATED_TASK_ARTIFACT_FILES = new Set([
|
|
1815
|
+
"changed-files.txt",
|
|
1816
|
+
"decision-log.md",
|
|
1817
|
+
"next-actions.md",
|
|
1818
|
+
"task-result.json",
|
|
1819
|
+
"validation-summary.json",
|
|
1820
|
+
"review-feedback.md",
|
|
1821
|
+
"review-state.json",
|
|
1822
|
+
"review-status.txt",
|
|
1823
|
+
"review-greptile-raw.json",
|
|
1824
|
+
"pr-state.json",
|
|
1825
|
+
"git-state.txt"
|
|
1826
|
+
]);
|
|
1827
|
+
function readLocalBeadsTasks(projectRoot) {
|
|
1828
|
+
const issuesPath = resolve15(resolveMonorepoRoot2(projectRoot), ".beads/issues.jsonl");
|
|
1829
|
+
if (!existsSync13(issuesPath)) {
|
|
1830
|
+
return [];
|
|
1831
|
+
}
|
|
1832
|
+
const tasks = [];
|
|
1833
|
+
for (const line of readFileSync7(issuesPath, "utf-8").split(/\r?\n/)) {
|
|
1834
|
+
const trimmed = line.trim();
|
|
1835
|
+
if (!trimmed) {
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
try {
|
|
1839
|
+
const parsed = JSON.parse(trimmed);
|
|
1840
|
+
if (parsed.issue_type === "task" && parsed.id) {
|
|
1841
|
+
tasks.push({
|
|
1842
|
+
id: parsed.id,
|
|
1843
|
+
status: parsed.status || "open",
|
|
1844
|
+
issue_type: parsed.issue_type,
|
|
1845
|
+
title: parsed.title,
|
|
1846
|
+
priority: parsed.priority,
|
|
1847
|
+
description: parsed.description,
|
|
1848
|
+
acceptance_criteria: parsed.acceptance_criteria,
|
|
1849
|
+
acceptanceCriteria: parsed.acceptanceCriteria,
|
|
1850
|
+
dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies : []
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
} catch {}
|
|
1854
|
+
}
|
|
1855
|
+
return tasks;
|
|
1856
|
+
}
|
|
1857
|
+
function readBeadsTasks(projectRoot, tracker, allowLocalFallback = false) {
|
|
1858
|
+
return (tracker ?? loadTaskTrackerContext(projectRoot, undefined, allowLocalFallback)).tasks;
|
|
1859
|
+
}
|
|
1860
|
+
function loadTaskTrackerContext(projectRoot, snapshot, allowLocalFallback = false) {
|
|
1861
|
+
const resolvedSnapshot = snapshot ?? readSyncedTrackerState(projectRoot, {}, allowLocalFallback ? { allowLocalFallback: true } : {});
|
|
1862
|
+
const tasks = resolvedSnapshot.issues.filter((issue) => issue.issueType === "task").map(projectTaskRecordFromSyncedIssue);
|
|
1863
|
+
const seenTaskIds = new Set(tasks.map((task) => task.id));
|
|
1864
|
+
if (allowLocalFallback) {
|
|
1865
|
+
for (const localTask of readLocalBeadsTasks(projectRoot)) {
|
|
1866
|
+
if (seenTaskIds.has(localTask.id)) {
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
tasks.push(localTask);
|
|
1870
|
+
seenTaskIds.add(localTask.id);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return {
|
|
1874
|
+
snapshot: resolvedSnapshot,
|
|
1875
|
+
tasks,
|
|
1876
|
+
tasksById: new Map(tasks.map((task) => [task.id, task]))
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function loadReadonlyTaskTrackerContext(projectRoot) {
|
|
1880
|
+
return loadTaskTrackerContext(projectRoot, undefined, true);
|
|
1881
|
+
}
|
|
1882
|
+
function projectTaskRecordFromSyncedIssue(issue) {
|
|
1883
|
+
const dependencies = issue.dependencies ?? [];
|
|
1884
|
+
return {
|
|
1885
|
+
id: issue.id,
|
|
1886
|
+
status: issue.status === "unknown" ? issue.rawStatus || "open" : issue.status,
|
|
1887
|
+
issue_type: "task",
|
|
1888
|
+
title: issue.title ?? undefined,
|
|
1889
|
+
priority: issue.priority ?? undefined,
|
|
1890
|
+
description: issue.description ?? undefined,
|
|
1891
|
+
acceptance_criteria: issue.acceptanceCriteria ?? undefined,
|
|
1892
|
+
acceptanceCriteria: issue.acceptanceCriteria ?? undefined,
|
|
1893
|
+
dependencies: dependencies.map((dependency) => ({
|
|
1894
|
+
type: dependency.type ?? undefined,
|
|
1895
|
+
issue_id: dependency.issueId ?? undefined,
|
|
1896
|
+
depends_on_id: dependency.dependsOnId ?? undefined,
|
|
1897
|
+
id: dependency.id ?? dependency.issueId ?? dependency.dependsOnId ?? undefined
|
|
1898
|
+
}))
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function taskDependencyIds(projectRoot, taskId) {
|
|
1902
|
+
const tracker = loadReadonlyTaskTrackerContext(projectRoot);
|
|
1903
|
+
const canonicalTaskIds = new Set(tracker.tasks.map((task) => task.id));
|
|
1904
|
+
const record = readBeadsTasks(projectRoot, tracker).find((entry) => entry.id === taskId);
|
|
1905
|
+
if (!record?.dependencies?.length) {
|
|
1906
|
+
return [];
|
|
1907
|
+
}
|
|
1908
|
+
const ids = new Set;
|
|
1909
|
+
for (const edge of record.dependencies) {
|
|
1910
|
+
if (!edge || edge.type === "parent-child") {
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
for (const candidate of [edge.depends_on_id, edge.id, edge.issue_id]) {
|
|
1914
|
+
if (typeof candidate !== "string" || candidate === taskId || !canonicalTaskIds.has(candidate)) {
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
ids.add(candidate);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
return [...ids].sort();
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// packages/runtime/src/control-plane/native/repo-ops.ts
|
|
1924
|
+
function primaryManagedRepoId() {
|
|
1925
|
+
const entries = listManagedRepoEntries();
|
|
1926
|
+
return entries.length > 0 ? entries[0].id : null;
|
|
1927
|
+
}
|
|
1928
|
+
function primaryManagedRepoAlias() {
|
|
1929
|
+
const entries = listManagedRepoEntries();
|
|
1930
|
+
return entries.length > 0 ? entries[0].alias : null;
|
|
1931
|
+
}
|
|
1932
|
+
function repoEnsure(projectRoot, taskId) {
|
|
1933
|
+
const monorepo = ensureMonorepoReady(projectRoot);
|
|
1934
|
+
const resolvedTask = taskId || currentTaskId(projectRoot);
|
|
1935
|
+
if (!resolvedTask) {
|
|
1936
|
+
if (monorepo) {
|
|
1937
|
+
const alias = primaryManagedRepoAlias();
|
|
1938
|
+
if (alias) {
|
|
1939
|
+
persistBaselinePins(projectRoot, { [alias]: monorepo.headCommit });
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
console.log("No active task. Refreshed baseline repo pins.");
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
const pins = resolvedPins(projectRoot, resolvedTask);
|
|
1946
|
+
applyPins(projectRoot, pins);
|
|
1947
|
+
verifyPins(projectRoot, pins);
|
|
1948
|
+
}
|
|
1949
|
+
function repoPins(projectRoot, taskId) {
|
|
1950
|
+
const resolvedTask = taskId || currentTaskId(projectRoot);
|
|
1951
|
+
if (!resolvedTask) {
|
|
1952
|
+
return {};
|
|
1953
|
+
}
|
|
1954
|
+
return resolvedPins(projectRoot, resolvedTask);
|
|
1955
|
+
}
|
|
1956
|
+
function repoVerify(projectRoot, taskId) {
|
|
1957
|
+
const resolvedTask = taskId || currentTaskId(projectRoot);
|
|
1958
|
+
if (!resolvedTask) {
|
|
1959
|
+
console.log("No active task. Nothing to verify.");
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
const pins = resolvedPins(projectRoot, resolvedTask);
|
|
1963
|
+
return verifyPins(projectRoot, pins);
|
|
1964
|
+
}
|
|
1965
|
+
function repoDiscover(projectRoot, taskId) {
|
|
1966
|
+
const resolvedTask = taskId || currentTaskId(projectRoot);
|
|
1967
|
+
if (!resolvedTask) {
|
|
1968
|
+
return {};
|
|
1969
|
+
}
|
|
1970
|
+
const explicit = explicitPins(projectRoot, resolvedTask);
|
|
1971
|
+
if (Object.keys(explicit).length > 0) {
|
|
1972
|
+
return explicit;
|
|
1973
|
+
}
|
|
1974
|
+
return discoverPins(projectRoot, resolvedTask);
|
|
1975
|
+
}
|
|
1976
|
+
function repoBaseline(projectRoot, refresh = false) {
|
|
1977
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
1978
|
+
if (!refresh && existsSync14(paths.baseRepoPinsPath)) {
|
|
1979
|
+
const baseline = readJsonFile(paths.baseRepoPinsPath, { recorded_at: nowIso3(), repos: {} });
|
|
1980
|
+
return baseline.repos || {};
|
|
1981
|
+
}
|
|
1982
|
+
const id = primaryManagedRepoId();
|
|
1983
|
+
if (!id) {
|
|
1984
|
+
return persistBaselinePins(projectRoot, {});
|
|
1985
|
+
}
|
|
1986
|
+
const synced = syncManagedRepo(projectRoot, id);
|
|
1987
|
+
const alias = primaryManagedRepoAlias() ?? id;
|
|
1988
|
+
return persistBaselinePins(projectRoot, { [alias]: synced.headCommit });
|
|
1989
|
+
}
|
|
1990
|
+
function resetBaseline(projectRoot, keepTaskStatus) {
|
|
1991
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
1992
|
+
const label = primaryManagedRepoAlias() ?? "monorepo";
|
|
1993
|
+
resetRepoToOriginMain(projectRoot, paths.monorepoRoot, label);
|
|
1994
|
+
deleteBranches(projectRoot, paths.monorepoRoot, label, "rig/*");
|
|
1995
|
+
deleteBranches(projectRoot, projectRoot, "project-rig", "codex/*");
|
|
1996
|
+
repoBaseline(projectRoot, true);
|
|
1997
|
+
console.log("OK: refreshed baseline repo pins");
|
|
1998
|
+
if (!keepTaskStatus) {
|
|
1999
|
+
reopenClosedTasks(projectRoot);
|
|
2000
|
+
} else {
|
|
2001
|
+
console.log("INFO: task status unchanged (--keep-task-status)");
|
|
2002
|
+
}
|
|
2003
|
+
clearDirectory(paths.artifactsDir, ".gitkeep");
|
|
2004
|
+
clearDirectory(paths.logsDir, ".gitkeep");
|
|
2005
|
+
for (const stateFile of ["hook_trips.log", "task-repo-commits.json", "current-task-id.txt"]) {
|
|
2006
|
+
rmSync4(resolve16(paths.stateDir, stateFile), { force: true });
|
|
2007
|
+
}
|
|
2008
|
+
console.log("OK: cleared harness artifacts/logs/runtime state");
|
|
2009
|
+
console.log("");
|
|
2010
|
+
console.log("Baseline reset complete.");
|
|
2011
|
+
}
|
|
2012
|
+
function ensureMonorepoReady(projectRoot) {
|
|
2013
|
+
const id = primaryManagedRepoId();
|
|
2014
|
+
if (!id) {
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
const synced = syncManagedRepo(projectRoot, id);
|
|
2018
|
+
const sha = synced.headCommit.slice(0, 7);
|
|
2019
|
+
console.log(`Monorepo ready: ${synced.layout.alias}@${sha}`);
|
|
2020
|
+
return synced;
|
|
2021
|
+
}
|
|
2022
|
+
function persistBaselinePins(projectRoot, repos) {
|
|
2023
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
2024
|
+
mkdirSync8(resolve16(paths.baseRepoPinsPath, ".."), { recursive: true });
|
|
2025
|
+
writeFileSync6(paths.baseRepoPinsPath, `${JSON.stringify({ recorded_at: nowIso3(), repos }, null, 2)}
|
|
2026
|
+
`, "utf-8");
|
|
2027
|
+
return repos;
|
|
2028
|
+
}
|
|
2029
|
+
function resetRepoToOriginMain(projectRoot, repoPath, label) {
|
|
2030
|
+
if (!existsSync14(resolve16(repoPath, ".git"))) {
|
|
2031
|
+
console.log(`WARN: ${label} repo missing at ${repoPath}, skipping reset.`);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
runGitCapture(["git", "-C", repoPath, "fetch", "origin", "main"], projectRoot);
|
|
2035
|
+
const hasMain = runGitCapture(["git", "-C", repoPath, "show-ref", "--verify", "--quiet", "refs/heads/main"], projectRoot).exitCode === 0;
|
|
2036
|
+
if (hasMain) {
|
|
2037
|
+
runGitCapture(["git", "-C", repoPath, "checkout", "main"], projectRoot);
|
|
2038
|
+
} else {
|
|
2039
|
+
runGitCapture(["git", "-C", repoPath, "checkout", "-B", "main", "origin/main"], projectRoot);
|
|
2040
|
+
}
|
|
2041
|
+
const resetResult = runGitCapture(["git", "-C", repoPath, "reset", "--hard", "origin/main"], projectRoot);
|
|
2042
|
+
if (resetResult.exitCode !== 0) {
|
|
2043
|
+
throw new Error(`Failed to reset ${label} to origin/main:
|
|
2044
|
+
${resetResult.stderr || resetResult.stdout}`);
|
|
2045
|
+
}
|
|
2046
|
+
runGitCapture(["git", "-C", repoPath, "clean", "-fd"], projectRoot);
|
|
2047
|
+
const head = runGitCapture(["git", "-C", repoPath, "rev-parse", "--short", "HEAD"], projectRoot).stdout.trim();
|
|
2048
|
+
console.log(`OK: ${label} reset to origin/main (${head})`);
|
|
2049
|
+
}
|
|
2050
|
+
function deleteBranches(projectRoot, repoPath, label, pattern) {
|
|
2051
|
+
if (!existsSync14(resolve16(repoPath, ".git"))) {
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
const listed = runGitCapture(["git", "-C", repoPath, "for-each-ref", `refs/heads/${pattern}`, "--format=%(refname:short)"], projectRoot).stdout.split(/\r?\n/).filter(Boolean);
|
|
2055
|
+
let deleted = 0;
|
|
2056
|
+
for (const branch of listed) {
|
|
2057
|
+
runGitCapture(["git", "-C", repoPath, "branch", "-D", branch], projectRoot);
|
|
2058
|
+
deleted += 1;
|
|
2059
|
+
}
|
|
2060
|
+
console.log(`OK: ${label} deleted ${deleted} branch(es) matching ${pattern}`);
|
|
2061
|
+
}
|
|
2062
|
+
function reopenClosedTasks(projectRoot) {
|
|
2063
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
2064
|
+
const issuesPath = resolve16(paths.monorepoRoot, ".beads/issues.jsonl");
|
|
2065
|
+
if (!existsSync14(issuesPath)) {
|
|
2066
|
+
console.log("WARN: .beads/issues.jsonl not found, skipping task reopen.");
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const content = readFileSync8(issuesPath, "utf-8");
|
|
2070
|
+
const closed = [];
|
|
2071
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2072
|
+
const trimmed = line.trim();
|
|
2073
|
+
if (!trimmed) {
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
try {
|
|
2077
|
+
const issue = JSON.parse(trimmed);
|
|
2078
|
+
const normalizedStatus = normalizeTaskLifecycleStatus(issue.status);
|
|
2079
|
+
if (issue.issue_type === "task" && issue.id && (normalizedStatus === "completed" || normalizedStatus === "cancelled" || normalizedStatus === "blocked")) {
|
|
2080
|
+
closed.push(issue.id);
|
|
2081
|
+
}
|
|
2082
|
+
} catch {}
|
|
2083
|
+
}
|
|
2084
|
+
if (closed.length === 0) {
|
|
2085
|
+
console.log("OK: no terminal tasks to reopen");
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
for (const id of closed) {
|
|
2089
|
+
const update = runCapture(["br", "--no-db", "update", id, "--status", "open"], paths.monorepoRoot);
|
|
2090
|
+
if (update.exitCode === 0) {
|
|
2091
|
+
console.log(`OK: reopened task ${id}`);
|
|
2092
|
+
} else {
|
|
2093
|
+
console.log(`WARN: failed to reopen task ${id}`);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
function clearDirectory(dir, keepName) {
|
|
2098
|
+
if (!existsSync14(dir)) {
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
for (const entry of readdirSync3(dir)) {
|
|
2102
|
+
if (entry === keepName) {
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
rmSync4(resolve16(dir, entry), { recursive: true, force: true });
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function resolvedPins(projectRoot, taskId) {
|
|
2109
|
+
const explicit = explicitPins(projectRoot, taskId);
|
|
2110
|
+
if (Object.keys(explicit).length > 0) {
|
|
2111
|
+
return explicit;
|
|
2112
|
+
}
|
|
2113
|
+
return discoverPins(projectRoot, taskId);
|
|
2114
|
+
}
|
|
2115
|
+
function explicitPins(projectRoot, taskId) {
|
|
2116
|
+
const repoPins2 = readRepoDiscoveryTaskConfig(projectRoot)[taskId]?.repo_pins || {};
|
|
2117
|
+
const normalized = {};
|
|
2118
|
+
const validAliases = new Set(listManagedRepoEntries().map((e) => e.alias));
|
|
2119
|
+
for (const [key, value] of Object.entries(repoPins2)) {
|
|
2120
|
+
if (!value) {
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
if (validAliases.size > 0 && !validAliases.has(key)) {
|
|
2124
|
+
throw new Error(`Unsupported repo pin key for ${taskId}: ${key}. Known aliases: ${[...validAliases].join(", ") || "(none registered)"}`);
|
|
2125
|
+
}
|
|
2126
|
+
const existing = normalized[key];
|
|
2127
|
+
if (existing && existing !== value) {
|
|
2128
|
+
throw new Error(`Conflicting explicit repo pins for ${key}: ${existing} vs ${value}`);
|
|
2129
|
+
}
|
|
2130
|
+
normalized[key] = value;
|
|
2131
|
+
}
|
|
2132
|
+
return normalized;
|
|
2133
|
+
}
|
|
2134
|
+
function taskDependencies(projectRoot, taskId) {
|
|
2135
|
+
return taskDependencyIds(projectRoot, taskId);
|
|
2136
|
+
}
|
|
2137
|
+
function discoverPins(projectRoot, taskId) {
|
|
2138
|
+
const deps = taskDependencies(projectRoot, taskId);
|
|
2139
|
+
if (deps.length === 0) {
|
|
2140
|
+
return repoBaseline(projectRoot, true);
|
|
2141
|
+
}
|
|
2142
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
2143
|
+
const state = readJsonFile(paths.taskRepoCommitsPath, {});
|
|
2144
|
+
const pinKeys = listManagedRepoEntries().map((e) => e.alias);
|
|
2145
|
+
if (pinKeys.length === 0) {
|
|
2146
|
+
return {};
|
|
2147
|
+
}
|
|
2148
|
+
const discovered = {};
|
|
2149
|
+
for (const key of pinKeys) {
|
|
2150
|
+
const commits = new Set;
|
|
2151
|
+
for (const dep of deps) {
|
|
2152
|
+
const fromState = state[dep]?.repos?.[key];
|
|
2153
|
+
if (fromState) {
|
|
2154
|
+
commits.add(fromState);
|
|
2155
|
+
}
|
|
2156
|
+
const fromArtifact = readPinFromArtifact(projectRoot, dep, key);
|
|
2157
|
+
if (fromArtifact) {
|
|
2158
|
+
commits.add(fromArtifact);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
if (commits.size > 1) {
|
|
2162
|
+
const values = [...commits].join(`
|
|
2163
|
+
- `);
|
|
2164
|
+
throw new Error(`Conflicting discovered pins for ${key} from dependency graph of ${taskId}:
|
|
2165
|
+
- ${values}`);
|
|
2166
|
+
}
|
|
2167
|
+
if (commits.size === 1) {
|
|
2168
|
+
discovered[key] = [...commits][0];
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return discovered;
|
|
2172
|
+
}
|
|
2173
|
+
function readRepoDiscoveryTaskConfig(projectRoot) {
|
|
2174
|
+
try {
|
|
2175
|
+
return readSourceTaskConfig(projectRoot);
|
|
2176
|
+
} catch {
|
|
2177
|
+
return readTaskConfig(projectRoot);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
function resolveRepoDiscoveryPaths(projectRoot) {
|
|
2181
|
+
const monorepoRoot = resolveMonorepoRepoLayout(projectRoot).checkoutRoot;
|
|
2182
|
+
const explicitHostProjectRoot = (process.env.RIG_HOST_PROJECT_ROOT || "").trim();
|
|
2183
|
+
const normalizedProjectRoot = resolve16(projectRoot);
|
|
2184
|
+
const hostProjectRoot = explicitHostProjectRoot && shouldUseHostProjectStateRoot(normalizedProjectRoot) ? explicitHostProjectRoot : normalizedProjectRoot;
|
|
2185
|
+
const stateDir = resolve16(hostProjectRoot, ".rig", "state");
|
|
2186
|
+
return {
|
|
2187
|
+
monorepoRoot,
|
|
2188
|
+
taskRepoCommitsPath: resolve16(stateDir, "task-repo-commits.json"),
|
|
2189
|
+
baseRepoPinsPath: resolve16(stateDir, "base-repo-pins.json")
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
function shouldUseHostProjectStateRoot(projectRoot) {
|
|
2193
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
2194
|
+
if (runtimeWorkspace && resolve16(runtimeWorkspace) === projectRoot) {
|
|
2195
|
+
return true;
|
|
2196
|
+
}
|
|
2197
|
+
return basename6(dirname9(projectRoot)) === ".worktrees";
|
|
2198
|
+
}
|
|
2199
|
+
function readPinFromArtifact(projectRoot, depTask, repoKey) {
|
|
2200
|
+
const snapshot = resolve16(artifactDirForId(projectRoot, depTask), "git-state.txt");
|
|
2201
|
+
if (!existsSync14(snapshot)) {
|
|
2202
|
+
return "";
|
|
2203
|
+
}
|
|
2204
|
+
const content = readFileSync8(snapshot, "utf-8");
|
|
2205
|
+
const chunk = content.split(/\r?\n/);
|
|
2206
|
+
let inSection = false;
|
|
2207
|
+
for (const line of chunk) {
|
|
2208
|
+
if (line.startsWith("## ")) {
|
|
2209
|
+
inSection = line.startsWith(`## ${repoKey}`);
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
if (!inSection) {
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
if (line.startsWith("head:")) {
|
|
2216
|
+
return line.replace(/^head:\s*/, "").trim();
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return "";
|
|
2220
|
+
}
|
|
2221
|
+
function repoPath(projectRoot, key) {
|
|
2222
|
+
const managed = resolveManagedRepoLayoutByAlias(projectRoot, key);
|
|
2223
|
+
if (managed) {
|
|
2224
|
+
return managed.checkoutRoot;
|
|
2225
|
+
}
|
|
2226
|
+
return key.startsWith("/") ? key : resolve16(projectRoot, key);
|
|
2227
|
+
}
|
|
2228
|
+
function applyPins(projectRoot, pins) {
|
|
2229
|
+
for (const [key, commit] of Object.entries(pins)) {
|
|
2230
|
+
const path = repoPath(projectRoot, key);
|
|
2231
|
+
if (!existsSync14(resolve16(path, ".git"))) {
|
|
2232
|
+
throw new Error(`Repo for pin not available: ${key} (${path})`);
|
|
2233
|
+
}
|
|
2234
|
+
let hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
|
|
2235
|
+
if (!hasCommit) {
|
|
2236
|
+
runGitCapture(["git", "-C", path, "fetch", "--all", "--tags", "--prune"], projectRoot);
|
|
2237
|
+
hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
|
|
2238
|
+
}
|
|
2239
|
+
if (!hasCommit) {
|
|
2240
|
+
throw new Error(`Commit not found for ${key}: ${commit}`);
|
|
2241
|
+
}
|
|
2242
|
+
const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
|
|
2243
|
+
if (current === commit) {
|
|
2244
|
+
console.log(`Repo pin: ${key} already at ${commit}`);
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
const branch = runGitCapture(["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"], projectRoot).stdout.trim();
|
|
2248
|
+
const checkout = branch.startsWith("rig/") ? runGitCapture(["git", "-C", path, "reset", "--hard", commit], projectRoot) : runGitCapture(["git", "-C", path, "checkout", "--detach", commit], projectRoot);
|
|
2249
|
+
if (checkout.exitCode !== 0) {
|
|
2250
|
+
throw new Error(`Failed to apply repo pin ${key} -> ${commit}:
|
|
2251
|
+
${checkout.stderr || checkout.stdout}`);
|
|
2252
|
+
}
|
|
2253
|
+
console.log(`Repo pin: ${key} -> ${commit}`);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
function verifyPins(projectRoot, pins) {
|
|
2257
|
+
let ok = true;
|
|
2258
|
+
for (const [key, expected] of Object.entries(pins)) {
|
|
2259
|
+
const path = repoPath(projectRoot, key);
|
|
2260
|
+
if (!existsSync14(resolve16(path, ".git"))) {
|
|
2261
|
+
console.error(`ERROR: repo missing during pin verification: ${key}`);
|
|
2262
|
+
ok = false;
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
|
|
2266
|
+
if (current === expected) {
|
|
2267
|
+
console.log(`Repo verify: ${key} at ${current}`);
|
|
2268
|
+
} else {
|
|
2269
|
+
console.error(`ERROR: repo pin mismatch for ${key}: expected ${expected}, got ${current}`);
|
|
2270
|
+
ok = false;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
return ok;
|
|
2274
|
+
}
|
|
2275
|
+
function runGitCapture(command, projectRoot) {
|
|
2276
|
+
return runCapture(command, projectRoot, resolveRuntimeGitEnv());
|
|
2277
|
+
}
|
|
2278
|
+
function resolveRuntimeGitEnv() {
|
|
2279
|
+
if (process.env.GIT_SSH_COMMAND?.trim()) {
|
|
2280
|
+
return {
|
|
2281
|
+
HOME: process.env.HOME ?? "",
|
|
2282
|
+
GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
const runtimeRoot = process.env.RIG_RUNTIME_HOME?.trim() || (process.env.RIG_RUNTIME_CONTEXT_FILE?.trim() ? resolve16(process.env.RIG_RUNTIME_CONTEXT_FILE, "..") : inferRuntimeRootFromWorkspace(process.cwd()));
|
|
2286
|
+
const runtimeHome = runtimeRoot ? resolve16(runtimeRoot, "home") : process.env.HOME?.trim() || "";
|
|
2287
|
+
if (!runtimeHome) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
const knownHostsPath = resolve16(runtimeHome, ".ssh", "known_hosts");
|
|
2291
|
+
if (!existsSync14(knownHostsPath)) {
|
|
2292
|
+
return { HOME: runtimeHome };
|
|
2293
|
+
}
|
|
2294
|
+
const agentSshKey = resolve16(runtimeHome, ".ssh", "rig-agent-key");
|
|
2295
|
+
const sshParts = [
|
|
2296
|
+
"ssh",
|
|
2297
|
+
`-o UserKnownHostsFile="${knownHostsPath}"`,
|
|
2298
|
+
"-o StrictHostKeyChecking=yes",
|
|
2299
|
+
"-F /dev/null"
|
|
2300
|
+
];
|
|
2301
|
+
if (existsSync14(agentSshKey)) {
|
|
2302
|
+
sshParts.splice(1, 0, `-i "${agentSshKey}"`, "-o IdentitiesOnly=yes");
|
|
2303
|
+
}
|
|
2304
|
+
return {
|
|
2305
|
+
HOME: runtimeHome,
|
|
2306
|
+
GIT_SSH_COMMAND: sshParts.join(" ")
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
function inferRuntimeRootFromWorkspace(cwd) {
|
|
2310
|
+
const contextPath = findRuntimeContextFile2(cwd);
|
|
2311
|
+
if (!contextPath || !existsSync14(contextPath)) {
|
|
2312
|
+
return "";
|
|
2313
|
+
}
|
|
2314
|
+
try {
|
|
2315
|
+
loadRuntimeContext(contextPath);
|
|
2316
|
+
return resolve16(contextPath, "..");
|
|
2317
|
+
} catch {
|
|
2318
|
+
return "";
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
function findRuntimeContextFile2(startPath) {
|
|
2322
|
+
let current = resolve16(startPath);
|
|
2323
|
+
while (true) {
|
|
2324
|
+
const candidate = resolve16(current, "runtime-context.json");
|
|
2325
|
+
if (existsSync14(candidate)) {
|
|
2326
|
+
return candidate;
|
|
2327
|
+
}
|
|
2328
|
+
const parent = resolve16(current, "..");
|
|
2329
|
+
if (parent === current) {
|
|
2330
|
+
return "";
|
|
2331
|
+
}
|
|
2332
|
+
current = parent;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
export {
|
|
2336
|
+
resetBaseline,
|
|
2337
|
+
repoVerify,
|
|
2338
|
+
repoPins,
|
|
2339
|
+
repoEnsure,
|
|
2340
|
+
repoDiscover,
|
|
2341
|
+
repoBaseline
|
|
2342
|
+
};
|