@h-rig/harness-plugin 0.0.6-alpha.186
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 +1 -0
- package/dist/bin/rig-agent-dispatch.d.ts +2 -0
- package/dist/bin/rig-agent-dispatch.js +826 -0
- package/dist/src/agent-command.d.ts +3 -0
- package/dist/src/agent-command.js +233 -0
- package/dist/src/agent-harness/agent-mode.d.ts +1 -0
- package/dist/src/agent-harness/agent-mode.js +48 -0
- package/dist/src/agent-harness/agent-wrapper.d.ts +60 -0
- package/dist/src/agent-harness/agent-wrapper.js +842 -0
- package/dist/src/agent-harness/controlled-bash.d.ts +3 -0
- package/dist/src/agent-harness/controlled-bash.js +45 -0
- package/dist/src/agent-harness/git-ops.d.ts +2 -0
- package/dist/src/agent-harness/git-ops.js +27 -0
- package/dist/src/agent-harness/repo-ops.d.ts +14 -0
- package/dist/src/agent-harness/repo-ops.js +37 -0
- package/dist/src/agent-harness/rig-agent-entrypoint.d.ts +1 -0
- package/dist/src/agent-harness/rig-agent-entrypoint.js +1362 -0
- package/dist/src/agent-harness/rig-agent.d.ts +2 -0
- package/dist/src/agent-harness/rig-agent.js +1324 -0
- package/dist/src/agent-harness/runtime-snapshot-config.d.ts +2 -0
- package/dist/src/agent-harness/runtime-snapshot-config.js +25 -0
- package/dist/src/agent-harness/task-data.d.ts +27 -0
- package/dist/src/agent-harness/task-data.js +286 -0
- package/dist/src/agent-harness/task-ops.d.ts +10 -0
- package/dist/src/agent-harness/task-ops.js +352 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4525 -0
- package/dist/src/model.d.ts +80 -0
- package/dist/src/model.js +64 -0
- package/dist/src/pi-command.d.ts +3 -0
- package/dist/src/pi-command.js +154 -0
- package/dist/src/pi-settings-materializer.d.ts +10 -0
- package/dist/src/pi-settings-materializer.js +52 -0
- package/dist/src/plugin.d.ts +24 -0
- package/dist/src/plugin.js +4486 -0
- package/dist/src/profile-command.d.ts +3 -0
- package/dist/src/profile-command.js +79 -0
- package/dist/src/profile-state.d.ts +7 -0
- package/dist/src/profile-state.js +39 -0
- package/dist/src/rig-task-run-skill.d.ts +8 -0
- package/dist/src/rig-task-run-skill.js +39 -0
- package/dist/src/runtime-instructions.d.ts +4 -0
- package/dist/src/runtime-instructions.js +26 -0
- package/dist/src/runtime-secrets.d.ts +6 -0
- package/dist/src/runtime-secrets.js +58 -0
- package/dist/src/service.d.ts +14 -0
- package/dist/src/service.js +33 -0
- package/dist/src/session-asset-materializer-service.d.ts +13 -0
- package/dist/src/session-asset-materializer-service.js +164 -0
- package/dist/src/session-hook-materializer-service.d.ts +34 -0
- package/dist/src/session-hook-materializer-service.js +142 -0
- package/dist/src/skill-materializer.d.ts +25 -0
- package/dist/src/skill-materializer.js +86 -0
- package/dist/src/tooling/browser-tool-entrypoint.d.ts +2 -0
- package/dist/src/tooling/browser-tool-entrypoint.js +125 -0
- package/dist/src/tooling/browser-tools.d.ts +3 -0
- package/dist/src/tooling/browser-tools.js +27 -0
- package/dist/src/tooling/claude-router-binary.d.ts +3 -0
- package/dist/src/tooling/claude-router-binary.js +62 -0
- package/dist/src/tooling/claude-router.d.ts +22 -0
- package/dist/src/tooling/claude-router.js +524 -0
- package/dist/src/tooling/embedded-native-assets.d.ts +7 -0
- package/dist/src/tooling/embedded-native-assets.js +6 -0
- package/dist/src/tooling/file-tools.d.ts +5 -0
- package/dist/src/tooling/file-tools.js +192 -0
- package/dist/src/tooling/gateway.d.ts +4 -0
- package/dist/src/tooling/gateway.js +400 -0
- package/dist/src/tooling/shell-tools.d.ts +5 -0
- package/dist/src/tooling/shell-tools.js +185 -0
- package/native/darwin-arm64/rig-shell +0 -0
- package/native/darwin-arm64/rig-shell.build-manifest.json +4 -0
- package/native/darwin-arm64/rig-tools +0 -0
- package/native/darwin-arm64/rig-tools.build-manifest.json +4 -0
- package/native/darwin-x64/rig-shell +0 -0
- package/native/darwin-x64/rig-tools +0 -0
- package/native/linux-arm64/rig-shell +0 -0
- package/native/linux-arm64/rig-tools +0 -0
- package/native/linux-x64/rig-shell +0 -0
- package/native/linux-x64/rig-tools +0 -0
- package/native/win32-x64/rig-shell.exe +0 -0
- package/native/win32-x64/rig-tools.exe +0 -0
- package/package.json +101 -0
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/harness-plugin/src/agent-harness/rig-agent.ts
|
|
5
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
6
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
7
|
+
import { readBuildConfig } from "@rig/core/build-time-config";
|
|
8
|
+
|
|
9
|
+
// packages/harness-plugin/src/agent-harness/controlled-bash.ts
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
import { resolveRigLayout } from "@rig/core/layout";
|
|
13
|
+
function controlledBashCandidates(projectRoot) {
|
|
14
|
+
const layout = resolveRigLayout(projectRoot);
|
|
15
|
+
const candidates = [
|
|
16
|
+
process.env.RIG_CONTROLLED_BASH_BIN?.trim() || "",
|
|
17
|
+
resolve(layout.binDir, "controlled-bash"),
|
|
18
|
+
Bun.which("controlled-bash") || ""
|
|
19
|
+
];
|
|
20
|
+
return candidates.filter((candidate) => Boolean(candidate));
|
|
21
|
+
}
|
|
22
|
+
function resolveControlledBash(projectRoot) {
|
|
23
|
+
for (const candidate of controlledBashCandidates(projectRoot)) {
|
|
24
|
+
if (existsSync(candidate))
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
async function runControlledBash(args, options) {
|
|
30
|
+
const projectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || process.env.PROJECT_RIG_ROOT?.trim() || process.env.RIG_TASK_WORKSPACE?.trim() || process.cwd() || options.projectRootFallbackDir;
|
|
31
|
+
const controlled = resolveControlledBash(projectRoot);
|
|
32
|
+
if (!controlled) {
|
|
33
|
+
console.error("[rig-agent] controlled-bash entrypoint unavailable; refusing to run an unguarded shell.");
|
|
34
|
+
return 126;
|
|
35
|
+
}
|
|
36
|
+
const command = [controlled, ...args];
|
|
37
|
+
const child = Bun.spawn(command, {
|
|
38
|
+
stdin: "inherit",
|
|
39
|
+
stdout: "inherit",
|
|
40
|
+
stderr: "inherit",
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
env: {
|
|
43
|
+
...process.env,
|
|
44
|
+
PROJECT_RIG_ROOT: projectRoot,
|
|
45
|
+
RIG_BASH_ACTIVE: "1"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return await child.exited;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// packages/harness-plugin/src/agent-harness/rig-agent.ts
|
|
52
|
+
import { LIFECYCLE_VERIFICATION_SERVICE as LIFECYCLE_VERIFICATION_SERVICE2, MEMORY } from "@rig/contracts";
|
|
53
|
+
import { defineCapability as defineCapability4, defineServiceCapability as defineServiceCapability2 } from "@rig/core/capability";
|
|
54
|
+
import { buildProjectPluginHost, loadCapabilityForRoot as loadCapabilityForRoot2 } from "@rig/core/capability-loaders";
|
|
55
|
+
import { resolveCheckoutRoot as resolveMonorepoRoot } from "@rig/core/checkout-root";
|
|
56
|
+
|
|
57
|
+
// packages/harness-plugin/src/agent-harness/agent-mode.ts
|
|
58
|
+
function looksLikeShellInvocation(args, mode = process.env.RIG_AGENT_MODE) {
|
|
59
|
+
if (mode === "shell") {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (mode === "agent") {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (args.length === 0) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const first = args[0];
|
|
69
|
+
if (!first) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const bashLikeFlags = new Set([
|
|
73
|
+
"-c",
|
|
74
|
+
"-lc",
|
|
75
|
+
"-l",
|
|
76
|
+
"-i",
|
|
77
|
+
"-s",
|
|
78
|
+
"--login",
|
|
79
|
+
"--noprofile",
|
|
80
|
+
"--norc",
|
|
81
|
+
"--posix",
|
|
82
|
+
"--rcfile",
|
|
83
|
+
"--init-file",
|
|
84
|
+
"--restricted",
|
|
85
|
+
"--debug",
|
|
86
|
+
"--debugger",
|
|
87
|
+
"--verbose",
|
|
88
|
+
"--wordexp",
|
|
89
|
+
"--dump-strings",
|
|
90
|
+
"--dump-po-strings",
|
|
91
|
+
"--help"
|
|
92
|
+
]);
|
|
93
|
+
if (bashLikeFlags.has(first)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (!first.startsWith("-")) {
|
|
97
|
+
return first.endsWith(".sh");
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// packages/harness-plugin/src/agent-harness/rig-agent.ts
|
|
103
|
+
import { browserEnvFromContext, loadRuntimeContext, loadRuntimeContextFromEnv } from "@rig/core/runtime-context";
|
|
104
|
+
|
|
105
|
+
// packages/harness-plugin/src/runtime-secrets.ts
|
|
106
|
+
import { loadDotEnvValues, resolveRuntimeConfigValues } from "@rig/core/baked-secrets";
|
|
107
|
+
var BAKED_RUNTIME_SECRETS = {
|
|
108
|
+
ANTHROPIC_API_KEY: typeof RIG_BAKED_ANTHROPIC_API_KEY !== "undefined" ? RIG_BAKED_ANTHROPIC_API_KEY : "",
|
|
109
|
+
OPENAI_API_KEY: typeof RIG_BAKED_OPENAI_API_KEY !== "undefined" ? RIG_BAKED_OPENAI_API_KEY : "",
|
|
110
|
+
OPENROUTER_API_KEY: typeof RIG_BAKED_OPENROUTER_API_KEY !== "undefined" ? RIG_BAKED_OPENROUTER_API_KEY : "",
|
|
111
|
+
AI_REVIEW_MODE: typeof RIG_BAKED_AI_REVIEW_MODE !== "undefined" ? RIG_BAKED_AI_REVIEW_MODE : "",
|
|
112
|
+
AI_REVIEW_PROVIDER: typeof RIG_BAKED_AI_REVIEW_PROVIDER !== "undefined" ? RIG_BAKED_AI_REVIEW_PROVIDER : "",
|
|
113
|
+
GH_TOKEN: typeof RIG_BAKED_GITHUB_TOKEN !== "undefined" ? RIG_BAKED_GITHUB_TOKEN : "",
|
|
114
|
+
GITHUB_TOKEN: typeof RIG_BAKED_GITHUB_TOKEN !== "undefined" ? RIG_BAKED_GITHUB_TOKEN : "",
|
|
115
|
+
GITHUB_SSH_KEY: typeof RIG_BAKED_GITHUB_SSH_KEY !== "undefined" ? RIG_BAKED_GITHUB_SSH_KEY : "",
|
|
116
|
+
AWS_ACCESS_KEY_ID: typeof RIG_BAKED_AWS_ACCESS_KEY_ID !== "undefined" ? RIG_BAKED_AWS_ACCESS_KEY_ID : "",
|
|
117
|
+
AWS_SECRET_ACCESS_KEY: typeof RIG_BAKED_AWS_SECRET_ACCESS_KEY !== "undefined" ? RIG_BAKED_AWS_SECRET_ACCESS_KEY : "",
|
|
118
|
+
AWS_REGION: typeof RIG_BAKED_AWS_REGION !== "undefined" ? RIG_BAKED_AWS_REGION : "",
|
|
119
|
+
LINEAR_API_KEY: typeof RIG_BAKED_LINEAR_API_KEY !== "undefined" ? RIG_BAKED_LINEAR_API_KEY : "",
|
|
120
|
+
LINEAR_WEBHOOK_SECRET: typeof RIG_BAKED_LINEAR_WEBHOOK_SECRET !== "undefined" ? RIG_BAKED_LINEAR_WEBHOOK_SECRET : ""
|
|
121
|
+
};
|
|
122
|
+
var RUNTIME_SECRET_KEYS = Object.keys(BAKED_RUNTIME_SECRETS);
|
|
123
|
+
function resolveRuntimeSecrets(env, baked = BAKED_RUNTIME_SECRETS) {
|
|
124
|
+
return resolveRuntimeConfigValues(env, RUNTIME_SECRET_KEYS, baked);
|
|
125
|
+
}
|
|
126
|
+
function loadDotEnvSecrets(projectRoot, env = process.env) {
|
|
127
|
+
return loadDotEnvValues(projectRoot, RUNTIME_SECRET_KEYS, env);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// packages/harness-plugin/src/agent-harness/rig-agent.ts
|
|
131
|
+
import { RuntimeEventBus } from "@rig/core/runtime-events";
|
|
132
|
+
|
|
133
|
+
// packages/harness-plugin/src/agent-harness/git-ops.ts
|
|
134
|
+
import { LIFECYCLE_GIT_AGENT } from "@rig/contracts";
|
|
135
|
+
import { defineCapability } from "@rig/core/capability";
|
|
136
|
+
import { buildPluginHostContext } from "@rig/core/plugin-host-context";
|
|
137
|
+
import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
138
|
+
var LifecycleGitAgentCap = defineCapability(LIFECYCLE_GIT_AGENT);
|
|
139
|
+
var hostContextByRoot = new Map;
|
|
140
|
+
async function ensureHostContext(projectRoot) {
|
|
141
|
+
let cached = hostContextByRoot.get(projectRoot);
|
|
142
|
+
if (!cached) {
|
|
143
|
+
cached = buildPluginHostContext(projectRoot);
|
|
144
|
+
hostContextByRoot.set(projectRoot, cached);
|
|
145
|
+
}
|
|
146
|
+
await cached;
|
|
147
|
+
}
|
|
148
|
+
async function loadLifecycleGit(projectRoot) {
|
|
149
|
+
await ensureHostContext(projectRoot);
|
|
150
|
+
const git = await loadCapabilityForRoot(projectRoot, LifecycleGitAgentCap);
|
|
151
|
+
if (!git) {
|
|
152
|
+
throw new Error("lifecycle git capability unavailable: load @rig/lifecycle-plugin (default bundle) before running rig-agent git commands.");
|
|
153
|
+
}
|
|
154
|
+
return git;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// packages/harness-plugin/src/profile-state.ts
|
|
158
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
159
|
+
import { resolve as resolve2 } from "path";
|
|
160
|
+
import { resolveHarnessPaths } from "@rig/core/harness-paths";
|
|
161
|
+
function harnessStateJsonPath(projectRoot, filename) {
|
|
162
|
+
return resolve2(resolveHarnessPaths(projectRoot).stateDir, filename);
|
|
163
|
+
}
|
|
164
|
+
function readHarnessStateJson(projectRoot, filename) {
|
|
165
|
+
const path = harnessStateJsonPath(projectRoot, filename);
|
|
166
|
+
if (!existsSync2(path)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
171
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function writeHarnessStateJson(projectRoot, filename, value) {
|
|
177
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
178
|
+
mkdirSync(paths.stateDir, { recursive: true });
|
|
179
|
+
const path = resolve2(paths.stateDir, filename);
|
|
180
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
181
|
+
`, "utf-8");
|
|
182
|
+
return path;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// packages/harness-plugin/src/agent-harness/repo-ops.ts
|
|
186
|
+
import {
|
|
187
|
+
REPO_OPERATIONS_CAPABILITY
|
|
188
|
+
} from "@rig/contracts";
|
|
189
|
+
import { defineCapability as defineCapability2 } from "@rig/core/capability";
|
|
190
|
+
import { requireInstalledCapability } from "@rig/core/capability-loaders";
|
|
191
|
+
var RepoOperationsCap = defineCapability2(REPO_OPERATIONS_CAPABILITY);
|
|
192
|
+
function repoOperations() {
|
|
193
|
+
return requireInstalledCapability(RepoOperationsCap, "repo operations capability unavailable: load @rig/repos-plugin before using provider harness repo operations.");
|
|
194
|
+
}
|
|
195
|
+
function repoEnsure(projectRoot, taskId) {
|
|
196
|
+
repoOperations().repoEnsure(projectRoot, taskId);
|
|
197
|
+
}
|
|
198
|
+
function repoPins(projectRoot, taskId) {
|
|
199
|
+
return repoOperations().repoPins(projectRoot, taskId);
|
|
200
|
+
}
|
|
201
|
+
function repoVerify(projectRoot, taskId) {
|
|
202
|
+
return repoOperations().repoVerify(projectRoot, taskId);
|
|
203
|
+
}
|
|
204
|
+
function repoDiscover(projectRoot, taskId) {
|
|
205
|
+
return repoOperations().repoDiscover(projectRoot, taskId);
|
|
206
|
+
}
|
|
207
|
+
function repoBaseline(projectRoot, refresh = false) {
|
|
208
|
+
return repoOperations().repoBaseline(projectRoot, refresh);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// packages/harness-plugin/src/agent-harness/task-ops.ts
|
|
212
|
+
import { LIFECYCLE_VERIFICATION_SERVICE } from "@rig/contracts";
|
|
213
|
+
import { defineServiceCapability } from "@rig/core/capability";
|
|
214
|
+
import { buildPluginHostContext as buildPluginHostContext2 } from "@rig/core/plugin-host-context";
|
|
215
|
+
|
|
216
|
+
// packages/harness-plugin/src/agent-harness/task-data.ts
|
|
217
|
+
import { basename } from "path";
|
|
218
|
+
import {
|
|
219
|
+
REPO_CHANGE_SET,
|
|
220
|
+
TASK_ARTIFACTS,
|
|
221
|
+
TASK_STATE_STORE,
|
|
222
|
+
TASK_SOURCE_CONFIG_PROJECTION
|
|
223
|
+
} from "@rig/contracts";
|
|
224
|
+
import { defineCapability as defineCapability3 } from "@rig/core/capability";
|
|
225
|
+
import { requireInstalledCapability as requireInstalledCapability2 } from "@rig/core/capability-loaders";
|
|
226
|
+
var RepoChangeSetCap = defineCapability3(REPO_CHANGE_SET);
|
|
227
|
+
var TaskArtifactsCap = defineCapability3(TASK_ARTIFACTS);
|
|
228
|
+
var TaskStateStoreCap = defineCapability3(TASK_STATE_STORE);
|
|
229
|
+
var TaskSourceConfigProjectionCap = defineCapability3(TASK_SOURCE_CONFIG_PROJECTION);
|
|
230
|
+
function stateStore() {
|
|
231
|
+
return requireInstalledCapability2(TaskStateStoreCap, "task state store capability unavailable: load @rig/tasks-plugin (default bundle) before running the provider agent harness.");
|
|
232
|
+
}
|
|
233
|
+
function artifacts() {
|
|
234
|
+
return requireInstalledCapability2(TaskArtifactsCap, "task artifacts capability unavailable: load @rig/tasks-plugin (default bundle) before running the provider agent harness.");
|
|
235
|
+
}
|
|
236
|
+
function taskSourceConfigProjection() {
|
|
237
|
+
return requireInstalledCapability2(TaskSourceConfigProjectionCap, "task source config projection capability unavailable: load @rig/tasks-plugin (default bundle) before reading validation descriptions.");
|
|
238
|
+
}
|
|
239
|
+
function repoChangeSet() {
|
|
240
|
+
return requireInstalledCapability2(RepoChangeSetCap, "repo change-set capability unavailable: load @rig/repos-plugin (default bundle) before reading task file changes.");
|
|
241
|
+
}
|
|
242
|
+
function currentTaskId(projectRoot) {
|
|
243
|
+
return stateStore().readCurrentTaskId(projectRoot) ?? "";
|
|
244
|
+
}
|
|
245
|
+
function readTaskConfig(projectRoot) {
|
|
246
|
+
return stateStore().readTaskConfig(projectRoot);
|
|
247
|
+
}
|
|
248
|
+
function readSourceTaskConfig(projectRoot) {
|
|
249
|
+
return stateStore().readSourceTaskConfig(projectRoot);
|
|
250
|
+
}
|
|
251
|
+
function readValidationDescriptions(projectRoot) {
|
|
252
|
+
return taskSourceConfigProjection().readSourceValidationDescriptions(projectRoot);
|
|
253
|
+
}
|
|
254
|
+
function lookupTask(_projectRoot, input) {
|
|
255
|
+
return input;
|
|
256
|
+
}
|
|
257
|
+
function artifactDirForId(projectRoot, id) {
|
|
258
|
+
return artifacts().artifactDir({ projectRoot, taskId: id });
|
|
259
|
+
}
|
|
260
|
+
function readStringList(candidate) {
|
|
261
|
+
return Array.isArray(candidate) ? candidate.filter((entry) => typeof entry === "string") : [];
|
|
262
|
+
}
|
|
263
|
+
function firstStringList(...candidates) {
|
|
264
|
+
for (const candidate of candidates) {
|
|
265
|
+
const values = readStringList(candidate);
|
|
266
|
+
if (values.length > 0)
|
|
267
|
+
return values;
|
|
268
|
+
}
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
function unique(values) {
|
|
272
|
+
return Array.from(new Set(values));
|
|
273
|
+
}
|
|
274
|
+
function readTaskEntry(projectRoot, taskId) {
|
|
275
|
+
try {
|
|
276
|
+
return readTaskConfig(projectRoot)[taskId] ?? readSourceTaskConfig(projectRoot)[taskId] ?? null;
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function taskDependencyIds(projectRoot, taskId) {
|
|
282
|
+
const entry = readTaskEntry(projectRoot, taskId);
|
|
283
|
+
if (!entry)
|
|
284
|
+
return [];
|
|
285
|
+
const record = entry;
|
|
286
|
+
return unique(firstStringList(record.deps, record.dependencies, record.validation_deps, record.validationDeps)).filter((id) => id !== taskId);
|
|
287
|
+
}
|
|
288
|
+
function taskScopePatterns(projectRoot, taskId) {
|
|
289
|
+
const entry = readTaskEntry(projectRoot, taskId);
|
|
290
|
+
return entry ? readStringList(entry.scope) : [];
|
|
291
|
+
}
|
|
292
|
+
function syncRepoChangeSetPaths(result, operation) {
|
|
293
|
+
if (result && typeof result.then === "function") {
|
|
294
|
+
throw new Error(`repo change-set ${operation} returned a Promise; lifecycle task adapter requires a synchronous provider.`);
|
|
295
|
+
}
|
|
296
|
+
return unique(result.map((entry) => entry.path).filter(Boolean));
|
|
297
|
+
}
|
|
298
|
+
function changedFilesForTask(projectRoot, taskId, scoped) {
|
|
299
|
+
return syncRepoChangeSetPaths(repoChangeSet().changedFiles({ projectRoot, selector: { kind: "task", taskId, scoped } }), "changedFiles");
|
|
300
|
+
}
|
|
301
|
+
function pendingFilesForTask(projectRoot, taskId, scoped) {
|
|
302
|
+
return syncRepoChangeSetPaths(repoChangeSet().pendingFiles({ projectRoot, selector: { kind: "task", taskId, scoped } }), "pendingFiles");
|
|
303
|
+
}
|
|
304
|
+
function taskLookup(projectRoot, id) {
|
|
305
|
+
const result = lookupTask(projectRoot, id);
|
|
306
|
+
if (!result)
|
|
307
|
+
throw new Error(`Not found: ${id}`);
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
function taskConfigEntryToRecord(id, entry, source) {
|
|
311
|
+
const record = entry;
|
|
312
|
+
const deps = firstStringList(record.deps, record.dependencies, record.validation_deps, record.validationDeps);
|
|
313
|
+
return {
|
|
314
|
+
id,
|
|
315
|
+
deps,
|
|
316
|
+
status: typeof entry.status === "string" ? entry.status : "open",
|
|
317
|
+
source,
|
|
318
|
+
...typeof entry.title === "string" ? { title: entry.title } : {},
|
|
319
|
+
...typeof entry.description === "string" ? { description: entry.description } : {},
|
|
320
|
+
...typeof entry.acceptance_criteria === "string" ? { acceptanceCriteria: entry.acceptance_criteria } : {},
|
|
321
|
+
...Array.isArray(entry.scope) ? { scope: entry.scope } : {},
|
|
322
|
+
...Array.isArray(entry.validation) ? { validation: entry.validation } : {},
|
|
323
|
+
...typeof entry.role === "string" ? { role: entry.role } : {},
|
|
324
|
+
...entry.browser ? { browser: entry.browser } : {},
|
|
325
|
+
...entry.repo_pins ? { repo_pins: entry.repo_pins } : {},
|
|
326
|
+
...entry.criticality ? { criticality: entry.criticality } : {},
|
|
327
|
+
...typeof entry.queue_weight === "number" ? { queue_weight: entry.queue_weight } : {},
|
|
328
|
+
...entry.creates_repo !== undefined ? { creates_repo: entry.creates_repo } : {},
|
|
329
|
+
...entry.auto_synced !== undefined ? { auto_synced: entry.auto_synced } : {}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async function taskInfo(projectRoot, taskId, deprecatedRuntimeProviderOverride) {
|
|
333
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
334
|
+
if (!activeTask)
|
|
335
|
+
throw new Error("No active task.");
|
|
336
|
+
const entry = readTaskEntry(projectRoot, activeTask);
|
|
337
|
+
if (!entry)
|
|
338
|
+
throw new Error(`Not found: ${activeTask}`);
|
|
339
|
+
process.stdout.write(`${JSON.stringify(taskConfigEntryToRecord(activeTask, entry, "task-config"), null, 2)}
|
|
340
|
+
`);
|
|
341
|
+
}
|
|
342
|
+
async function taskDeps(projectRoot, taskId) {
|
|
343
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
344
|
+
if (!activeTask)
|
|
345
|
+
throw new Error("No active task.");
|
|
346
|
+
const deps = taskDependencyIds(projectRoot, activeTask);
|
|
347
|
+
if (deps.length > 0)
|
|
348
|
+
process.stdout.write(`${deps.join(`
|
|
349
|
+
`)}
|
|
350
|
+
`);
|
|
351
|
+
}
|
|
352
|
+
function taskStatus(projectRoot) {
|
|
353
|
+
let tasks = [];
|
|
354
|
+
try {
|
|
355
|
+
tasks = Object.entries(readSourceTaskConfig(projectRoot)).map(([id, entry]) => taskConfigEntryToRecord(id, entry, "source-task-config"));
|
|
356
|
+
} catch {
|
|
357
|
+
tasks = Object.entries(readTaskConfig(projectRoot)).map(([id, entry]) => taskConfigEntryToRecord(id, entry, "legacy-task-config"));
|
|
358
|
+
}
|
|
359
|
+
const counts = tasks.reduce((acc, task) => {
|
|
360
|
+
const key = typeof task.status === "string" && task.status.length > 0 ? task.status : "open";
|
|
361
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
362
|
+
return acc;
|
|
363
|
+
}, {});
|
|
364
|
+
console.log(`=== Project Rig Progress ===
|
|
365
|
+
`);
|
|
366
|
+
console.log(`Total tasks: ${tasks.length}`);
|
|
367
|
+
for (const status of Object.keys(counts).sort())
|
|
368
|
+
console.log(` ${status}: ${counts[status]}`);
|
|
369
|
+
}
|
|
370
|
+
async function taskScope(projectRoot, expandFiles, taskId) {
|
|
371
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
372
|
+
if (!activeTask)
|
|
373
|
+
throw new Error("No active task.");
|
|
374
|
+
const scopes = taskScopePatterns(projectRoot, activeTask);
|
|
375
|
+
if (scopes.length > 0)
|
|
376
|
+
process.stdout.write(`${scopes.join(`
|
|
377
|
+
`)}
|
|
378
|
+
`);
|
|
379
|
+
}
|
|
380
|
+
async function taskRecord(projectRoot, type, text, taskId) {
|
|
381
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
382
|
+
if (!activeTask)
|
|
383
|
+
throw new Error("No active task.");
|
|
384
|
+
const timestamp = new Date().toISOString();
|
|
385
|
+
if (type === "decision") {
|
|
386
|
+
const artifactService = artifacts();
|
|
387
|
+
const filename = "decision-log.md";
|
|
388
|
+
const existingNames = await artifactNames(artifactService, { projectRoot, taskId: activeTask });
|
|
389
|
+
const existing = existingNames.has(filename) ? (await artifactService.readArtifact({
|
|
390
|
+
projectRoot,
|
|
391
|
+
taskId: activeTask,
|
|
392
|
+
filename,
|
|
393
|
+
maxBytes: Number.MAX_SAFE_INTEGER
|
|
394
|
+
})).contents : "";
|
|
395
|
+
const result2 = await artifactService.writeArtifact({
|
|
396
|
+
projectRoot,
|
|
397
|
+
taskId: activeTask,
|
|
398
|
+
filename,
|
|
399
|
+
content: `${existing}
|
|
400
|
+
### ${timestamp}
|
|
401
|
+
|
|
402
|
+
${text}
|
|
403
|
+
|
|
404
|
+
`
|
|
405
|
+
});
|
|
406
|
+
console.log(`Decision recorded for ${activeTask}.`);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const result = stateStore().recordFailedApproach({ projectRoot, taskId: activeTask, text, timestamp });
|
|
410
|
+
console.log(result.message);
|
|
411
|
+
}
|
|
412
|
+
function taskArtifactDir(projectRoot, taskId) {
|
|
413
|
+
return artifacts().artifactDir({ projectRoot, ...taskId !== undefined ? { taskId } : {} });
|
|
414
|
+
}
|
|
415
|
+
async function taskArtifactWrite(projectRoot, filename, content, taskId) {
|
|
416
|
+
const result = await artifacts().writeArtifact({
|
|
417
|
+
projectRoot,
|
|
418
|
+
filename,
|
|
419
|
+
content,
|
|
420
|
+
...taskId !== undefined ? { taskId } : {}
|
|
421
|
+
});
|
|
422
|
+
console.log(`Wrote: ${result.path}`);
|
|
423
|
+
}
|
|
424
|
+
async function taskArtifactRead(projectRoot, filename, options = {}) {
|
|
425
|
+
return artifacts().readArtifact({
|
|
426
|
+
projectRoot,
|
|
427
|
+
filename,
|
|
428
|
+
...options.taskId !== undefined ? { taskId: options.taskId } : {},
|
|
429
|
+
...options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
async function taskArtifacts(projectRoot, taskId) {
|
|
433
|
+
const activeTask = taskId || currentTaskId(projectRoot);
|
|
434
|
+
if (!activeTask)
|
|
435
|
+
throw new Error("No active task.");
|
|
436
|
+
const artifactService = artifacts();
|
|
437
|
+
const ref = { projectRoot, taskId: activeTask };
|
|
438
|
+
const artifactDir = artifactService.artifactDir(ref);
|
|
439
|
+
await artifactService.writeArtifact({
|
|
440
|
+
...ref,
|
|
441
|
+
filename: "changed-files.txt",
|
|
442
|
+
content: `${changedFilesForTask(projectRoot, activeTask, true).join(`
|
|
443
|
+
`)}
|
|
444
|
+
`
|
|
445
|
+
});
|
|
446
|
+
const existingNames = await artifactNames(artifactService, ref);
|
|
447
|
+
if (!existingNames.has("task-result.json")) {
|
|
448
|
+
await artifactService.writeArtifact({
|
|
449
|
+
...ref,
|
|
450
|
+
filename: "task-result.json",
|
|
451
|
+
content: `${JSON.stringify({ task_id: activeTask, status: "completed", summary: "TODO: Write a one-line summary of what you did", completed_at: new Date().toISOString() }, null, 2)}
|
|
452
|
+
`
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
for (const [filename, content] of [
|
|
456
|
+
["decision-log.md", `# Decision Log: ${activeTask}
|
|
457
|
+
|
|
458
|
+
Record key decisions here using: rig-agent record decision "..."
|
|
459
|
+
`],
|
|
460
|
+
["next-actions.md", `# Next Actions: ${activeTask}
|
|
461
|
+
|
|
462
|
+
- TODO: Replace this scaffold with real content before completion
|
|
463
|
+
`]
|
|
464
|
+
]) {
|
|
465
|
+
if (!existingNames.has(filename)) {
|
|
466
|
+
await artifactService.writeArtifact({ ...ref, filename, content });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
console.log(`Artifacts at: ${artifactDir}/`);
|
|
470
|
+
}
|
|
471
|
+
async function artifactNames(artifactService, ref) {
|
|
472
|
+
const entries = await artifactService.listArtifacts(ref);
|
|
473
|
+
return new Set(entries.filter((entry) => entry.kind === "file").map((entry) => basename(entry.path)));
|
|
474
|
+
}
|
|
475
|
+
function taskData() {
|
|
476
|
+
return {
|
|
477
|
+
currentTaskId,
|
|
478
|
+
readTaskConfig,
|
|
479
|
+
readSourceTaskConfig,
|
|
480
|
+
readValidationDescriptions,
|
|
481
|
+
lookupTask,
|
|
482
|
+
taskLookup,
|
|
483
|
+
artifactDirForId,
|
|
484
|
+
taskInfo,
|
|
485
|
+
taskDeps,
|
|
486
|
+
taskStatus,
|
|
487
|
+
taskScope,
|
|
488
|
+
taskRecord,
|
|
489
|
+
taskArtifacts,
|
|
490
|
+
taskArtifactDir,
|
|
491
|
+
taskArtifactWrite,
|
|
492
|
+
taskArtifactRead,
|
|
493
|
+
taskDependencyIds,
|
|
494
|
+
changedFilesForTask,
|
|
495
|
+
pendingFilesForTask
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// packages/harness-plugin/src/agent-harness/task-ops.ts
|
|
500
|
+
var LifecycleVerificationServiceCap = defineServiceCapability(LIFECYCLE_VERIFICATION_SERVICE);
|
|
501
|
+
var hostContextByRoot2 = new Map;
|
|
502
|
+
async function ensureHostContext2(projectRoot) {
|
|
503
|
+
let cached = hostContextByRoot2.get(projectRoot);
|
|
504
|
+
if (!cached) {
|
|
505
|
+
cached = buildPluginHostContext2(projectRoot);
|
|
506
|
+
hostContextByRoot2.set(projectRoot, cached);
|
|
507
|
+
}
|
|
508
|
+
return cached;
|
|
509
|
+
}
|
|
510
|
+
function taskArtifactDir2(projectRoot, taskId) {
|
|
511
|
+
return taskData().taskArtifactDir(projectRoot, taskId);
|
|
512
|
+
}
|
|
513
|
+
function taskArtifacts2(projectRoot, taskId) {
|
|
514
|
+
return taskData().taskArtifacts(projectRoot, taskId);
|
|
515
|
+
}
|
|
516
|
+
function taskArtifactWrite2(projectRoot, filename, content, taskId) {
|
|
517
|
+
return taskData().taskArtifactWrite(projectRoot, filename, content, taskId);
|
|
518
|
+
}
|
|
519
|
+
function taskDeps2(projectRoot, taskId) {
|
|
520
|
+
return taskData().taskDeps(projectRoot, taskId);
|
|
521
|
+
}
|
|
522
|
+
function taskInfo2(projectRoot, taskId) {
|
|
523
|
+
return taskData().taskInfo(projectRoot, taskId);
|
|
524
|
+
}
|
|
525
|
+
function taskLookup2(projectRoot, id) {
|
|
526
|
+
return taskData().taskLookup(projectRoot, id);
|
|
527
|
+
}
|
|
528
|
+
function taskRecord2(projectRoot, type, text, taskId) {
|
|
529
|
+
return taskData().taskRecord(projectRoot, type, text, taskId);
|
|
530
|
+
}
|
|
531
|
+
function taskScope2(projectRoot, expandFiles, taskId) {
|
|
532
|
+
return taskData().taskScope(projectRoot, expandFiles, taskId);
|
|
533
|
+
}
|
|
534
|
+
function taskStatus2(projectRoot) {
|
|
535
|
+
taskData().taskStatus(projectRoot);
|
|
536
|
+
}
|
|
537
|
+
async function taskValidate(projectRoot, taskId, validatorRegistry) {
|
|
538
|
+
const hostCtx = await ensureHostContext2(projectRoot);
|
|
539
|
+
const verification = hostCtx ? await LifecycleVerificationServiceCap.resolveService(hostCtx.pluginHost) : null;
|
|
540
|
+
const validate = verification ? verification.validate : null;
|
|
541
|
+
if (!validate) {
|
|
542
|
+
throw new Error("task validation capability unavailable: load @rig/lifecycle-plugin (default bundle) before running rig-agent validate.");
|
|
543
|
+
}
|
|
544
|
+
return validate({
|
|
545
|
+
projectRoot,
|
|
546
|
+
...taskId !== undefined ? { taskId } : {},
|
|
547
|
+
...validatorRegistry !== undefined ? { validatorRegistry } : hostCtx?.validatorRegistry !== undefined ? { validatorRegistry: hostCtx.validatorRegistry } : {}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// packages/harness-plugin/src/agent-harness/rig-agent.ts
|
|
552
|
+
var BUILD_CONFIG = readBuildConfig();
|
|
553
|
+
var BAKED_BINARY_PATH = BUILD_CONFIG.AGENT_BINARY_PATH ?? "";
|
|
554
|
+
var BAKED_MANIFEST_PATH = BUILD_CONFIG.AGENT_MANIFEST_PATH ?? "";
|
|
555
|
+
var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
|
|
556
|
+
var BAKED_RUNTIME_ID = BUILD_CONFIG.AGENT_RUNTIME_ID ?? "";
|
|
557
|
+
var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
|
|
558
|
+
var BAKED_SCOPE_HASH = BUILD_CONFIG.AGENT_SCOPE_HASH ?? "";
|
|
559
|
+
var BAKED_INFO_OUTPUT = BUILD_CONFIG.AGENT_INFO_OUTPUT ?? "";
|
|
560
|
+
var BAKED_DEPS_OUTPUT = BUILD_CONFIG.AGENT_DEPS_OUTPUT ?? "";
|
|
561
|
+
var BAKED_STATUS_OUTPUT = BUILD_CONFIG.AGENT_STATUS_OUTPUT ?? "";
|
|
562
|
+
var BAKED_GIT_BRANCH = BUILD_CONFIG.AGENT_GIT_BRANCH ?? "";
|
|
563
|
+
var BAKED_BASE_COMMIT = BUILD_CONFIG.AGENT_BASE_COMMIT ?? "";
|
|
564
|
+
if (BAKED_BINARY_PATH) {
|
|
565
|
+
const binaryDir = dirname(BAKED_BINARY_PATH);
|
|
566
|
+
const currentPath = process.env.PATH || "";
|
|
567
|
+
const pathEntries = currentPath.split(":").filter(Boolean);
|
|
568
|
+
if (!pathEntries.includes(binaryDir)) {
|
|
569
|
+
process.env.PATH = currentPath ? `${binaryDir}:${currentPath}` : binaryDir;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
var cachedContext;
|
|
573
|
+
var cachedEventBus;
|
|
574
|
+
function getContext() {
|
|
575
|
+
if (cachedContext !== undefined)
|
|
576
|
+
return cachedContext;
|
|
577
|
+
cachedContext = loadRuntimeContextFromEnv() ?? inferRuntimeContext();
|
|
578
|
+
return cachedContext;
|
|
579
|
+
}
|
|
580
|
+
function getEventBus() {
|
|
581
|
+
if (cachedEventBus !== undefined) {
|
|
582
|
+
return cachedEventBus ?? undefined;
|
|
583
|
+
}
|
|
584
|
+
const ctx = getContext();
|
|
585
|
+
if (!ctx) {
|
|
586
|
+
cachedEventBus = null;
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const runId = ctx.runtimeId || BAKED_RUNTIME_ID || "";
|
|
590
|
+
cachedEventBus = new RuntimeEventBus({
|
|
591
|
+
projectRoot: ctx.workspaceDir,
|
|
592
|
+
...runId ? { runId } : {}
|
|
593
|
+
});
|
|
594
|
+
return cachedEventBus;
|
|
595
|
+
}
|
|
596
|
+
function inferRuntimeContext() {
|
|
597
|
+
for (const candidate of runtimeContextCandidates()) {
|
|
598
|
+
if (!candidate || !existsSync3(candidate)) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
process.env.RIG_RUNTIME_CONTEXT_FILE = candidate;
|
|
603
|
+
return loadRuntimeContext(candidate);
|
|
604
|
+
} catch {}
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
function runtimeContextCandidates() {
|
|
609
|
+
const cwd = process.cwd();
|
|
610
|
+
const candidates = [
|
|
611
|
+
resolve3(cwd, "..", "runtime-context.json"),
|
|
612
|
+
resolve3(cwd, ".rig", "runtime-context.json")
|
|
613
|
+
];
|
|
614
|
+
const argv1 = process.argv[1]?.trim();
|
|
615
|
+
if (argv1) {
|
|
616
|
+
candidates.push(resolve3(argv1, "..", "..", "runtime-context.json"));
|
|
617
|
+
}
|
|
618
|
+
if (BAKED_BINARY_PATH) {
|
|
619
|
+
candidates.push(resolve3(BAKED_BINARY_PATH, "..", "..", "runtime-context.json"));
|
|
620
|
+
}
|
|
621
|
+
return Array.from(new Set(candidates));
|
|
622
|
+
}
|
|
623
|
+
function sha256Hex(input) {
|
|
624
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
625
|
+
hasher.update(input);
|
|
626
|
+
return hasher.digest("hex");
|
|
627
|
+
}
|
|
628
|
+
var GITHUB_KNOWN_HOSTS = [
|
|
629
|
+
"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
|
|
630
|
+
"github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=",
|
|
631
|
+
"github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk="
|
|
632
|
+
];
|
|
633
|
+
function ensureRuntimeKnownHosts(runtimeHome) {
|
|
634
|
+
const sshDir = resolve3(runtimeHome, ".ssh");
|
|
635
|
+
const knownHostsPath = resolve3(sshDir, "known_hosts");
|
|
636
|
+
if (!existsSync3(sshDir)) {
|
|
637
|
+
mkdirSync2(sshDir, { recursive: true });
|
|
638
|
+
}
|
|
639
|
+
const existing = existsSync3(knownHostsPath) ? readFileSync2(knownHostsPath, "utf-8") : "";
|
|
640
|
+
const existingLines = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
641
|
+
const missing = GITHUB_KNOWN_HOSTS.filter((line) => !existingLines.has(line));
|
|
642
|
+
if (missing.length === 0) {
|
|
643
|
+
return knownHostsPath;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
for (const line of missing) {
|
|
647
|
+
existingLines.add(line);
|
|
648
|
+
}
|
|
649
|
+
writeFileSync2(knownHostsPath, `${Array.from(existingLines).join(`
|
|
650
|
+
`)}
|
|
651
|
+
`, { mode: 420 });
|
|
652
|
+
} catch (err) {
|
|
653
|
+
const hint = existsSync3(knownHostsPath) ? "" : " \u2014 known_hosts is missing; git SSH operations may fail";
|
|
654
|
+
console.warn(`[rig-agent] Could not update ${knownHostsPath}: ${err instanceof Error ? err.message : String(err)}${hint}`);
|
|
655
|
+
}
|
|
656
|
+
return knownHostsPath;
|
|
657
|
+
}
|
|
658
|
+
function hydrateRuntimeProcessEnv(ctx) {
|
|
659
|
+
if (!ctx) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const contextFile = process.env.RIG_RUNTIME_CONTEXT_FILE?.trim() || inferRuntimeContextFileFromWorkspace(ctx.workspaceDir);
|
|
663
|
+
if (!contextFile) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
process.env.RIG_RUNTIME_CONTEXT_FILE = contextFile;
|
|
667
|
+
const runtimeRoot = dirname(resolve3(contextFile));
|
|
668
|
+
const runtimeHome = resolve3(runtimeRoot, "home");
|
|
669
|
+
const runtimeTmp = resolve3(runtimeRoot, "tmp");
|
|
670
|
+
const runtimeCache = resolve3(runtimeRoot, "cache");
|
|
671
|
+
const runtimeBin = resolve3(runtimeRoot, "bin");
|
|
672
|
+
const runtimeWorkspaceBin = resolve3(ctx.workspaceDir, ".rig", "bin");
|
|
673
|
+
const runtimeTools = resolve3(ctx.workspaceDir, "rig", "tools");
|
|
674
|
+
if (ctx.hostProjectRoot) {
|
|
675
|
+
process.env.PROJECT_RIG_ROOT = ctx.hostProjectRoot;
|
|
676
|
+
}
|
|
677
|
+
process.env.RIG_TASK_ID = ctx.taskId;
|
|
678
|
+
process.env.RIG_TASK_WORKSPACE = ctx.workspaceDir;
|
|
679
|
+
process.env.RIG_STATE_DIR = ctx.stateDir;
|
|
680
|
+
process.env.RIG_LOGS_DIR = ctx.logsDir;
|
|
681
|
+
process.env.RIG_SESSION_FILE = ctx.sessionFile;
|
|
682
|
+
process.env.RIG_RUNTIME_HOME = runtimeRoot;
|
|
683
|
+
if (!process.env.RIG_HOST_PROJECT_ROOT && ctx.hostProjectRoot) {
|
|
684
|
+
process.env.RIG_HOST_PROJECT_ROOT = ctx.hostProjectRoot;
|
|
685
|
+
}
|
|
686
|
+
for (const [key, value] of Object.entries(browserEnvFromContext(ctx.browser))) {
|
|
687
|
+
process.env[key] = value;
|
|
688
|
+
}
|
|
689
|
+
if (existsSync3(runtimeHome)) {
|
|
690
|
+
process.env.HOME = runtimeHome;
|
|
691
|
+
}
|
|
692
|
+
if (existsSync3(runtimeTmp)) {
|
|
693
|
+
process.env.TMPDIR = runtimeTmp;
|
|
694
|
+
}
|
|
695
|
+
if (existsSync3(runtimeCache)) {
|
|
696
|
+
process.env.XDG_CACHE_HOME = runtimeCache;
|
|
697
|
+
}
|
|
698
|
+
const workspaceSecrets = loadDotEnvSecrets(ctx.workspaceDir, process.env);
|
|
699
|
+
const hostWorkspaceSecrets = ctx.hostProjectRoot && ctx.hostProjectRoot !== ctx.workspaceDir ? loadDotEnvSecrets(ctx.hostProjectRoot, process.env) : {};
|
|
700
|
+
const resolvedSecrets = resolveRuntimeSecrets(process.env, {
|
|
701
|
+
...hostWorkspaceSecrets,
|
|
702
|
+
...workspaceSecrets
|
|
703
|
+
});
|
|
704
|
+
for (const [key, value] of Object.entries(resolvedSecrets)) {
|
|
705
|
+
if (value && !process.env[key]) {
|
|
706
|
+
process.env[key] = value;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (!process.env.GITHUB_TOKEN && process.env.GH_TOKEN) {
|
|
710
|
+
process.env.GITHUB_TOKEN = process.env.GH_TOKEN;
|
|
711
|
+
}
|
|
712
|
+
if (!process.env.GH_TOKEN && process.env.GITHUB_TOKEN) {
|
|
713
|
+
process.env.GH_TOKEN = process.env.GITHUB_TOKEN;
|
|
714
|
+
}
|
|
715
|
+
const currentPath = process.env.PATH || "";
|
|
716
|
+
const pathEntries = currentPath.split(":").filter(Boolean);
|
|
717
|
+
for (const entry of [runtimeBin, runtimeWorkspaceBin, runtimeTools, "/usr/bin", "/bin", "/usr/sbin", "/sbin"]) {
|
|
718
|
+
if (existsSync3(entry) && !pathEntries.includes(entry)) {
|
|
719
|
+
pathEntries.unshift(entry);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
process.env.PATH = pathEntries.join(":");
|
|
723
|
+
if (!process.env.RIG_GIT_BIN) {
|
|
724
|
+
const runtimeGit = resolve3(runtimeBin, "git");
|
|
725
|
+
process.env.RIG_GIT_BIN = existsSync3(runtimeGit) ? runtimeGit : Bun.which("git") || "/usr/bin/git";
|
|
726
|
+
}
|
|
727
|
+
const knownHosts = ensureRuntimeKnownHosts(runtimeHome);
|
|
728
|
+
const agentKey = resolve3(runtimeHome, ".ssh", "rig-agent-key");
|
|
729
|
+
const sshParts = [
|
|
730
|
+
"ssh",
|
|
731
|
+
`-o UserKnownHostsFile="${knownHosts}"`,
|
|
732
|
+
"-o StrictHostKeyChecking=yes",
|
|
733
|
+
"-F /dev/null"
|
|
734
|
+
];
|
|
735
|
+
if (existsSync3(agentKey)) {
|
|
736
|
+
sshParts.splice(1, 0, `-i "${agentKey}"`, "-o IdentitiesOnly=yes");
|
|
737
|
+
}
|
|
738
|
+
process.env.GIT_SSH_COMMAND = sshParts.join(" ");
|
|
739
|
+
}
|
|
740
|
+
function inferRuntimeContextFileFromWorkspace(workspaceDir) {
|
|
741
|
+
const candidate = resolve3(workspaceDir, "..", "runtime-context.json");
|
|
742
|
+
return existsSync3(candidate) ? candidate : "";
|
|
743
|
+
}
|
|
744
|
+
var AGENT_PROFILE_FILE = "agent-profile.json";
|
|
745
|
+
var REVIEW_PROFILE_FILE = "review-profile.json";
|
|
746
|
+
var DEFAULT_AGENT_PROFILE = { model: "pi", runtime: "pi", agent_plugin: "pi" };
|
|
747
|
+
var DEFAULT_REVIEW_MODE = parseEnvOrDefault(process.env.AI_REVIEW_MODE, ["off", "advisory", "required"], "advisory");
|
|
748
|
+
var DEFAULT_REVIEW_PROVIDER = parseEnvOrDefault(process.env.AI_REVIEW_PROVIDER, ["github"], "github");
|
|
749
|
+
function parseEnvOrDefault(value, allowed, fallback) {
|
|
750
|
+
if (!value) {
|
|
751
|
+
return fallback;
|
|
752
|
+
}
|
|
753
|
+
return allowed.includes(value) ? value : fallback;
|
|
754
|
+
}
|
|
755
|
+
function loadProfile(projectRoot) {
|
|
756
|
+
const parsed = readHarnessStateJson(projectRoot, AGENT_PROFILE_FILE);
|
|
757
|
+
return {
|
|
758
|
+
...DEFAULT_AGENT_PROFILE,
|
|
759
|
+
updated_at: typeof parsed?.updated_at === "string" ? parsed.updated_at : new Date().toISOString()
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function showProfile(projectRoot, compact = false) {
|
|
763
|
+
const profile = loadProfile(projectRoot);
|
|
764
|
+
if (compact) {
|
|
765
|
+
console.log(`model=${profile.model} runtime=${profile.runtime} plugin=${profile.agent_plugin}`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
console.log("Execution Profile:");
|
|
769
|
+
console.log(` model: ${profile.model}`);
|
|
770
|
+
console.log(` runtime: ${profile.runtime}`);
|
|
771
|
+
console.log(` plugin: ${profile.agent_plugin}`);
|
|
772
|
+
}
|
|
773
|
+
function setProfile(projectRoot, options) {
|
|
774
|
+
const invalid = [options.model, options.runtime, options.plugin, options.preset].filter((value) => typeof value === "string" && value.trim().length > 0 && value.trim() !== "pi");
|
|
775
|
+
if (invalid.length > 0) {
|
|
776
|
+
throw new Error("Only the Pi runtime profile is supported by this Rig substrate.");
|
|
777
|
+
}
|
|
778
|
+
writeHarnessStateJson(projectRoot, AGENT_PROFILE_FILE, {
|
|
779
|
+
...DEFAULT_AGENT_PROFILE,
|
|
780
|
+
updated_at: new Date().toISOString()
|
|
781
|
+
});
|
|
782
|
+
showProfile(projectRoot, false);
|
|
783
|
+
}
|
|
784
|
+
function loadReviewProfile(projectRoot) {
|
|
785
|
+
const parsed = readHarnessStateJson(projectRoot, REVIEW_PROFILE_FILE);
|
|
786
|
+
const mode = parsed?.mode;
|
|
787
|
+
const provider = parsed?.provider;
|
|
788
|
+
return {
|
|
789
|
+
mode: mode === "off" || mode === "advisory" || mode === "required" ? mode : DEFAULT_REVIEW_MODE,
|
|
790
|
+
provider: provider === "github" ? provider : DEFAULT_REVIEW_PROVIDER,
|
|
791
|
+
updated_at: typeof parsed?.updated_at === "string" ? parsed.updated_at : new Date().toISOString()
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function showReviewProfile(projectRoot) {
|
|
795
|
+
const profile = loadReviewProfile(projectRoot);
|
|
796
|
+
console.log("AI Review Profile:");
|
|
797
|
+
console.log(` mode: ${profile.mode}`);
|
|
798
|
+
console.log(` provider: ${profile.provider}`);
|
|
799
|
+
console.log(` file: ${harnessStateJsonPath(projectRoot, REVIEW_PROFILE_FILE)}`);
|
|
800
|
+
}
|
|
801
|
+
function setReviewProfile(projectRoot, mode, provider) {
|
|
802
|
+
if (mode !== "off" && mode !== "advisory" && mode !== "required") {
|
|
803
|
+
throw new Error(`Invalid mode: ${mode}. Use off|advisory|required.`);
|
|
804
|
+
}
|
|
805
|
+
const resolvedProvider = provider || DEFAULT_REVIEW_PROVIDER;
|
|
806
|
+
if (resolvedProvider !== "github") {
|
|
807
|
+
throw new Error(`Invalid provider: ${resolvedProvider}. Supported: github.`);
|
|
808
|
+
}
|
|
809
|
+
writeHarnessStateJson(projectRoot, REVIEW_PROFILE_FILE, {
|
|
810
|
+
mode,
|
|
811
|
+
provider: resolvedProvider,
|
|
812
|
+
updated_at: new Date().toISOString()
|
|
813
|
+
});
|
|
814
|
+
showReviewProfile(projectRoot);
|
|
815
|
+
}
|
|
816
|
+
async function main() {
|
|
817
|
+
const args = process.argv.slice(2);
|
|
818
|
+
hydrateRuntimeProcessEnv(getContext());
|
|
819
|
+
if (isVersionProbe(args)) {
|
|
820
|
+
await printVersion();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (args[0] === "internal") {
|
|
824
|
+
await runInternal(args.slice(1));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const ctx = getContext();
|
|
828
|
+
if (!BAKED_PROJECT_ROOT && !ctx) {
|
|
829
|
+
console.error("[rig-agent] Runtime binary is missing baked task context and no runtime context file found.");
|
|
830
|
+
console.error("[rig-agent] Set RIG_RUNTIME_CONTEXT_FILE or use the compiled host dispatch binary.");
|
|
831
|
+
process.exit(1);
|
|
832
|
+
}
|
|
833
|
+
await verifyRuntimeManifest();
|
|
834
|
+
const commandResult = await runAgentCommand(args);
|
|
835
|
+
if (commandResult !== undefined) {
|
|
836
|
+
process.exit(commandResult);
|
|
837
|
+
}
|
|
838
|
+
if (looksLikeShellInvocation(args)) {
|
|
839
|
+
const code = await runControlledBash(args, { projectRootFallbackDir: import.meta.dir });
|
|
840
|
+
process.exit(code);
|
|
841
|
+
}
|
|
842
|
+
console.error(`[rig-agent] Unknown command: ${args[0] || "(none)"}. Run 'rig-agent help' for usage.`);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
function isVersionProbe(args) {
|
|
846
|
+
if (args.length !== 1) {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
return args[0] === "--version" || args[0] === "-v" || args[0] === "version";
|
|
850
|
+
}
|
|
851
|
+
async function printVersion() {
|
|
852
|
+
const probe = await Bun.$`claude --version`.cwd(process.cwd()).env(process.env).quiet().nothrow();
|
|
853
|
+
if (probe.exitCode === 0) {
|
|
854
|
+
const output = probe.stdout.toString().trim();
|
|
855
|
+
if (output) {
|
|
856
|
+
console.log(output);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
console.log("rig-agent 1.0.0");
|
|
861
|
+
}
|
|
862
|
+
var TASK_COMMANDS = new Set([
|
|
863
|
+
"info",
|
|
864
|
+
"scope",
|
|
865
|
+
"deps",
|
|
866
|
+
"status",
|
|
867
|
+
"artifacts",
|
|
868
|
+
"artifact-dir",
|
|
869
|
+
"artifact-write",
|
|
870
|
+
"project-root",
|
|
871
|
+
"monorepo-root",
|
|
872
|
+
"validate",
|
|
873
|
+
"lookup",
|
|
874
|
+
"record",
|
|
875
|
+
"help",
|
|
876
|
+
"completion-verification",
|
|
877
|
+
"completition-verification",
|
|
878
|
+
"git",
|
|
879
|
+
"repo",
|
|
880
|
+
"repo-sync",
|
|
881
|
+
"profile",
|
|
882
|
+
"review",
|
|
883
|
+
"memory"
|
|
884
|
+
]);
|
|
885
|
+
async function runAgentCommand(args) {
|
|
886
|
+
const [command, ...rest] = args;
|
|
887
|
+
if (!command || !TASK_COMMANDS.has(command)) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const ctx = getContext();
|
|
891
|
+
const projectRoot = ctx?.workspaceDir || BAKED_PROJECT_ROOT || "";
|
|
892
|
+
const taskId = ctx?.taskId || BAKED_TASK_ID || "";
|
|
893
|
+
if (!projectRoot) {
|
|
894
|
+
console.error("[rig-agent] No project root baked in.");
|
|
895
|
+
return 1;
|
|
896
|
+
}
|
|
897
|
+
if (!taskId) {
|
|
898
|
+
console.error("[rig-agent] No task ID baked in.");
|
|
899
|
+
return 1;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
switch (command) {
|
|
903
|
+
case "info":
|
|
904
|
+
await taskInfo2(projectRoot, taskId);
|
|
905
|
+
return 0;
|
|
906
|
+
case "scope":
|
|
907
|
+
await taskScope2(projectRoot, rest.includes("--files"), taskId);
|
|
908
|
+
return 0;
|
|
909
|
+
case "deps":
|
|
910
|
+
await taskDeps2(projectRoot, taskId);
|
|
911
|
+
return 0;
|
|
912
|
+
case "status":
|
|
913
|
+
taskStatus2(projectRoot);
|
|
914
|
+
return 0;
|
|
915
|
+
case "artifacts":
|
|
916
|
+
await taskArtifacts2(projectRoot, taskId);
|
|
917
|
+
return 0;
|
|
918
|
+
case "artifact-dir":
|
|
919
|
+
console.log(taskArtifactDir2(projectRoot, taskId));
|
|
920
|
+
return 0;
|
|
921
|
+
case "project-root":
|
|
922
|
+
console.log(projectRoot);
|
|
923
|
+
return 0;
|
|
924
|
+
case "monorepo-root":
|
|
925
|
+
console.log(resolveMonorepoRoot(projectRoot));
|
|
926
|
+
return 0;
|
|
927
|
+
case "artifact-write": {
|
|
928
|
+
const filename = rest[0];
|
|
929
|
+
if (!filename) {
|
|
930
|
+
console.error(`Usage: rig-agent artifact-write <filename> [--file <path>]
|
|
931
|
+
` + ` Reads content from stdin (or --file), writes to the active task artifact dir.
|
|
932
|
+
` + " Example: echo '...' | rig-agent artifact-write collection-audit.md");
|
|
933
|
+
return 1;
|
|
934
|
+
}
|
|
935
|
+
let content;
|
|
936
|
+
const fileIdx = rest.indexOf("--file");
|
|
937
|
+
const inputFile = fileIdx !== -1 ? rest[fileIdx + 1] : undefined;
|
|
938
|
+
if (inputFile) {
|
|
939
|
+
content = readFileSync2(resolve3(projectRoot, inputFile), "utf-8");
|
|
940
|
+
} else {
|
|
941
|
+
const chunks = [];
|
|
942
|
+
for await (const chunk of process.stdin) {
|
|
943
|
+
chunks.push(Buffer.from(chunk));
|
|
944
|
+
}
|
|
945
|
+
content = Buffer.concat(chunks).toString("utf-8");
|
|
946
|
+
}
|
|
947
|
+
await taskArtifactWrite2(projectRoot, filename, content, taskId);
|
|
948
|
+
return 0;
|
|
949
|
+
}
|
|
950
|
+
case "validate": {
|
|
951
|
+
const passed = await taskValidate(projectRoot, taskId);
|
|
952
|
+
return passed ? 0 : 1;
|
|
953
|
+
}
|
|
954
|
+
case "completion-verification":
|
|
955
|
+
case "completition-verification":
|
|
956
|
+
return await runCompletionVerification(projectRoot);
|
|
957
|
+
case "lookup": {
|
|
958
|
+
if (rest.length !== 1) {
|
|
959
|
+
console.error("Usage: rig-agent lookup <task-id-or-source-id>");
|
|
960
|
+
return 1;
|
|
961
|
+
}
|
|
962
|
+
const lookupId = rest[0];
|
|
963
|
+
if (!lookupId) {
|
|
964
|
+
console.error("Usage: rig-agent lookup <task-id-or-source-id>");
|
|
965
|
+
return 1;
|
|
966
|
+
}
|
|
967
|
+
console.log(taskLookup2(projectRoot, lookupId));
|
|
968
|
+
return 0;
|
|
969
|
+
}
|
|
970
|
+
case "record": {
|
|
971
|
+
if (rest.length < 2) {
|
|
972
|
+
console.error("Usage: rig-agent record <decision|failure> <text>");
|
|
973
|
+
return 1;
|
|
974
|
+
}
|
|
975
|
+
const type = rest[0];
|
|
976
|
+
if (type !== "decision" && type !== "failure") {
|
|
977
|
+
console.error("Usage: rig-agent record <decision|failure> <text>");
|
|
978
|
+
return 1;
|
|
979
|
+
}
|
|
980
|
+
await taskRecord2(projectRoot, type, rest.slice(1).join(" "), taskId);
|
|
981
|
+
return 0;
|
|
982
|
+
}
|
|
983
|
+
case "memory": {
|
|
984
|
+
const memoryService = await loadCapabilityForRoot2(projectRoot, defineCapability4(MEMORY));
|
|
985
|
+
if (!memoryService) {
|
|
986
|
+
console.error("[rig-agent] Shared memory requires the @rig/memory-plugin plugin in rig.config.");
|
|
987
|
+
return 1;
|
|
988
|
+
}
|
|
989
|
+
const eventBus = getEventBus();
|
|
990
|
+
console.log(await memoryService.executeMemoryCommand({
|
|
991
|
+
projectRoot,
|
|
992
|
+
taskId,
|
|
993
|
+
runtimeContext: ctx,
|
|
994
|
+
args: rest,
|
|
995
|
+
...eventBus ? { eventBus } : {}
|
|
996
|
+
}));
|
|
997
|
+
return 0;
|
|
998
|
+
}
|
|
999
|
+
case "git": {
|
|
1000
|
+
const capabilityRoot = ctx?.hostProjectRoot || BAKED_PROJECT_ROOT || projectRoot;
|
|
1001
|
+
await runGitCommand(projectRoot, taskId, rest, capabilityRoot);
|
|
1002
|
+
return 0;
|
|
1003
|
+
}
|
|
1004
|
+
case "repo":
|
|
1005
|
+
case "repo-sync":
|
|
1006
|
+
runRepoSyncCommand(projectRoot, taskId, rest);
|
|
1007
|
+
return 0;
|
|
1008
|
+
case "profile":
|
|
1009
|
+
runProfileCommand(projectRoot, rest);
|
|
1010
|
+
return 0;
|
|
1011
|
+
case "review":
|
|
1012
|
+
runReviewCommand(projectRoot, rest);
|
|
1013
|
+
return 0;
|
|
1014
|
+
case "help":
|
|
1015
|
+
printAgentHelp();
|
|
1016
|
+
return 0;
|
|
1017
|
+
default:
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1022
|
+
return 1;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async function runGitCommand(projectRoot, bakedTaskId, args, capabilityRoot = projectRoot) {
|
|
1026
|
+
const [sub = "status", ...rest] = args;
|
|
1027
|
+
const task = takeOption(rest, "--task");
|
|
1028
|
+
const tid = task.value || bakedTaskId;
|
|
1029
|
+
const flags = task.rest;
|
|
1030
|
+
const git = await loadLifecycleGit(capabilityRoot);
|
|
1031
|
+
switch (sub) {
|
|
1032
|
+
case "status":
|
|
1033
|
+
git.gitStatus(projectRoot, tid);
|
|
1034
|
+
return;
|
|
1035
|
+
case "changed": {
|
|
1036
|
+
const scoped = flags.includes("--scoped");
|
|
1037
|
+
const files = git.gitChanged(projectRoot, tid, scoped);
|
|
1038
|
+
console.log(files.length > 0 ? files.join(`
|
|
1039
|
+
`) : "(none)");
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
case "preflight": {
|
|
1043
|
+
const strict = flags.includes("--strict");
|
|
1044
|
+
const ok = git.gitPreflight(projectRoot, tid, strict);
|
|
1045
|
+
if (!ok)
|
|
1046
|
+
throw new Error("Git preflight failed.");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
case "sync-branch":
|
|
1050
|
+
case "ensure-branch":
|
|
1051
|
+
git.gitSyncBranch(projectRoot, tid);
|
|
1052
|
+
return;
|
|
1053
|
+
case "commit": {
|
|
1054
|
+
const target = takeOption(flags, "--target");
|
|
1055
|
+
const message = takeOption(target.rest, "--message");
|
|
1056
|
+
const allowEmpty = message.rest.includes("--allow-empty");
|
|
1057
|
+
const scoped = git.shouldScopeGitCommit(message.rest, Boolean(tid));
|
|
1058
|
+
git.gitCommit({
|
|
1059
|
+
projectRoot,
|
|
1060
|
+
taskId: tid,
|
|
1061
|
+
target: target.value || "monorepo",
|
|
1062
|
+
allowEmpty,
|
|
1063
|
+
scoped,
|
|
1064
|
+
...message.value ? { message: message.value } : {}
|
|
1065
|
+
});
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
case "snapshot": {
|
|
1069
|
+
const output = takeOption(flags, "--output");
|
|
1070
|
+
const file = git.gitSnapshot(projectRoot, tid, output.value);
|
|
1071
|
+
console.log(`Snapshot written: ${file}`);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
case "open-pr": {
|
|
1075
|
+
const target = takeOption(flags, "--target");
|
|
1076
|
+
const reviewer = takeOption(target.rest, "--reviewer");
|
|
1077
|
+
const base = takeOption(reviewer.rest, "--base");
|
|
1078
|
+
const title = takeOption(base.rest, "--title");
|
|
1079
|
+
const body = takeOption(title.rest, "--body");
|
|
1080
|
+
const draft = body.rest.includes("--draft");
|
|
1081
|
+
const result = git.gitOpenPr({
|
|
1082
|
+
projectRoot,
|
|
1083
|
+
taskId: tid,
|
|
1084
|
+
...target.value ? { target: target.value } : {},
|
|
1085
|
+
...reviewer.value ? { reviewer: reviewer.value } : {},
|
|
1086
|
+
...base.value ? { base: base.value } : {},
|
|
1087
|
+
...title.value ? { title: title.value } : {},
|
|
1088
|
+
...body.value ? { body: body.value } : {},
|
|
1089
|
+
draft
|
|
1090
|
+
});
|
|
1091
|
+
console.log(`PR ready (${result.repoLabel}): ${result.url}`);
|
|
1092
|
+
console.log(`Reviewer assigned: ${result.reviewer} (${result.reviewerSource})`);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
default:
|
|
1096
|
+
throw new Error(`Unknown git subcommand: ${sub}. Try: status, changed, preflight, sync-branch, commit, snapshot, open-pr`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function runRepoSyncCommand(projectRoot, bakedTaskId, args) {
|
|
1100
|
+
const [sub = "ensure", ...rest] = args;
|
|
1101
|
+
const task = takeOption(rest, "--task");
|
|
1102
|
+
const tid = task.value || bakedTaskId;
|
|
1103
|
+
switch (sub) {
|
|
1104
|
+
case "sync":
|
|
1105
|
+
case "ensure":
|
|
1106
|
+
repoEnsure(projectRoot, tid);
|
|
1107
|
+
return;
|
|
1108
|
+
case "pins": {
|
|
1109
|
+
const pins = repoPins(projectRoot, tid);
|
|
1110
|
+
printPins(pins);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
case "verify": {
|
|
1114
|
+
const ok = repoVerify(projectRoot, tid);
|
|
1115
|
+
if (!ok)
|
|
1116
|
+
throw new Error("Repo pin verification failed.");
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
case "discover": {
|
|
1120
|
+
const pins = repoDiscover(projectRoot, tid);
|
|
1121
|
+
printPins(pins);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
case "baseline": {
|
|
1125
|
+
const refresh = task.rest.includes("--refresh");
|
|
1126
|
+
const pins = repoBaseline(projectRoot, refresh);
|
|
1127
|
+
printPins(pins);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
default:
|
|
1131
|
+
throw new Error(`Unknown repo subcommand: ${sub}. Try: sync, ensure, pins, verify, discover, baseline`);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function runProfileCommand(projectRoot, args) {
|
|
1135
|
+
const [sub = "show", ...rest] = args;
|
|
1136
|
+
if (sub === "show") {
|
|
1137
|
+
showProfile(projectRoot, rest.includes("--compact"));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (sub === "set") {
|
|
1141
|
+
const first = rest[0];
|
|
1142
|
+
if (first === "claude-code" || first === "codex-cli" || first === "codex-app-server") {
|
|
1143
|
+
setProfile(projectRoot, { preset: first });
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const model = takeOption(rest, "--model");
|
|
1147
|
+
const runtime = takeOption(model.rest, "--runtime");
|
|
1148
|
+
const plugin = takeOption(runtime.rest, "--plugin");
|
|
1149
|
+
setProfile(projectRoot, {
|
|
1150
|
+
...model.value ? { model: model.value } : {},
|
|
1151
|
+
...runtime.value ? { runtime: runtime.value } : {},
|
|
1152
|
+
...plugin.value ? { plugin: plugin.value } : {}
|
|
1153
|
+
});
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
throw new Error(`Unknown profile subcommand: ${sub}. Try: show, set`);
|
|
1157
|
+
}
|
|
1158
|
+
function runReviewCommand(projectRoot, args) {
|
|
1159
|
+
const [sub = "show", ...rest] = args;
|
|
1160
|
+
if (sub === "show") {
|
|
1161
|
+
showReviewProfile(projectRoot);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (sub === "set") {
|
|
1165
|
+
const mode = rest[0];
|
|
1166
|
+
if (!mode) {
|
|
1167
|
+
throw new Error("Usage: rig-agent review set <off|advisory|required> [--provider github]");
|
|
1168
|
+
}
|
|
1169
|
+
const provider = takeOption(rest.slice(1), "--provider");
|
|
1170
|
+
setReviewProfile(projectRoot, mode, provider.value);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
throw new Error(`Unknown review subcommand: ${sub}. Try: show, set`);
|
|
1174
|
+
}
|
|
1175
|
+
function takeOption(args, option) {
|
|
1176
|
+
const rest = [];
|
|
1177
|
+
let value;
|
|
1178
|
+
for (let i = 0;i < args.length; i += 1) {
|
|
1179
|
+
const current = args[i];
|
|
1180
|
+
if (current === option) {
|
|
1181
|
+
const next = args[i + 1];
|
|
1182
|
+
if (!next || next.startsWith("-")) {
|
|
1183
|
+
throw new Error(`Missing value for ${option}`);
|
|
1184
|
+
}
|
|
1185
|
+
value = next;
|
|
1186
|
+
i += 1;
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
if (current !== undefined) {
|
|
1190
|
+
rest.push(current);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return { value, rest };
|
|
1194
|
+
}
|
|
1195
|
+
function printPins(pins) {
|
|
1196
|
+
if (Object.keys(pins).length === 0) {
|
|
1197
|
+
console.log("(none)");
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
for (const [key, value] of Object.entries(pins)) {
|
|
1201
|
+
console.log(`${key} ${value}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function printAgentHelp() {
|
|
1205
|
+
console.log(`rig-agent \u2014 CLI for Project Rig agents
|
|
1206
|
+
|
|
1207
|
+
ORIENTATION:
|
|
1208
|
+
rig-agent info Your task, role, scope, deps \u2014 start here
|
|
1209
|
+
rig-agent scope [--files] Scope globs; --files expands to file list
|
|
1210
|
+
rig-agent deps Read dependency artifacts (decisions, next-actions)
|
|
1211
|
+
rig-agent lookup <id> Validate a task/source ID
|
|
1212
|
+
rig-agent status Epic/task progress overview
|
|
1213
|
+
|
|
1214
|
+
EXECUTION:
|
|
1215
|
+
rig-agent validate Run validation commands for your task
|
|
1216
|
+
rig-agent record decision "..." Record an architectural decision
|
|
1217
|
+
rig-agent record failure "..." Record a failed approach (cross-session only \u2014 not re-read in this session)
|
|
1218
|
+
rig-agent memory observe ... Promote a shared memory immediately
|
|
1219
|
+
rig-agent memory recall [query] Recall relevant shared memories
|
|
1220
|
+
rig-agent git status Task-aware git status
|
|
1221
|
+
rig-agent git changed Changed files (--scoped for scope-filtered)
|
|
1222
|
+
rig-agent git commit Task-aware commit (--target monorepo|project|both)
|
|
1223
|
+
rig-agent git snapshot Capture worktree state
|
|
1224
|
+
rig-agent git sync-branch Ensure task branch exists
|
|
1225
|
+
rig-agent git open-pr Create PR for task changes
|
|
1226
|
+
rig-agent repo sync Ensure monorepo checkout + task repo pins
|
|
1227
|
+
rig-agent profile show Show runtime profile
|
|
1228
|
+
rig-agent review show Show AI review gate settings
|
|
1229
|
+
|
|
1230
|
+
COMPLETION:
|
|
1231
|
+
rig-agent artifacts Scaffold completion artifacts (templates)
|
|
1232
|
+
rig-agent artifact-dir Print absolute artifact directory path
|
|
1233
|
+
rig-agent project-root Print absolute task worktree root
|
|
1234
|
+
rig-agent monorepo-root Print absolute monorepo root in the task worktree
|
|
1235
|
+
rig-agent artifact-write <filename> Write artifact from stdin
|
|
1236
|
+
rig-agent completion-verification Run final validation/review gate
|
|
1237
|
+
rig-agent completition-verification Alias for the same final gate
|
|
1238
|
+
|
|
1239
|
+
WORKFLOW:
|
|
1240
|
+
1. rig-agent info Understand your assignment
|
|
1241
|
+
2. rig-agent deps Read what prior tasks decided
|
|
1242
|
+
3. rig-agent repo sync Align monorepo checkout + repo pins
|
|
1243
|
+
4. (do your work)
|
|
1244
|
+
5. rig-agent record decision "chose X because Y"
|
|
1245
|
+
6. rig-agent validate Check your work
|
|
1246
|
+
7. rig-agent artifacts Scaffold completion artifacts
|
|
1247
|
+
8. rig-agent artifact-write collection-audit.md (write custom artifacts)
|
|
1248
|
+
9. rig-agent completion-verification
|
|
1249
|
+
`);
|
|
1250
|
+
}
|
|
1251
|
+
async function runCompletionVerification(projectRoot) {
|
|
1252
|
+
const host = await buildProjectPluginHost(projectRoot);
|
|
1253
|
+
const verification = host ? await defineServiceCapability2(LIFECYCLE_VERIFICATION_SERVICE2).resolveService(host) : null;
|
|
1254
|
+
if (!verification) {
|
|
1255
|
+
console.error("[rig-agent] Completion verification requires the @rig/lifecycle-plugin plugin in rig.config.");
|
|
1256
|
+
return 1;
|
|
1257
|
+
}
|
|
1258
|
+
const { ok } = await verification.verifyCompletion({ projectRoot });
|
|
1259
|
+
return ok ? 0 : 1;
|
|
1260
|
+
}
|
|
1261
|
+
async function runInternal(args) {
|
|
1262
|
+
const [command = "", ...rest] = args;
|
|
1263
|
+
if (command === "shell") {
|
|
1264
|
+
const code = await runControlledBash(rest, { projectRootFallbackDir: import.meta.dir });
|
|
1265
|
+
process.exit(code);
|
|
1266
|
+
}
|
|
1267
|
+
if (command === "manifest-verify") {
|
|
1268
|
+
await verifyRuntimeManifest();
|
|
1269
|
+
console.log("manifest: ok");
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
throw new Error("Unknown internal command. Use `rig-agent internal shell <bash-args...>` or `rig-agent internal manifest-verify`.");
|
|
1273
|
+
}
|
|
1274
|
+
async function verifyRuntimeManifest() {
|
|
1275
|
+
if (getContext()) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const manifestPath = BAKED_MANIFEST_PATH;
|
|
1279
|
+
if (!manifestPath) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (!existsSync3(manifestPath)) {
|
|
1283
|
+
throw new Error(`[rig-agent] Runtime manifest missing: ${manifestPath}`);
|
|
1284
|
+
}
|
|
1285
|
+
let manifest;
|
|
1286
|
+
try {
|
|
1287
|
+
manifest = await Bun.file(manifestPath).json();
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
throw new Error(`[rig-agent] Failed to parse runtime manifest at ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1290
|
+
}
|
|
1291
|
+
assertMatch("taskId", BAKED_TASK_ID, manifest.taskId);
|
|
1292
|
+
assertMatch("runtimeId", BAKED_RUNTIME_ID, manifest.runtimeId);
|
|
1293
|
+
assertMatch("scopeHash", BAKED_SCOPE_HASH, manifest.scopeHash);
|
|
1294
|
+
const manifestBinaryPath = manifest.binary?.path || BAKED_BINARY_PATH || process.execPath;
|
|
1295
|
+
const expectedHash = manifest.binary?.sha256 || "";
|
|
1296
|
+
if (!manifestBinaryPath || !expectedHash) {
|
|
1297
|
+
throw new Error(`[rig-agent] Runtime manifest is missing binary hash data (${manifestPath}).`);
|
|
1298
|
+
}
|
|
1299
|
+
if (!existsSync3(manifestBinaryPath)) {
|
|
1300
|
+
throw new Error(`[rig-agent] Runtime binary not found at ${manifestBinaryPath}.`);
|
|
1301
|
+
}
|
|
1302
|
+
const actualHash = sha256Hex(readFileSync2(resolve3(manifestBinaryPath)));
|
|
1303
|
+
if (actualHash !== expectedHash) {
|
|
1304
|
+
throw new Error(`[rig-agent] Runtime manifest mismatch for binary hash.
|
|
1305
|
+
` + `expected=${expectedHash}
|
|
1306
|
+
` + `actual=${actualHash}
|
|
1307
|
+
` + `path=${manifestBinaryPath}`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function assertMatch(name, expected, actual) {
|
|
1311
|
+
if (!expected && !actual) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (expected !== (actual || "")) {
|
|
1315
|
+
throw new Error(`[rig-agent] Runtime manifest mismatch for ${name}: expected='${expected}' actual='${actual || ""}'.`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
main().catch((error) => {
|
|
1319
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1320
|
+
process.exit(1);
|
|
1321
|
+
});
|
|
1322
|
+
export {
|
|
1323
|
+
looksLikeShellInvocation
|
|
1324
|
+
};
|