@garuda-agent/garuda 0.0.1 → 0.14.1
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/CHANGELOG.md +58 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +1040 -0
- package/SECURITY.md +32 -0
- package/dist/anthropic-NQDBCHJU.js +327 -0
- package/dist/chunk-35GMLLJW.js +157 -0
- package/dist/chunk-36GQIVGT.js +526 -0
- package/dist/chunk-3CEPHS4C.js +231 -0
- package/dist/chunk-3NJNVZTK.js +123 -0
- package/dist/chunk-42DSA4EA.js +116 -0
- package/dist/chunk-4P26OKTP.js +100 -0
- package/dist/chunk-5DEWP3YX.js +139 -0
- package/dist/chunk-6TWBYMY7.js +9 -0
- package/dist/chunk-A2QAIRQL.js +45 -0
- package/dist/chunk-ATW7BNIG.js +31 -0
- package/dist/chunk-D5O5CIWK.js +1339 -0
- package/dist/chunk-EU7PA45O.js +324 -0
- package/dist/chunk-FARS4R67.js +206 -0
- package/dist/chunk-G2AX7MGS.js +36 -0
- package/dist/chunk-HUIK3WXO.js +234 -0
- package/dist/chunk-ITITLNAR.js +9341 -0
- package/dist/chunk-KKB5W47S.js +231 -0
- package/dist/chunk-KWRJREUH.js +283 -0
- package/dist/chunk-N4I6YS3E.js +73 -0
- package/dist/chunk-OUCV3FCT.js +205 -0
- package/dist/chunk-PZS3V6P3.js +46 -0
- package/dist/chunk-QOW5TK4L.js +320 -0
- package/dist/chunk-U4OQVT2T.js +73 -0
- package/dist/chunk-V7WPGQLB.js +207 -0
- package/dist/chunk-W7BPYQMW.js +173 -0
- package/dist/chunk-WMOLXTBU.js +61 -0
- package/dist/chunk-YIZWYUBG.js +138 -0
- package/dist/chunk-YM6T4J5V.js +1350 -0
- package/dist/chunk-YQTGMCU6.js +111 -0
- package/dist/chunk-ZAE25JM2.js +158 -0
- package/dist/chunk-ZGZ2ZADL.js +49 -0
- package/dist/cli/index.js +1191 -0
- package/dist/create-Y5SXJUXO.js +223 -0
- package/dist/deadline-F5RJJ2QW.js +86 -0
- package/dist/detect-LARDSLLZ.js +10 -0
- package/dist/evalCommand-5TG2CKZY.js +2795 -0
- package/dist/export-HN3CT7AV.js +158 -0
- package/dist/inkChat-3J26R24L.js +1190 -0
- package/dist/install-XWLU22OH.js +17 -0
- package/dist/job-3NYDMP2M.js +31 -0
- package/dist/jobCommand-IYWZWTSI.js +30 -0
- package/dist/launchd-2FL5UN2U.js +40 -0
- package/dist/lspCommand-TJEZBV2E.js +99 -0
- package/dist/manager-4PMSXT47.js +14 -0
- package/dist/manager-EBTYO2GR.js +847 -0
- package/dist/nightCommand-DUNWJASI.js +247 -0
- package/dist/oauth-W6OK6XXO.js +23 -0
- package/dist/openaiCompatible-CJWVJP5M.js +501 -0
- package/dist/policy-EJPLEEXP.js +31 -0
- package/dist/proxy-XJAHCZB2.js +192 -0
- package/dist/remove-AENULZZE.js +59 -0
- package/dist/run-66Y6R2KU.js +886 -0
- package/dist/servers-6YDGWOZ6.js +24 -0
- package/dist/text-J2GD647J.js +11 -0
- package/package.json +83 -3
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/jobs/proof.ts
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
5
|
+
import { extname, join } from "path";
|
|
6
|
+
var TEST_TAIL_LINES = 30;
|
|
7
|
+
var REVIEW_DIFF_CHARS = 6e4;
|
|
8
|
+
function detectTestCommand(root, profiles) {
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
11
|
+
const test = pkg.scripts?.test;
|
|
12
|
+
if (typeof test === "string" && test.trim() !== "" && !test.includes("no test specified")) {
|
|
13
|
+
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm test";
|
|
14
|
+
if (existsSync(join(root, "yarn.lock"))) return "yarn test";
|
|
15
|
+
return "npm test";
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
if (profiles[0] !== void 0) return profiles[0].test;
|
|
20
|
+
return hasNodeTests(root) ? "node --test" : void 0;
|
|
21
|
+
}
|
|
22
|
+
function hasNodeTests(root) {
|
|
23
|
+
const isTest = (name) => /\.test\.[cm]?js$/.test(name);
|
|
24
|
+
for (const dir of [root, join(root, "test")]) {
|
|
25
|
+
try {
|
|
26
|
+
if (readdirSync(dir).some(isTest)) return true;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function testSummary(run) {
|
|
33
|
+
if (run === void 0) return "not run";
|
|
34
|
+
if (run.timedOut) return `stopped after ${Math.round(run.durationMs / 1e3)} s (too long)`;
|
|
35
|
+
return `${run.exitCode === 0 ? "pass" : `fail (exit code ${run.exitCode})`} in ${Math.round(run.durationMs / 1e3)} s`;
|
|
36
|
+
}
|
|
37
|
+
var passed = (run) => run !== void 0 && !run.timedOut && run.exitCode === 0;
|
|
38
|
+
var TEST_PATH = /(^|\/)(tests?|__tests__|spec)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)test_[^/]*\.py$|_test\.(py|go)$|(^|\/)src\/test\//;
|
|
39
|
+
var MANIFEST = /(^|\/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|pyproject\.toml|requirements[^/]*\.txt|poetry\.lock|uv\.lock|pom\.xml|build\.gradle(\.kts)?|go\.mod|go\.sum|Cargo\.toml|Cargo\.lock|Gemfile(\.lock)?)$/;
|
|
40
|
+
var DELIVERY = /(^|\/)(\.github\/workflows\/|\.gitlab-ci\.yml$|Dockerfile$|docker-compose[^/]*\.ya?ml$|\.env)/;
|
|
41
|
+
function riskFlags(input) {
|
|
42
|
+
const { result, test, before, after } = input;
|
|
43
|
+
const flags = [];
|
|
44
|
+
if (result.stopReason !== "done") {
|
|
45
|
+
flags.push({
|
|
46
|
+
level: "stop",
|
|
47
|
+
text: `The job did not finish: it stopped with "${result.stopReason}".`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (result.error !== void 0) flags.push({ level: "stop", text: `Error: ${result.error}` });
|
|
51
|
+
if (test === void 0) {
|
|
52
|
+
flags.push({ level: "look", text: "No test command: nothing proves the change works." });
|
|
53
|
+
} else if (after !== void 0 && !passed(after)) {
|
|
54
|
+
flags.push({
|
|
55
|
+
level: "stop",
|
|
56
|
+
text: passed(before) ? "The tests passed before the job and fail after it." : "The tests fail after the job (they failed before it too)."
|
|
57
|
+
});
|
|
58
|
+
} else if (after !== void 0 && before !== void 0 && !passed(before)) {
|
|
59
|
+
flags.push({
|
|
60
|
+
level: "look",
|
|
61
|
+
text: "The tests failed before the job and pass after it: check that the change is why."
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const tests = result.files.filter((f) => TEST_PATH.test(f.path));
|
|
65
|
+
const deletedTests = tests.filter((f) => f.status === "deleted");
|
|
66
|
+
if (deletedTests.length > 0) {
|
|
67
|
+
flags.push({
|
|
68
|
+
level: "look",
|
|
69
|
+
text: `Test files deleted: ${deletedTests.map((f) => f.path).join(", ")}.`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const changedTests = tests.filter((f) => f.status === "modified");
|
|
73
|
+
if (changedTests.length > 0) {
|
|
74
|
+
flags.push({
|
|
75
|
+
level: "look",
|
|
76
|
+
text: `Existing tests changed: ${changedTests.map((f) => f.path).join(", ")}. Check that they still test what they did.`
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const manifests = result.files.filter((f) => MANIFEST.test(f.path));
|
|
80
|
+
if (manifests.length > 0) {
|
|
81
|
+
flags.push({
|
|
82
|
+
level: "look",
|
|
83
|
+
text: `Dependencies or build files changed: ${manifests.map((f) => f.path).join(", ")}.`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const delivery = result.files.filter((f) => DELIVERY.test(f.path));
|
|
87
|
+
if (delivery.length > 0) {
|
|
88
|
+
flags.push({
|
|
89
|
+
level: "look",
|
|
90
|
+
text: `CI, container or environment files changed: ${delivery.map((f) => f.path).join(", ")}.`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const lines = result.files.reduce((n, f) => n + (f.added ?? 0) + (f.removed ?? 0), 0);
|
|
94
|
+
if (result.files.length > 20 || lines > 500) {
|
|
95
|
+
flags.push({
|
|
96
|
+
level: "look",
|
|
97
|
+
text: `A large change: ${result.files.length} files, ${lines} lines.`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (result.denied.length > 0) {
|
|
101
|
+
flags.push({
|
|
102
|
+
level: "look",
|
|
103
|
+
text: `${result.denied.length} call(s) were denied: the job may be incomplete.`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return flags;
|
|
107
|
+
}
|
|
108
|
+
var LANGUAGES = {
|
|
109
|
+
ts: "TypeScript",
|
|
110
|
+
tsx: "TypeScript",
|
|
111
|
+
mts: "TypeScript",
|
|
112
|
+
cts: "TypeScript",
|
|
113
|
+
js: "JavaScript",
|
|
114
|
+
jsx: "JavaScript",
|
|
115
|
+
mjs: "JavaScript",
|
|
116
|
+
cjs: "JavaScript",
|
|
117
|
+
py: "Python",
|
|
118
|
+
java: "Java",
|
|
119
|
+
kt: "Kotlin",
|
|
120
|
+
go: "Go",
|
|
121
|
+
rs: "Rust",
|
|
122
|
+
rb: "Ruby",
|
|
123
|
+
cs: "C#",
|
|
124
|
+
swift: "Swift",
|
|
125
|
+
php: "PHP",
|
|
126
|
+
scala: "Scala",
|
|
127
|
+
sql: "SQL"
|
|
128
|
+
};
|
|
129
|
+
function stackOf(files, profiles) {
|
|
130
|
+
const out = /* @__PURE__ */ new Set();
|
|
131
|
+
for (const f of files) {
|
|
132
|
+
const language = LANGUAGES[extname(f.path).slice(1).toLowerCase()];
|
|
133
|
+
if (language !== void 0) out.add(language);
|
|
134
|
+
}
|
|
135
|
+
for (const p of profiles) out.add(p.label);
|
|
136
|
+
return [...out];
|
|
137
|
+
}
|
|
138
|
+
function reviewerSystem(stack) {
|
|
139
|
+
const expert = stack.length === 0 ? "this codebase" : `${stack.join(", ")} and this codebase`;
|
|
140
|
+
return [
|
|
141
|
+
`You are a principal engineer and a domain expert in ${expert}. A staff engineer made the change below`,
|
|
142
|
+
"as an unattended job, from a plan that the user approved. You review it before the user merges it.",
|
|
143
|
+
"Check, in this order: correctness and edge cases; that the change does what the plan says, no more and",
|
|
144
|
+
"no less; that tests prove it (new behaviour tested, no test weakened); security and data risks; the",
|
|
145
|
+
"project's conventions. Judge only what the diff and the facts show. Do not invent problems; say what",
|
|
146
|
+
"you cannot see.",
|
|
147
|
+
"",
|
|
148
|
+
"Answer in exactly this form:",
|
|
149
|
+
"VERDICT: ready | needs a look",
|
|
150
|
+
"SUMMARY: one sentence",
|
|
151
|
+
"FINDINGS:",
|
|
152
|
+
"- [high|medium|low] path:line: the problem and the fix",
|
|
153
|
+
'(or "- none")'
|
|
154
|
+
].join("\n");
|
|
155
|
+
}
|
|
156
|
+
function reviewPrompt(input) {
|
|
157
|
+
const diff = input.diff.length <= REVIEW_DIFF_CHARS ? input.diff : `${input.diff.slice(0, REVIEW_DIFF_CHARS)}
|
|
158
|
+
[\u2026 the diff is cut here; ${input.diff.length - REVIEW_DIFF_CHARS} more characters]`;
|
|
159
|
+
return [
|
|
160
|
+
"<job>",
|
|
161
|
+
input.prompt.trim(),
|
|
162
|
+
"</job>",
|
|
163
|
+
"",
|
|
164
|
+
"<facts>",
|
|
165
|
+
`Files: ${input.files.map((f) => `${f.status} ${f.path} (+${f.added ?? "?"} \u2212${f.removed ?? "?"})`).join("; ") || "none"}`,
|
|
166
|
+
`Tests (${input.test ?? "no test command"}): before the job ${testSummary(input.before)}; after it ${testSummary(input.after)}`,
|
|
167
|
+
...input.after !== void 0 && !passed(input.after) ? [`Test output after the job:
|
|
168
|
+
${input.after.tail}`] : [],
|
|
169
|
+
`Risk flags: ${input.flags.length === 0 ? "none" : input.flags.map((f) => f.text).join(" ")}`,
|
|
170
|
+
"</facts>",
|
|
171
|
+
"",
|
|
172
|
+
"<diff>",
|
|
173
|
+
diff,
|
|
174
|
+
"</diff>"
|
|
175
|
+
].join("\n");
|
|
176
|
+
}
|
|
177
|
+
function parseReview(text) {
|
|
178
|
+
const m = /^\s*VERDICT:\s*(ready|needs a look)\b/im.exec(text);
|
|
179
|
+
return { verdict: m?.[1]?.toLowerCase() === "ready" ? "ready" : "needs-look", text: text.trim() };
|
|
180
|
+
}
|
|
181
|
+
function jobVerdict(flags, review) {
|
|
182
|
+
if (flags.some((f) => f.level === "stop")) return "needs-look";
|
|
183
|
+
return review ?? (flags.length === 0 ? "ready" : "needs-look");
|
|
184
|
+
}
|
|
185
|
+
function tail(text, lines = TEST_TAIL_LINES) {
|
|
186
|
+
const all = text.trimEnd().split("\n");
|
|
187
|
+
return all.length <= lines ? all.join("\n") : `[\u2026 ${all.length - lines} lines]
|
|
188
|
+
${all.slice(-lines).join("\n")}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
detectTestCommand,
|
|
193
|
+
testSummary,
|
|
194
|
+
passed,
|
|
195
|
+
TEST_PATH,
|
|
196
|
+
MANIFEST,
|
|
197
|
+
riskFlags,
|
|
198
|
+
stackOf,
|
|
199
|
+
reviewerSystem,
|
|
200
|
+
reviewPrompt,
|
|
201
|
+
parseReview,
|
|
202
|
+
jobVerdict,
|
|
203
|
+
tail
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=chunk-OUCV3FCT.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp/sanitize.ts
|
|
4
|
+
var ESCAPES = /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b[@-_]|\u009b[0-?]*[ -/]*[@-~]/g;
|
|
5
|
+
var CONTROLS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
6
|
+
var INVISIBLE = /[\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/g;
|
|
7
|
+
var TAGS = /[\u{e0000}-\u{e007f}]/gu;
|
|
8
|
+
function cleanText(text) {
|
|
9
|
+
return text.replace(/\r\n?/g, "\n").replace(ESCAPES, "").replace(CONTROLS, "").replace(INVISIBLE, "").replace(TAGS, "");
|
|
10
|
+
}
|
|
11
|
+
function cleanLine(text) {
|
|
12
|
+
return cleanText(text).replace(/\s+/g, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
function capText(text, max) {
|
|
15
|
+
if (text.length <= max) return text;
|
|
16
|
+
const head = Math.floor(max * 0.7);
|
|
17
|
+
const tail = max - head;
|
|
18
|
+
return `${text.slice(0, head)}
|
|
19
|
+
[\u2026 ${text.length - max} characters cut by Garuda \u2026]
|
|
20
|
+
${text.slice(-tail)}`;
|
|
21
|
+
}
|
|
22
|
+
function cleanJson(value) {
|
|
23
|
+
if (typeof value === "string") return cleanText(value);
|
|
24
|
+
if (Array.isArray(value)) return value.map(cleanJson);
|
|
25
|
+
if (value !== null && typeof value === "object") {
|
|
26
|
+
return Object.fromEntries(
|
|
27
|
+
Object.entries(value).map(([k, v]) => [
|
|
28
|
+
cleanText(k),
|
|
29
|
+
cleanJson(v)
|
|
30
|
+
])
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function neutralizeTags(text) {
|
|
36
|
+
return text.replace(/<(\/?)(mcp_result|web_result|garuda_note|skill_file|skill)/gi, "<\\$1$2");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
cleanText,
|
|
41
|
+
cleanLine,
|
|
42
|
+
capText,
|
|
43
|
+
cleanJson,
|
|
44
|
+
neutralizeTags
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=chunk-PZS3V6P3.js.map
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
notificationBytes,
|
|
4
|
+
pickChannel
|
|
5
|
+
} from "./chunk-N4I6YS3E.js";
|
|
6
|
+
import {
|
|
7
|
+
gateProjectSettings,
|
|
8
|
+
totalTokens
|
|
9
|
+
} from "./chunk-W7BPYQMW.js";
|
|
10
|
+
import {
|
|
11
|
+
TrustStore
|
|
12
|
+
} from "./chunk-5DEWP3YX.js";
|
|
13
|
+
import {
|
|
14
|
+
cleanArgs,
|
|
15
|
+
commitJob,
|
|
16
|
+
jobChanges,
|
|
17
|
+
prepareWorktree
|
|
18
|
+
} from "./chunk-YIZWYUBG.js";
|
|
19
|
+
import {
|
|
20
|
+
defaultAgentEnv,
|
|
21
|
+
removeAgent
|
|
22
|
+
} from "./chunk-35GMLLJW.js";
|
|
23
|
+
import {
|
|
24
|
+
jobReport
|
|
25
|
+
} from "./chunk-YQTGMCU6.js";
|
|
26
|
+
import {
|
|
27
|
+
jobVerdict,
|
|
28
|
+
parseReview,
|
|
29
|
+
reviewPrompt,
|
|
30
|
+
reviewerSystem,
|
|
31
|
+
riskFlags,
|
|
32
|
+
stackOf,
|
|
33
|
+
testSummary
|
|
34
|
+
} from "./chunk-OUCV3FCT.js";
|
|
35
|
+
import {
|
|
36
|
+
JobGitError,
|
|
37
|
+
git,
|
|
38
|
+
hostCommand
|
|
39
|
+
} from "./chunk-ZGZ2ZADL.js";
|
|
40
|
+
import {
|
|
41
|
+
createExecutor,
|
|
42
|
+
loadSettings
|
|
43
|
+
} from "./chunk-D5O5CIWK.js";
|
|
44
|
+
import {
|
|
45
|
+
JOBS_DIR,
|
|
46
|
+
loadJob,
|
|
47
|
+
saveJob
|
|
48
|
+
} from "./chunk-KKB5W47S.js";
|
|
49
|
+
import {
|
|
50
|
+
parseRule
|
|
51
|
+
} from "./chunk-3NJNVZTK.js";
|
|
52
|
+
|
|
53
|
+
// src/cli/jobCommand.ts
|
|
54
|
+
import { writeFile } from "fs/promises";
|
|
55
|
+
import { join } from "path";
|
|
56
|
+
var JOB_TEST_TIMEOUT_MS = 10 * 6e4;
|
|
57
|
+
async function testsBefore(prepared, runtime, renderer, git2 = createExecutor("host").executor) {
|
|
58
|
+
const { job } = prepared;
|
|
59
|
+
if (job.test === void 0) return;
|
|
60
|
+
renderer.info(`Tests before the job: ${job.test}`);
|
|
61
|
+
prepared.testsBefore = await runtime.runCheck(
|
|
62
|
+
job.test,
|
|
63
|
+
JOB_TEST_TIMEOUT_MS,
|
|
64
|
+
new AbortController().signal
|
|
65
|
+
);
|
|
66
|
+
renderer.info(`Tests before the job: ${testSummary(prepared.testsBefore)}`);
|
|
67
|
+
await git(git2, job.worktree, ["checkout", "--", "."], { check: false });
|
|
68
|
+
await git(git2, job.worktree, cleanArgs(job.links), { check: false });
|
|
69
|
+
}
|
|
70
|
+
async function proveJob(prepared, result, runtime, renderer, git2) {
|
|
71
|
+
const { job } = prepared;
|
|
72
|
+
const signal = new AbortController().signal;
|
|
73
|
+
let after;
|
|
74
|
+
if (job.test !== void 0) {
|
|
75
|
+
after = await runtime.runCheck(job.test, JOB_TEST_TIMEOUT_MS, signal);
|
|
76
|
+
renderer.info(`Tests after the job: ${testSummary(after)}`);
|
|
77
|
+
}
|
|
78
|
+
const before = prepared.testsBefore;
|
|
79
|
+
const flags = riskFlags({
|
|
80
|
+
result,
|
|
81
|
+
...job.test === void 0 ? {} : { test: job.test },
|
|
82
|
+
...before === void 0 ? {} : { before },
|
|
83
|
+
...after === void 0 ? {} : { after }
|
|
84
|
+
});
|
|
85
|
+
if (result.commit === void 0) flags.push({ level: "look", text: "No file changed." });
|
|
86
|
+
const proof = {
|
|
87
|
+
verdict: "needs-look",
|
|
88
|
+
...before === void 0 ? {} : { before },
|
|
89
|
+
...after === void 0 ? {} : { after },
|
|
90
|
+
flags
|
|
91
|
+
};
|
|
92
|
+
if (job.review !== false && result.commit !== void 0) {
|
|
93
|
+
renderer.info("Reviewing the diff (a principal engineer's review)\u2026");
|
|
94
|
+
try {
|
|
95
|
+
const diff = (await git(git2, job.root, ["diff", "--no-renames", job.base, job.branch], {
|
|
96
|
+
check: false
|
|
97
|
+
})).stdout;
|
|
98
|
+
const answer = await runtime.askModel(
|
|
99
|
+
reviewerSystem(stackOf(result.files, runtime.profiles)),
|
|
100
|
+
reviewPrompt({
|
|
101
|
+
prompt: job.prompt,
|
|
102
|
+
files: result.files,
|
|
103
|
+
...job.test === void 0 ? {} : { test: job.test },
|
|
104
|
+
...before === void 0 ? {} : { before },
|
|
105
|
+
...after === void 0 ? {} : { after },
|
|
106
|
+
flags,
|
|
107
|
+
diff
|
|
108
|
+
}),
|
|
109
|
+
signal
|
|
110
|
+
);
|
|
111
|
+
const parsed = parseReview(answer.text);
|
|
112
|
+
proof.review = {
|
|
113
|
+
...parsed,
|
|
114
|
+
...answer.costUsd === void 0 ? {} : { costUsd: answer.costUsd }
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
proof.reviewError = error.message;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
proof.verdict = jobVerdict(flags, proof.review?.verdict);
|
|
121
|
+
return proof;
|
|
122
|
+
}
|
|
123
|
+
async function prepareJob(mainRoot, id, at, renderer, git2 = createExecutor("host").executor, launchd = {}, trustHome) {
|
|
124
|
+
const prepared = await prepare(mainRoot, id, at, renderer, git2, trustHome);
|
|
125
|
+
if (typeof prepared === "number" && launchd.fromLaunchd === true) {
|
|
126
|
+
const job = await loadJob(mainRoot, id).catch(() => void 0);
|
|
127
|
+
if (job !== void 0) await removeAgent(git2, job, launchd.env ?? defaultAgentEnv());
|
|
128
|
+
}
|
|
129
|
+
return prepared;
|
|
130
|
+
}
|
|
131
|
+
async function prepare(mainRoot, id, at, renderer, git2, trustHome) {
|
|
132
|
+
let job;
|
|
133
|
+
try {
|
|
134
|
+
job = await loadJob(mainRoot, id);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
renderer.error(error.message);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
if (job.status === "running") {
|
|
140
|
+
renderer.error(
|
|
141
|
+
`Job ${id} is marked as running. If no other garuda runs it, set "status" to "stopped" in ${JOBS_DIR}/${id}.json and try again.`
|
|
142
|
+
);
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
if (job.status === "done") {
|
|
146
|
+
renderer.error(`Job ${id} is done. Its report: ${join(JOBS_DIR, `${id}.md`)}`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
if (at !== void 0) {
|
|
150
|
+
const waited = await waitUntil(at, renderer, job);
|
|
151
|
+
if (waited !== 0) return waited;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await prepareWorktree(git2, job);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!(error instanceof JobGitError)) throw error;
|
|
157
|
+
renderer.error(`The job's worktree failed: ${error.message}`);
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
const gate = await gateProjectSettings({
|
|
161
|
+
root: job.root,
|
|
162
|
+
settings: await loadSettings(job.worktree),
|
|
163
|
+
trust: trustHome === void 0 ? void 0 : await TrustStore.open(trustHome)
|
|
164
|
+
});
|
|
165
|
+
if (gate.notice !== void 0) renderer.info(gate.notice);
|
|
166
|
+
const project = gate.settings;
|
|
167
|
+
const links = job.links.map((name) => join(job.root, name));
|
|
168
|
+
const { network: _projectNetwork, ...projectRest } = project;
|
|
169
|
+
const settings = {
|
|
170
|
+
...projectRest,
|
|
171
|
+
...job.network === void 0 ? {} : { network: { allow: job.network } },
|
|
172
|
+
allow: [...project.allow, ...job.allow.map((rule) => parseRule(rule))],
|
|
173
|
+
maxSteps: job.maxSteps,
|
|
174
|
+
sandbox: {
|
|
175
|
+
...project.sandbox,
|
|
176
|
+
// Linked folders (node_modules …) live in the checkout: tools may write their caches there.
|
|
177
|
+
writePaths: [...project.sandbox?.writePaths ?? [], ...links]
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
job.status = "running";
|
|
181
|
+
job.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
182
|
+
await saveJob(job);
|
|
183
|
+
renderer.info(
|
|
184
|
+
`Job ${job.id}: ${job.title}
|
|
185
|
+
Branch ${job.branch} \xB7 worktree ${job.worktree}
|
|
186
|
+
Approved: ${job.allow.length === 0 ? "nothing beyond the sandbox" : job.allow.join(", ")}`
|
|
187
|
+
);
|
|
188
|
+
return { job, root: job.worktree, settings, denied: [], startedAt: Date.now() };
|
|
189
|
+
}
|
|
190
|
+
async function waitUntil(at, renderer, job) {
|
|
191
|
+
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(at);
|
|
192
|
+
if (match === null) {
|
|
193
|
+
renderer.error(`"${at}" is not a time. Use HH:MM, for example 01:00.`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
const ms = msUntil(Number(match[1]), Number(match[2]));
|
|
197
|
+
const hours = Math.floor(ms / 36e5);
|
|
198
|
+
const minutes = Math.round(ms % 36e5 / 6e4);
|
|
199
|
+
renderer.info(
|
|
200
|
+
`Job ${job.id} waits until ${at} (in ${hours} h ${minutes} min). Keep this terminal open and the Mac awake and on power. Ctrl-C cancels the wait.`
|
|
201
|
+
);
|
|
202
|
+
const cancelled = await new Promise((resolve) => {
|
|
203
|
+
const timer = setTimeout(() => {
|
|
204
|
+
process.off("SIGINT", cancel);
|
|
205
|
+
resolve(false);
|
|
206
|
+
}, ms);
|
|
207
|
+
const cancel = () => {
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
resolve(true);
|
|
210
|
+
};
|
|
211
|
+
process.once("SIGINT", cancel);
|
|
212
|
+
});
|
|
213
|
+
if (cancelled) {
|
|
214
|
+
renderer.warn(`The wait was cancelled. Job ${job.id} is still scheduled.`);
|
|
215
|
+
return 130;
|
|
216
|
+
}
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
function msUntil(hour, minute, now = /* @__PURE__ */ new Date()) {
|
|
220
|
+
const next = new Date(now);
|
|
221
|
+
next.setHours(hour, minute, 0, 0);
|
|
222
|
+
if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
|
|
223
|
+
return next.getTime() - now.getTime();
|
|
224
|
+
}
|
|
225
|
+
async function finishJob(prepared, outcome, runtime, renderer, git2 = createExecutor("host").executor, launchd = {}) {
|
|
226
|
+
const { job } = prepared;
|
|
227
|
+
const hadAgent = job.launchd !== void 0 || launchd.fromLaunchd === true;
|
|
228
|
+
delete job.launchd;
|
|
229
|
+
const result = {
|
|
230
|
+
stopReason: outcome.kind === "done" ? outcome.result.stopReason : outcome.kind,
|
|
231
|
+
steps: outcome.kind === "done" ? outcome.result.steps : 0,
|
|
232
|
+
tokens: runtime.session === void 0 ? 0 : totalTokens(runtime.session.usage),
|
|
233
|
+
...runtime.session?.costUsd === void 0 ? {} : { costUsd: runtime.session.costUsd },
|
|
234
|
+
durationMs: Date.now() - prepared.startedAt,
|
|
235
|
+
...runtime.session === void 0 ? {} : { sessionId: runtime.session.id },
|
|
236
|
+
files: [],
|
|
237
|
+
denied: prepared.denied,
|
|
238
|
+
answer: lastAnswer(runtime),
|
|
239
|
+
...prepared.deadline === void 0 ? {} : {
|
|
240
|
+
modelCalls: {
|
|
241
|
+
batch: prepared.deadline.calls.primary,
|
|
242
|
+
normal: prepared.deadline.calls.fallback,
|
|
243
|
+
slow: prepared.deadline.calls.slow,
|
|
244
|
+
...prepared.deadline.calls.switchedAt === void 0 ? {} : { switchedAt: prepared.deadline.calls.switchedAt.toISOString() }
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
...outcome.kind === "error" ? { error: outcome.message } : {}
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
const commit = await commitJob(
|
|
251
|
+
git2,
|
|
252
|
+
job,
|
|
253
|
+
`Garuda job ${job.id}: ${job.title}${outcome.kind === "done" && outcome.result.stopReason === "done" ? "" : ` (${result.stopReason})`}`
|
|
254
|
+
);
|
|
255
|
+
if (commit !== void 0) result.commit = commit;
|
|
256
|
+
result.files = await jobChanges(git2, job);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (!(error instanceof JobGitError)) throw error;
|
|
259
|
+
result.error = [result.error, `commit: ${error.message}`].filter(Boolean).join("; ");
|
|
260
|
+
}
|
|
261
|
+
if (outcome.kind !== "interrupted") {
|
|
262
|
+
result.proof = await proveJob(prepared, result, runtime, renderer, git2);
|
|
263
|
+
}
|
|
264
|
+
job.status = outcome.kind === "error" ? "failed" : outcome.kind === "interrupted" ? "stopped" : "done";
|
|
265
|
+
job.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
266
|
+
job.result = result;
|
|
267
|
+
await saveJob(job);
|
|
268
|
+
const report = jobReport(job);
|
|
269
|
+
const file = join(job.root, JOBS_DIR, `${job.id}.md`);
|
|
270
|
+
await writeFile(file, `${report}
|
|
271
|
+
`, { mode: 384 });
|
|
272
|
+
renderer.info(`
|
|
273
|
+
${report}
|
|
274
|
+
|
|
275
|
+
Report: ${file}`);
|
|
276
|
+
const verdict = result.proof === void 0 ? "" : result.proof.verdict === "ready" ? ", ready to merge" : ", needs a look";
|
|
277
|
+
const note = `Garuda: job ${job.id} ${job.status}${verdict} (${result.files.length} file(s) changed)`;
|
|
278
|
+
if (process.stdout.isTTY) {
|
|
279
|
+
process.stdout.write(
|
|
280
|
+
notificationBytes(pickChannel(runtime.notificationSettings?.channel), note)
|
|
281
|
+
);
|
|
282
|
+
} else if (launchd.fromLaunchd === true && process.platform === "darwin") {
|
|
283
|
+
await hostCommand(
|
|
284
|
+
git2,
|
|
285
|
+
job.root,
|
|
286
|
+
[
|
|
287
|
+
"osascript",
|
|
288
|
+
"-e",
|
|
289
|
+
"on run argv",
|
|
290
|
+
"-e",
|
|
291
|
+
'display notification (item 1 of argv) with title "Garuda"',
|
|
292
|
+
"-e",
|
|
293
|
+
"end run",
|
|
294
|
+
note
|
|
295
|
+
],
|
|
296
|
+
{ check: false }
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (hadAgent) await removeAgent(git2, job, launchd.env ?? defaultAgentEnv());
|
|
300
|
+
}
|
|
301
|
+
function lastAnswer(runtime) {
|
|
302
|
+
const messages = runtime.session?.messages ?? [];
|
|
303
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
304
|
+
const m = messages[i];
|
|
305
|
+
if (m?.role !== "assistant") continue;
|
|
306
|
+
const text = m.content.map((b) => b.type === "text" ? b.text : "").join("").trim();
|
|
307
|
+
if (text !== "") return text.length <= 8e3 ? text : `${text.slice(0, 8e3)}
|
|
308
|
+
\u2026 [cut]`;
|
|
309
|
+
}
|
|
310
|
+
return "";
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export {
|
|
314
|
+
JOB_TEST_TIMEOUT_MS,
|
|
315
|
+
testsBefore,
|
|
316
|
+
prepareJob,
|
|
317
|
+
msUntil,
|
|
318
|
+
finishJob
|
|
319
|
+
};
|
|
320
|
+
//# sourceMappingURL=chunk-QOW5TK4L.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MANAGED,
|
|
4
|
+
jdtlsInstallCommand,
|
|
5
|
+
managedDir
|
|
6
|
+
} from "./chunk-EU7PA45O.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_ENV_ALLOWLIST
|
|
9
|
+
} from "./chunk-D5O5CIWK.js";
|
|
10
|
+
|
|
11
|
+
// src/lsp/install.ts
|
|
12
|
+
import { mkdir } from "fs/promises";
|
|
13
|
+
var INSTALL_TIMEOUT_MS = 3e5;
|
|
14
|
+
var NPM_ENV = [
|
|
15
|
+
"HTTP_PROXY",
|
|
16
|
+
"HTTPS_PROXY",
|
|
17
|
+
"NO_PROXY",
|
|
18
|
+
"http_proxy",
|
|
19
|
+
"https_proxy",
|
|
20
|
+
"no_proxy",
|
|
21
|
+
"NODE_EXTRA_CA_CERTS",
|
|
22
|
+
"npm_config_registry",
|
|
23
|
+
"NPM_CONFIG_REGISTRY"
|
|
24
|
+
];
|
|
25
|
+
function installCommand(language, dir) {
|
|
26
|
+
const managed = MANAGED[language];
|
|
27
|
+
if (managed.kind === "eclipse") return jdtlsInstallCommand(dir, quote);
|
|
28
|
+
return [
|
|
29
|
+
"npm",
|
|
30
|
+
"install",
|
|
31
|
+
"--prefix",
|
|
32
|
+
quote(dir),
|
|
33
|
+
"--ignore-scripts",
|
|
34
|
+
"--no-audit",
|
|
35
|
+
"--no-fund",
|
|
36
|
+
"--loglevel=error",
|
|
37
|
+
...managed.packages
|
|
38
|
+
].join(" ");
|
|
39
|
+
}
|
|
40
|
+
async function installServer(language, options) {
|
|
41
|
+
const dir = managedDir(language, options.home);
|
|
42
|
+
await mkdir(dir, { recursive: true });
|
|
43
|
+
const command = installCommand(language, dir);
|
|
44
|
+
const policy = {
|
|
45
|
+
root: dir,
|
|
46
|
+
sandbox: false,
|
|
47
|
+
writePaths: [],
|
|
48
|
+
denyWritePaths: [],
|
|
49
|
+
denyReadPaths: [],
|
|
50
|
+
network: true,
|
|
51
|
+
envAllowlist: [...DEFAULT_ENV_ALLOWLIST, ...NPM_ENV],
|
|
52
|
+
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
53
|
+
maxOutputBytes: 2e4
|
|
54
|
+
};
|
|
55
|
+
const result = await options.executor.run(
|
|
56
|
+
command,
|
|
57
|
+
policy,
|
|
58
|
+
options.signal === void 0 ? {} : { signal: options.signal }
|
|
59
|
+
);
|
|
60
|
+
if (result.exitCode === 0) return { ok: true, dir, command };
|
|
61
|
+
const why = result.timedOut ? `npm took longer than ${INSTALL_TIMEOUT_MS / 1e3} s.` : [result.stderr.text, result.stdout.text].join("\n").trim().split("\n").slice(-8).join("\n");
|
|
62
|
+
return { ok: false, dir, command, output: why || `npm exited with ${result.exitCode}.` };
|
|
63
|
+
}
|
|
64
|
+
function quote(text) {
|
|
65
|
+
return /^[\w./:@-]+$/.test(text) ? text : `'${text.replaceAll("'", `'\\''`)}'`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
INSTALL_TIMEOUT_MS,
|
|
70
|
+
installCommand,
|
|
71
|
+
installServer
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=chunk-U4OQVT2T.js.map
|