@ipul-labs/forge 1.16.7 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -21
- package/lib/bin/cli/analyze.d.ts +9 -0
- package/lib/bin/cli/analyze.d.ts.map +1 -0
- package/lib/bin/cli/analyze.js +491 -0
- package/lib/bin/cli/analyze.js.map +1 -0
- package/lib/bin/cli/apply.d.ts +2 -0
- package/lib/bin/cli/apply.d.ts.map +1 -0
- package/lib/bin/cli/apply.js +262 -0
- package/lib/bin/cli/apply.js.map +1 -0
- package/lib/bin/cli/cifix.d.ts +2 -0
- package/lib/bin/cli/cifix.d.ts.map +1 -0
- package/lib/bin/cli/cifix.js +310 -0
- package/lib/bin/cli/cifix.js.map +1 -0
- package/lib/bin/cli/close.d.ts +2 -0
- package/lib/bin/cli/close.d.ts.map +1 -0
- package/lib/bin/cli/close.js +113 -0
- package/lib/bin/cli/close.js.map +1 -0
- package/lib/bin/cli/createIssue.d.ts +2 -0
- package/lib/bin/cli/createIssue.d.ts.map +1 -0
- package/lib/bin/cli/createIssue.js +108 -0
- package/lib/bin/cli/createIssue.js.map +1 -0
- package/lib/bin/cli/doctor.d.ts +2 -0
- package/lib/bin/cli/doctor.d.ts.map +1 -0
- package/lib/bin/cli/doctor.js +145 -0
- package/lib/bin/cli/doctor.js.map +1 -0
- package/lib/bin/cli/init.d.ts +4 -0
- package/lib/bin/cli/init.d.ts.map +1 -0
- package/lib/bin/cli/init.js +288 -0
- package/lib/bin/cli/init.js.map +1 -0
- package/lib/bin/cli/ink.d.ts +23 -0
- package/lib/bin/cli/ink.d.ts.map +1 -0
- package/{bin → lib/bin}/cli/ink.js +18 -19
- package/lib/bin/cli/ink.js.map +1 -0
- package/lib/bin/cli/issue.d.ts +2 -0
- package/lib/bin/cli/issue.d.ts.map +1 -0
- package/lib/bin/cli/issue.js +114 -0
- package/lib/bin/cli/issue.js.map +1 -0
- package/lib/bin/cli/issues.d.ts +2 -0
- package/lib/bin/cli/issues.d.ts.map +1 -0
- package/lib/bin/cli/issues.js +149 -0
- package/lib/bin/cli/issues.js.map +1 -0
- package/lib/bin/cli/merge.d.ts +2 -0
- package/lib/bin/cli/merge.d.ts.map +1 -0
- package/lib/bin/cli/merge.js +162 -0
- package/lib/bin/cli/merge.js.map +1 -0
- package/lib/bin/cli/refine.d.ts +2 -0
- package/lib/bin/cli/refine.d.ts.map +1 -0
- package/lib/bin/cli/refine.js +249 -0
- package/lib/bin/cli/refine.js.map +1 -0
- package/lib/bin/cli/repl.d.ts +2 -0
- package/lib/bin/cli/repl.d.ts.map +1 -0
- package/lib/bin/cli/repl.js +309 -0
- package/lib/bin/cli/repl.js.map +1 -0
- package/lib/bin/cli/repos.d.ts +2 -0
- package/lib/bin/cli/repos.d.ts.map +1 -0
- package/lib/bin/cli/repos.js +86 -0
- package/lib/bin/cli/repos.js.map +1 -0
- package/lib/bin/cli/resolution.d.ts +2 -0
- package/lib/bin/cli/resolution.d.ts.map +1 -0
- package/lib/bin/cli/resolution.js +139 -0
- package/lib/bin/cli/resolution.js.map +1 -0
- package/lib/bin/cli/serverClient.d.ts +26 -0
- package/lib/bin/cli/serverClient.d.ts.map +1 -0
- package/lib/bin/cli/serverClient.js +111 -0
- package/lib/bin/cli/serverClient.js.map +1 -0
- package/lib/bin/cli/session.d.ts +8 -0
- package/lib/bin/cli/session.d.ts.map +1 -0
- package/lib/bin/cli/session.js +212 -0
- package/lib/bin/cli/session.js.map +1 -0
- package/lib/bin/cli/sessionStore.d.ts +69 -0
- package/lib/bin/cli/sessionStore.d.ts.map +1 -0
- package/lib/bin/cli/sessionStore.js +196 -0
- package/lib/bin/cli/sessionStore.js.map +1 -0
- package/lib/bin/cli/ship.d.ts +33 -0
- package/lib/bin/cli/ship.d.ts.map +1 -0
- package/lib/bin/cli/ship.js +737 -0
- package/lib/bin/cli/ship.js.map +1 -0
- package/lib/bin/cli/types.d.ts +59 -0
- package/lib/bin/cli/types.d.ts.map +1 -0
- package/lib/bin/cli/types.js +2 -0
- package/lib/bin/cli/types.js.map +1 -0
- package/lib/bin/cli/update.d.ts +2 -0
- package/lib/bin/cli/update.d.ts.map +1 -0
- package/lib/bin/cli/update.js +87 -0
- package/lib/bin/cli/update.js.map +1 -0
- package/lib/bin/cli/utils.d.ts +43 -0
- package/lib/bin/cli/utils.d.ts.map +1 -0
- package/lib/bin/cli/utils.js +171 -0
- package/lib/bin/cli/utils.js.map +1 -0
- package/lib/bin/cli/validate.d.ts +2 -0
- package/lib/bin/cli/validate.d.ts.map +1 -0
- package/lib/bin/cli/validate.js +283 -0
- package/lib/bin/cli/validate.js.map +1 -0
- package/lib/bin/cli/watch.d.ts +5 -0
- package/lib/bin/cli/watch.d.ts.map +1 -0
- package/lib/bin/cli/watch.js +263 -0
- package/lib/bin/cli/watch.js.map +1 -0
- package/lib/bin/forge-agent.d.ts +3 -0
- package/lib/bin/forge-agent.d.ts.map +1 -0
- package/{bin → lib/bin}/forge-agent.js +104 -105
- package/lib/bin/forge-agent.js.map +1 -0
- package/lib/server/forge/core/ForgeAgent.d.ts +260 -0
- package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -0
- package/lib/server/forge/core/ForgeAgent.js +995 -0
- package/lib/server/forge/core/ForgeAgent.js.map +1 -0
- package/lib/server/forge/core/agentClient.d.ts +39 -0
- package/lib/server/forge/core/agentClient.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/agentClient.js +7 -12
- package/lib/server/forge/core/agentClient.js.map +1 -0
- package/lib/server/forge/core/branchName.d.ts +20 -0
- package/lib/server/forge/core/branchName.d.ts.map +1 -0
- package/lib/server/forge/core/branchName.js +39 -0
- package/lib/server/forge/core/branchName.js.map +1 -0
- package/lib/server/forge/core/claudeClient.d.ts +60 -0
- package/lib/server/forge/core/claudeClient.d.ts.map +1 -0
- package/lib/server/forge/core/claudeClient.js +347 -0
- package/lib/server/forge/core/claudeClient.js.map +1 -0
- package/lib/server/forge/core/configStore.d.ts +76 -0
- package/lib/server/forge/core/configStore.d.ts.map +1 -0
- package/lib/server/forge/core/configStore.js +213 -0
- package/lib/server/forge/core/configStore.js.map +1 -0
- package/lib/server/forge/core/gitlabClient.d.ts +144 -0
- package/lib/server/forge/core/gitlabClient.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/gitlabClient.js +98 -180
- package/lib/server/forge/core/gitlabClient.js.map +1 -0
- package/lib/server/forge/core/jobs.d.ts +67 -0
- package/lib/server/forge/core/jobs.d.ts.map +1 -0
- package/lib/server/forge/core/jobs.js +185 -0
- package/lib/server/forge/core/jobs.js.map +1 -0
- package/lib/server/forge/core/k8s.d.ts +18 -0
- package/lib/server/forge/core/k8s.d.ts.map +1 -0
- package/lib/server/forge/core/k8s.js +49 -0
- package/lib/server/forge/core/k8s.js.map +1 -0
- package/lib/server/forge/core/localRepo.d.ts +36 -0
- package/lib/server/forge/core/localRepo.d.ts.map +1 -0
- package/lib/server/forge/core/localRepo.js +165 -0
- package/lib/server/forge/core/localRepo.js.map +1 -0
- package/lib/server/forge/core/openclawClient.d.ts +61 -0
- package/lib/server/forge/core/openclawClient.d.ts.map +1 -0
- package/lib/server/forge/core/openclawClient.js +111 -0
- package/lib/server/forge/core/openclawClient.js.map +1 -0
- package/lib/server/forge/core/prompts.d.ts +96 -0
- package/lib/server/forge/core/prompts.d.ts.map +1 -0
- package/lib/server/forge/core/prompts.js +450 -0
- package/lib/server/forge/core/prompts.js.map +1 -0
- package/lib/server/forge/core/providers/azuredevops.d.ts +134 -0
- package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -0
- package/lib/server/forge/core/providers/azuredevops.js +360 -0
- package/lib/server/forge/core/providers/azuredevops.js.map +1 -0
- package/lib/server/forge/core/providers/base.d.ts +114 -0
- package/lib/server/forge/core/providers/base.d.ts.map +1 -0
- package/lib/server/forge/core/providers/base.js +98 -0
- package/lib/server/forge/core/providers/base.js.map +1 -0
- package/lib/server/forge/core/providers/github.d.ts +133 -0
- package/lib/server/forge/core/providers/github.d.ts.map +1 -0
- package/lib/server/forge/core/providers/github.js +307 -0
- package/lib/server/forge/core/providers/github.js.map +1 -0
- package/lib/server/forge/core/providers/gitlab.d.ts +144 -0
- package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -0
- package/lib/server/forge/core/providers/gitlab.js +407 -0
- package/lib/server/forge/core/providers/gitlab.js.map +1 -0
- package/lib/server/forge/core/providers/index.d.ts +21 -0
- package/lib/server/forge/core/providers/index.d.ts.map +1 -0
- package/lib/server/forge/core/providers/index.js +43 -0
- package/lib/server/forge/core/providers/index.js.map +1 -0
- package/lib/server/forge/core/providers/types.d.ts +80 -0
- package/lib/server/forge/core/providers/types.d.ts.map +1 -0
- package/lib/server/forge/core/providers/types.js +2 -0
- package/lib/server/forge/core/providers/types.js.map +1 -0
- package/lib/server/forge/core/quotaClient.d.ts +48 -0
- package/lib/server/forge/core/quotaClient.d.ts.map +1 -0
- package/lib/server/forge/core/quotaClient.js +88 -0
- package/lib/server/forge/core/quotaClient.js.map +1 -0
- package/lib/server/forge/core/types.d.ts +160 -0
- package/lib/server/forge/core/types.d.ts.map +1 -0
- package/lib/server/forge/core/types.js +2 -0
- package/lib/server/forge/core/types.js.map +1 -0
- package/lib/server/forge/core/validator/attribute.d.ts +31 -0
- package/lib/server/forge/core/validator/attribute.d.ts.map +1 -0
- package/lib/server/forge/core/validator/attribute.js +82 -0
- package/lib/server/forge/core/validator/attribute.js.map +1 -0
- package/lib/server/forge/core/validator/clone.d.ts +14 -0
- package/lib/server/forge/core/validator/clone.d.ts.map +1 -0
- package/lib/server/forge/core/validator/clone.js +99 -0
- package/lib/server/forge/core/validator/clone.js.map +1 -0
- package/lib/server/forge/core/validator/cmd.d.ts +23 -0
- package/lib/server/forge/core/validator/cmd.d.ts.map +1 -0
- package/lib/server/forge/core/validator/cmd.js +87 -0
- package/lib/server/forge/core/validator/cmd.js.map +1 -0
- package/lib/server/forge/core/validator/fs.d.ts +5 -0
- package/lib/server/forge/core/validator/fs.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/validator/fs.js +8 -3
- package/lib/server/forge/core/validator/fs.js.map +1 -0
- package/lib/server/forge/core/validator/overlay.d.ts +13 -0
- package/lib/server/forge/core/validator/overlay.d.ts.map +1 -0
- package/lib/server/forge/core/validator/overlay.js +53 -0
- package/lib/server/forge/core/validator/overlay.js.map +1 -0
- package/lib/server/forge/core/validator/projectType.d.ts +20 -0
- package/lib/server/forge/core/validator/projectType.d.ts.map +1 -0
- package/lib/server/forge/core/validator/projectType.js +94 -0
- package/lib/server/forge/core/validator/projectType.js.map +1 -0
- package/lib/server/forge/core/validator/runners/go.d.ts +21 -0
- package/lib/server/forge/core/validator/runners/go.d.ts.map +1 -0
- package/lib/server/forge/core/validator/runners/go.js +99 -0
- package/lib/server/forge/core/validator/runners/go.js.map +1 -0
- package/lib/server/forge/core/validator/runners/node.d.ts +12 -0
- package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -0
- package/lib/server/forge/core/validator/runners/node.js +75 -0
- package/lib/server/forge/core/validator/runners/node.js.map +1 -0
- package/lib/server/forge/core/validator/tdd.d.ts +30 -0
- package/lib/server/forge/core/validator/tdd.d.ts.map +1 -0
- package/lib/server/forge/core/validator/tdd.js +96 -0
- package/lib/server/forge/core/validator/tdd.js.map +1 -0
- package/lib/server/forge/core/validator.d.ts +58 -0
- package/lib/server/forge/core/validator.d.ts.map +1 -0
- package/lib/server/forge/core/validator.js +150 -0
- package/lib/server/forge/core/validator.js.map +1 -0
- package/lib/server/forge/routes.d.ts +22 -0
- package/lib/server/forge/routes.d.ts.map +1 -0
- package/lib/server/forge/routes.js +984 -0
- package/lib/server/forge/routes.js.map +1 -0
- package/lib/server.d.ts +2 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +52 -0
- package/lib/server.js.map +1 -0
- package/lib/shared/events.d.ts +12 -0
- package/lib/shared/events.d.ts.map +1 -0
- package/lib/shared/events.js +12 -0
- package/lib/shared/events.js.map +1 -0
- package/package.json +17 -13
- package/scripts/fix-shebang.cjs +10 -0
- package/bin/cli/analyze.js +0 -516
- package/bin/cli/apply.js +0 -264
- package/bin/cli/cifix.js +0 -300
- package/bin/cli/close.js +0 -125
- package/bin/cli/createIssue.js +0 -112
- package/bin/cli/doctor.js +0 -161
- package/bin/cli/init.js +0 -306
- package/bin/cli/issue.js +0 -116
- package/bin/cli/issues.js +0 -155
- package/bin/cli/merge.js +0 -175
- package/bin/cli/refine.js +0 -253
- package/bin/cli/repl.js +0 -301
- package/bin/cli/repos.js +0 -91
- package/bin/cli/resolution.js +0 -126
- package/bin/cli/session.js +0 -230
- package/bin/cli/sessionStore.js +0 -211
- package/bin/cli/ship.js +0 -762
- package/bin/cli/update.js +0 -95
- package/bin/cli/utils.js +0 -145
- package/bin/cli/validate.js +0 -280
- package/bin/cli/watch.js +0 -274
- package/server/forge/core/ForgeAgent.js +0 -1179
- package/server/forge/core/ForgeAgent.test.js +0 -179
- package/server/forge/core/branchName.js +0 -41
- package/server/forge/core/claudeClient.js +0 -378
- package/server/forge/core/configStore.js +0 -229
- package/server/forge/core/configStore.test.js +0 -89
- package/server/forge/core/jobs.js +0 -180
- package/server/forge/core/k8s.js +0 -54
- package/server/forge/core/localRepo.js +0 -153
- package/server/forge/core/openclawClient.js +0 -125
- package/server/forge/core/prompts.js +0 -483
- package/server/forge/core/prompts.test.js +0 -219
- package/server/forge/core/providers/azuredevops.js +0 -409
- package/server/forge/core/providers/base.js +0 -208
- package/server/forge/core/providers/github.js +0 -348
- package/server/forge/core/providers/gitlab.js +0 -529
- package/server/forge/core/providers/index.js +0 -47
- package/server/forge/core/quotaClient.js +0 -101
- package/server/forge/core/validator/attribute.js +0 -84
- package/server/forge/core/validator/clone.js +0 -105
- package/server/forge/core/validator/cmd.js +0 -98
- package/server/forge/core/validator/overlay.js +0 -52
- package/server/forge/core/validator/projectType.js +0 -87
- package/server/forge/core/validator/runners/go.js +0 -107
- package/server/forge/core/validator/runners/node.js +0 -84
- package/server/forge/core/validator/tdd.js +0 -105
- package/server/forge/core/validator.js +0 -167
- package/server/forge/routes.js +0 -1026
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/forge/core/openclawClient.js
|
|
3
|
-
*
|
|
4
|
-
* Communicates with a locally-running OpenClaw Gateway over HTTP.
|
|
5
|
-
* Configure with:
|
|
6
|
-
* OPENCLAW_GATEWAY_URL — e.g. http://192.168.x.x:18789 (Tailscale) or SSH tunnel
|
|
7
|
-
* OPENCLAW_TOKEN — Bearer token if your Gateway requires auth
|
|
8
|
-
*
|
|
9
|
-
* The Gateway runs on 127.0.0.1:18789 by default. Expose it remotely via:
|
|
10
|
-
* Tailscale: bind to tailnet interface
|
|
11
|
-
* SSH tunnel: ssh -N -L 18789:127.0.0.1:18789 user@host
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
buildIdentifyFilesPrompt,
|
|
16
|
-
buildProposeFixPrompt,
|
|
17
|
-
buildRefineOrAskPrompt,
|
|
18
|
-
buildCheckResolutionPrompt,
|
|
19
|
-
} from "./prompts.js";
|
|
20
|
-
|
|
21
|
-
const GATEWAY_URL = process.env.OPENCLAW_GATEWAY_URL?.replace(/\/$/, "");
|
|
22
|
-
const GATEWAY_TOKEN = process.env.OPENCLAW_TOKEN;
|
|
23
|
-
const TIMEOUT_MS = 120_000;
|
|
24
|
-
|
|
25
|
-
// ─── JSON output helpers ──────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
function extractJSON(text) {
|
|
28
|
-
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
29
|
-
if (fenced) return JSON.parse(fenced[1].trim());
|
|
30
|
-
const start = text.indexOf("{");
|
|
31
|
-
const end = text.lastIndexOf("}");
|
|
32
|
-
if (start !== -1 && end !== -1) return JSON.parse(text.slice(start, end + 1));
|
|
33
|
-
throw new Error("No JSON found in OpenClaw response");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ─── Gateway transport ────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Send a prompt to the OpenClaw Gateway and return the text response.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} prompt
|
|
42
|
-
* @param {string} [model]
|
|
43
|
-
* @param {{ signal?: AbortSignal }} [opts]
|
|
44
|
-
* @returns {Promise<string>}
|
|
45
|
-
*/
|
|
46
|
-
async function callGateway(prompt, model, opts = {}) {
|
|
47
|
-
if (!GATEWAY_URL) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
"OPENCLAW_GATEWAY_URL is not set. Add it to your .env and point it at your OpenClaw Gateway.",
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
55
|
-
// Chain the external cancel signal so a job cancel aborts the fetch in
|
|
56
|
-
// addition to the timeout.
|
|
57
|
-
const external = opts.signal;
|
|
58
|
-
const onExternalAbort = () => controller.abort();
|
|
59
|
-
if (external) {
|
|
60
|
-
if (external.aborted) controller.abort();
|
|
61
|
-
else external.addEventListener("abort", onExternalAbort, { once: true });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const body = { message: prompt };
|
|
66
|
-
if (model) body.model = model;
|
|
67
|
-
|
|
68
|
-
const res = await fetch(`${GATEWAY_URL}/api/v1/agent`, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: {
|
|
71
|
-
"Content-Type": "application/json",
|
|
72
|
-
...(GATEWAY_TOKEN ? { Authorization: `Bearer ${GATEWAY_TOKEN}` } : {}),
|
|
73
|
-
},
|
|
74
|
-
body: JSON.stringify(body),
|
|
75
|
-
signal: controller.signal,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (!res.ok) {
|
|
79
|
-
const text = await res.text().catch(() => "");
|
|
80
|
-
throw new Error(`OpenClaw gateway ${res.status}: ${text.slice(0, 300)}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const data = await res.json();
|
|
84
|
-
return data.result ?? data.message ?? data.content ?? data.text ?? JSON.stringify(data);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
if (err.name === "AbortError") {
|
|
87
|
-
// External cancellation wins over "timeout" framing so the terminal
|
|
88
|
-
// event the job emits reflects the user action, not a stale timer.
|
|
89
|
-
if (external?.aborted) throw err;
|
|
90
|
-
throw new Error("OpenClaw gateway timed out after 2 minutes");
|
|
91
|
-
}
|
|
92
|
-
throw err;
|
|
93
|
-
} finally {
|
|
94
|
-
clearTimeout(timer);
|
|
95
|
-
external?.removeEventListener("abort", onExternalAbort);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ─── Public API (mirrors claudeClient.js) ────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
export async function checkResolution(issue, diffs, model, opts = {}) {
|
|
102
|
-
const text = await callGateway(buildCheckResolutionPrompt(issue, diffs), model, opts);
|
|
103
|
-
const data = extractJSON(text);
|
|
104
|
-
return { ...data, cost: 0, serviceTier: "openclaw", inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export async function identifyRelevantFiles(issue, fileTree, model, opts = {}) {
|
|
108
|
-
const text = await callGateway(buildIdentifyFilesPrompt(issue, fileTree), model, opts);
|
|
109
|
-
const data = extractJSON(text);
|
|
110
|
-
return { ...data, cost: 0, serviceTier: "openclaw", inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export async function proposeFix(issue, fileContents, options = {}, model, opts = {}) {
|
|
114
|
-
const text = await callGateway(buildProposeFixPrompt(issue, fileContents, options), model, opts);
|
|
115
|
-
const data = extractJSON(text);
|
|
116
|
-
return { ...data, cost: 0, serviceTier: "openclaw", inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function refineOrAsk(issue, fileContents, options = {}, model, _onEvent, opts = {}) {
|
|
120
|
-
// OpenClaw is a single HTTP round-trip — no streaming surface. The onEvent
|
|
121
|
-
// callback is accepted for signature parity with claudeClient and ignored.
|
|
122
|
-
const text = await callGateway(buildRefineOrAskPrompt(issue, fileContents, options), model, opts);
|
|
123
|
-
const data = extractJSON(text);
|
|
124
|
-
return { ...data, cost: 0, serviceTier: "openclaw", inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
125
|
-
}
|
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/forge/core/prompts.js
|
|
3
|
-
*
|
|
4
|
-
* Shared prompt builders used by both claudeClient and openclawClient.
|
|
5
|
-
*
|
|
6
|
-
* Multi-repo awareness: when more than one repo is in play (full-stack fixes),
|
|
7
|
-
* every file reference carries a `repo` field so the model can target changes
|
|
8
|
-
* to the correct repository. Single-repo callers can still pass file trees /
|
|
9
|
-
* contents without a repo field — they are normalised before rendering.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const SELECT_FILES_SCHEMA = JSON.stringify({
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
files: {
|
|
16
|
-
type: "array",
|
|
17
|
-
items: {
|
|
18
|
-
type: "object",
|
|
19
|
-
properties: {
|
|
20
|
-
repo: { type: "string", description: "Repository id, as given in the 'Available repositories' list." },
|
|
21
|
-
path: { type: "string", description: "Path from that repo's root." },
|
|
22
|
-
},
|
|
23
|
-
required: ["repo", "path"],
|
|
24
|
-
},
|
|
25
|
-
description: "Minimum set of files relevant to the fix. Maximum 10 across all repos.",
|
|
26
|
-
},
|
|
27
|
-
reasoning: {
|
|
28
|
-
type: "string",
|
|
29
|
-
description: "One or two sentences explaining why these files are relevant and, if the fix spans multiple repos, why both are needed.",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
required: ["files", "reasoning"],
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export const PROPOSE_FIX_SCHEMA = JSON.stringify({
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {
|
|
38
|
-
title: {
|
|
39
|
-
type: "string",
|
|
40
|
-
description: "Conventional-commit MR title describing the actual fix, e.g. 'fix: resolve token refresh race condition in AuthService'. Max 72 chars. No issue number.",
|
|
41
|
-
},
|
|
42
|
-
explanation: {
|
|
43
|
-
type: "string",
|
|
44
|
-
description: "2-4 sentences on root cause and approach. If changes span multiple repos, describe how the pieces fit together.",
|
|
45
|
-
},
|
|
46
|
-
changes: {
|
|
47
|
-
type: "array",
|
|
48
|
-
items: {
|
|
49
|
-
type: "object",
|
|
50
|
-
properties: {
|
|
51
|
-
repo: { type: "string", description: "Repository id this change belongs to, exactly as given in the 'Available repositories' list." },
|
|
52
|
-
file: { type: "string", description: "Path from that repo's root." },
|
|
53
|
-
action: { type: "string", enum: ["create", "modify", "delete"] },
|
|
54
|
-
proposed: { type: "string", description: "For create/modify: the COMPLETE new file content. For delete: empty string." },
|
|
55
|
-
summary: { type: "string", description: "One sentence describing this file's change." },
|
|
56
|
-
},
|
|
57
|
-
required: ["repo", "file", "action", "proposed", "summary"],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
required: ["title", "explanation", "changes"],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const CI_FIX_SCHEMA = JSON.stringify({
|
|
65
|
-
type: "object",
|
|
66
|
-
properties: {
|
|
67
|
-
unfixable: {
|
|
68
|
-
type: "boolean",
|
|
69
|
-
description: "True ONLY when the failure cannot be resolved by any source-code or package.json change (e.g. Docker registry outage, runner OOM, flaky network). Default false.",
|
|
70
|
-
},
|
|
71
|
-
unfixableReason: {
|
|
72
|
-
type: "string",
|
|
73
|
-
description: "Required when unfixable=true. One sentence explaining why no code change can fix this.",
|
|
74
|
-
},
|
|
75
|
-
title: {
|
|
76
|
-
type: "string",
|
|
77
|
-
description: "Conventional-commit MR title describing the actual fix, e.g. 'fix: resolve token refresh race condition in AuthService'. Max 72 chars. No issue number.",
|
|
78
|
-
},
|
|
79
|
-
explanation: {
|
|
80
|
-
type: "string",
|
|
81
|
-
description: "2-4 sentences on root cause and approach. If changes span multiple repos, describe how the pieces fit together.",
|
|
82
|
-
},
|
|
83
|
-
changes: {
|
|
84
|
-
type: "array",
|
|
85
|
-
items: {
|
|
86
|
-
type: "object",
|
|
87
|
-
properties: {
|
|
88
|
-
repo: { type: "string", description: "Repository id this change belongs to, exactly as given in the 'Available repositories' list." },
|
|
89
|
-
file: { type: "string", description: "Path from that repo's root." },
|
|
90
|
-
action: { type: "string", enum: ["create", "modify", "delete"] },
|
|
91
|
-
proposed: { type: "string", description: "For create/modify: the COMPLETE new file content. For delete: empty string." },
|
|
92
|
-
summary: { type: "string", description: "One sentence describing this file's change." },
|
|
93
|
-
},
|
|
94
|
-
required: ["repo", "file", "action", "proposed", "summary"],
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
required: ["title", "explanation", "changes"],
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
export const REFINE_OR_ASK_SCHEMA = JSON.stringify({
|
|
102
|
-
type: "object",
|
|
103
|
-
properties: {
|
|
104
|
-
kind: {
|
|
105
|
-
type: "string",
|
|
106
|
-
enum: ["answer", "refine"],
|
|
107
|
-
description: "\"answer\" when the user is asking about state (lint status, readiness, rationale); \"refine\" when they are directing a code change.",
|
|
108
|
-
},
|
|
109
|
-
answer: {
|
|
110
|
-
type: "string",
|
|
111
|
-
description: "User-facing response (markdown OK). Always present. For refine, summarise what you changed; for answer, directly address the question grounded in validation results and the proposed changes.",
|
|
112
|
-
},
|
|
113
|
-
changes: {
|
|
114
|
-
type: "array",
|
|
115
|
-
description: "Only present when kind=refine. Same shape as PROPOSE_FIX_SCHEMA.changes.",
|
|
116
|
-
items: {
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: {
|
|
119
|
-
repo: { type: "string" },
|
|
120
|
-
file: { type: "string" },
|
|
121
|
-
action: { type: "string", enum: ["create", "modify", "delete"] },
|
|
122
|
-
proposed: { type: "string" },
|
|
123
|
-
summary: { type: "string" },
|
|
124
|
-
},
|
|
125
|
-
required: ["repo", "file", "action", "proposed", "summary"],
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
title: {
|
|
129
|
-
type: "string",
|
|
130
|
-
description: "Only when kind=refine. Updated conventional-commit MR title for the revised fix.",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
required: ["kind", "answer"],
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
export const RESOLUTION_CHECK_SCHEMA = JSON.stringify({
|
|
137
|
-
type: "object",
|
|
138
|
-
properties: {
|
|
139
|
-
resolved: {
|
|
140
|
-
type: "boolean",
|
|
141
|
-
description: "True if the changes fully address the issue.",
|
|
142
|
-
},
|
|
143
|
-
reasoning: {
|
|
144
|
-
type: "string",
|
|
145
|
-
description: "2-3 sentences explaining why the issue is or isn't resolved.",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
required: ["resolved", "reasoning"],
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Classifies an issue as "feature", "fix", or "chore" from its title, labels,
|
|
153
|
-
* and description. Used to select the right prompt guidance and file caps.
|
|
154
|
-
*
|
|
155
|
-
* Priority: conventional-commit title prefix > labels > description heuristics > default "fix".
|
|
156
|
-
*/
|
|
157
|
-
export function classifyIssue(issue) {
|
|
158
|
-
const title = (issue.title ?? "").toLowerCase();
|
|
159
|
-
const labels = (issue.labels ?? []).map((l) => (typeof l === "string" ? l : l.name ?? "").toLowerCase());
|
|
160
|
-
|
|
161
|
-
if (/^feat[\s(:/!]/.test(title)) return "feature";
|
|
162
|
-
if (/^fix[\s(:/!]/.test(title)) return "fix";
|
|
163
|
-
if (/^chore[\s(:/!]/.test(title)) return "chore";
|
|
164
|
-
if (/^refactor[\s(:/!]/.test(title)) return "refactor";
|
|
165
|
-
|
|
166
|
-
if (labels.some((l) => l.includes("feature") || l.includes("enhancement"))) return "feature";
|
|
167
|
-
if (labels.some((l) => l.includes("bug") || l.includes("defect"))) return "fix";
|
|
168
|
-
|
|
169
|
-
const desc = (issue.description ?? "").toLowerCase();
|
|
170
|
-
if (desc.includes("## approach") || desc.includes("## frontend changes") || desc.includes("## backend")) return "feature";
|
|
171
|
-
|
|
172
|
-
return "fix";
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Builds a human-readable block listing each repo's file tree.
|
|
177
|
-
* Accepts either:
|
|
178
|
-
* - a flat string[] (single-repo, legacy callers) — wrapped as one repo
|
|
179
|
-
* - an array of { repo, files: string[] } (multi-repo)
|
|
180
|
-
*/
|
|
181
|
-
function renderRepoTrees(repoTrees) {
|
|
182
|
-
const normalized = Array.isArray(repoTrees) && typeof repoTrees[0] === "string"
|
|
183
|
-
? [{ repo: "repo", files: repoTrees }]
|
|
184
|
-
: repoTrees;
|
|
185
|
-
|
|
186
|
-
return normalized
|
|
187
|
-
.map(({ repo, files }) =>
|
|
188
|
-
`### ${repo} (${files.length} files)\n${files.join("\n")}`,
|
|
189
|
-
)
|
|
190
|
-
.join("\n\n");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Builds the file-contents block. Accepts either a flat array
|
|
195
|
-
* (legacy: { path, content }) or multi-repo ({ repo, path, content }).
|
|
196
|
-
*/
|
|
197
|
-
function renderFileContents(fileContents) {
|
|
198
|
-
return fileContents
|
|
199
|
-
.map((f) => {
|
|
200
|
-
const header = f.repo ? `${f.repo} — ${f.path}` : f.path;
|
|
201
|
-
return `### ${header}\n\`\`\`\n${f.content}\n\`\`\``;
|
|
202
|
-
})
|
|
203
|
-
.join("\n\n");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function renderPreviousChanges(previousChanges) {
|
|
207
|
-
return previousChanges
|
|
208
|
-
.map((c) => {
|
|
209
|
-
const header = c.repo ? `${c.repo} — ${c.file} (${c.action})` : `${c.file} (${c.action})`;
|
|
210
|
-
return `### ${header}\n${c.summary}\n\`\`\`\n${c.proposed}\n\`\`\``;
|
|
211
|
-
})
|
|
212
|
-
.join("\n\n");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function buildIdentifyFilesPrompt(issue, repoTrees) {
|
|
216
|
-
const multi = Array.isArray(repoTrees) && typeof repoTrees[0] === "object";
|
|
217
|
-
const repoIds = multi
|
|
218
|
-
? repoTrees.map((r) => r.repo).join(", ")
|
|
219
|
-
: "repo";
|
|
220
|
-
|
|
221
|
-
const multiBlock = multi && repoTrees.length > 1
|
|
222
|
-
? `\n\n## Multi-repo guidance\nThese repositories are deployed together as a full-stack application. If the issue genuinely requires coordinated changes in more than one repo (e.g. a new API endpoint in the backend AND its client in the frontend), select files from each affected repo. If the fix is self-contained, pick files from only one repo.`
|
|
223
|
-
: "";
|
|
224
|
-
|
|
225
|
-
const kind = classifyIssue(issue);
|
|
226
|
-
const isFeature = kind === "feature" || kind === "refactor";
|
|
227
|
-
|
|
228
|
-
const selectionRules = isFeature
|
|
229
|
-
? `Selection rules (feature implementation):
|
|
230
|
-
- Focus on files that ALREADY EXIST and need modification to support the new feature.
|
|
231
|
-
- Include files that serve as PATTERNS for new files the issue asks you to create (e.g. an existing page component when a new page route is needed, an existing hook when a new hook is needed).
|
|
232
|
-
- Include shared registries and type files that need new entries (routes, nav items, type definitions, API endpoints).
|
|
233
|
-
- When the issue specifies exact file paths to modify, prefer those paths.
|
|
234
|
-
- Do NOT select files for changes the issue doesn't mention — follow the spec, don't invent work.
|
|
235
|
-
- If the issue references an existing UI element (e.g. sidebar, navbar) and only asks to ADD an entry, select the data/config file that drives that element, not the rendering component (unless the issue explicitly says to change rendering logic).`
|
|
236
|
-
: `Selection rules (bug fix):
|
|
237
|
-
- When a bug points at a failing assertion, guard, or validation (e.g. "undefined priceId", "X is not defined", "cannot read property of undefined"), include the IMMEDIATE CALLER(S) of that function — not just the file where the error is thrown. The bad value usually originates upstream.
|
|
238
|
-
- When a bug points at an event handler or React component, include the component that renders the handler AND the handler's implementation.
|
|
239
|
-
- Prefer fewer files over more, but never omit a caller when the reported symptom is a downstream consequence of input received from that caller.`;
|
|
240
|
-
|
|
241
|
-
const maxFiles = isFeature ? 15 : 10;
|
|
242
|
-
|
|
243
|
-
return `Respond with ONLY a raw JSON object — no markdown, no prose, no code fences.
|
|
244
|
-
|
|
245
|
-
Schema: {"files": [{"repo": "<repo id>", "path": "src/foo/bar.ts"}], "reasoning": "one or two sentences"}
|
|
246
|
-
|
|
247
|
-
Identify the ${isFeature ? "set" : "minimum set"} of source files (max ${maxFiles} across all repos) relevant to ${isFeature ? "implementing" : "fixing"} this issue.
|
|
248
|
-
Always use an exact repo id from the "Available repositories" list below — do not invent or abbreviate.
|
|
249
|
-
|
|
250
|
-
${selectionRules}
|
|
251
|
-
|
|
252
|
-
## Issue #${issue.iid}: ${issue.title}
|
|
253
|
-
${issue.description || "(no description)"}
|
|
254
|
-
Labels: ${issue.labels?.join(", ") || "none"}
|
|
255
|
-
|
|
256
|
-
## Available repositories
|
|
257
|
-
${repoIds}${multiBlock}
|
|
258
|
-
|
|
259
|
-
## Repository file trees
|
|
260
|
-
${renderRepoTrees(repoTrees)}`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export function buildProposeFixPrompt(issue, fileContents, options = {}) {
|
|
264
|
-
const { feedback, previousChanges, ciContext, userPrompt, repos, promptRules } = options;
|
|
265
|
-
|
|
266
|
-
const filesBlock = renderFileContents(fileContents);
|
|
267
|
-
const kind = classifyIssue(issue);
|
|
268
|
-
const isFeature = kind === "feature" || kind === "refactor";
|
|
269
|
-
|
|
270
|
-
const previousBlock = previousChanges?.length
|
|
271
|
-
? `\n\n## Previous proposed changes\n${renderPreviousChanges(previousChanges)}`
|
|
272
|
-
: "";
|
|
273
|
-
|
|
274
|
-
const feedbackBlock = feedback?.trim()
|
|
275
|
-
? `\n\n## Reviewer feedback\n${feedback.trim()}\n\nAdjust the proposed changes to address this feedback.`
|
|
276
|
-
: "";
|
|
277
|
-
|
|
278
|
-
const ciBlock = ciContext?.trim()
|
|
279
|
-
? `\n\n## CI pipeline failure logs
|
|
280
|
-
The pipeline failed. Read the logs carefully and classify the failure:
|
|
281
|
-
|
|
282
|
-
1. **Code/test failure** — broken imports, type errors, failing assertions → propose the source-code fix.
|
|
283
|
-
2. **Dependency audit failure** (npm audit, yarn audit) — a package has a known vulnerability → update the version constraint in package.json. Do NOT touch package-lock.json; the lock file will be regenerated automatically.
|
|
284
|
-
3. **Unfixable infrastructure failure** — Docker registry outage, runner OOM, network timeout, flaky CI runner → set "unfixable": true and explain why in "unfixableReason". Return an empty "changes" array.
|
|
285
|
-
|
|
286
|
-
${ciContext.trim()}`
|
|
287
|
-
: "";
|
|
288
|
-
|
|
289
|
-
const userBlock = userPrompt?.trim()
|
|
290
|
-
? `\n\n## Additional instructions\n${userPrompt.trim()}`
|
|
291
|
-
: "";
|
|
292
|
-
|
|
293
|
-
const reposLine = repos?.length
|
|
294
|
-
? `\n\n## Available repositories\n${repos.join(", ")}\n\nEvery "repo" field in your response MUST be one of these exact ids.`
|
|
295
|
-
: "";
|
|
296
|
-
|
|
297
|
-
const multiNote = repos && repos.length > 1
|
|
298
|
-
? `\n\n## Multi-repo guidance\nThis is a full-stack change. If the fix requires coordinated updates in more than one repo, produce changes tagged with the correct repo for each. Make sure API shapes, types, and field names agree across repos.`
|
|
299
|
-
: "";
|
|
300
|
-
|
|
301
|
-
const rulesBlock = promptRules?.trim()
|
|
302
|
-
? `\n\n## Custom rules (from project settings — always follow these)\n${promptRules.trim()}`
|
|
303
|
-
: "";
|
|
304
|
-
|
|
305
|
-
const titlePrefix = isFeature ? "feat" : "fix";
|
|
306
|
-
const titleExample = isFeature
|
|
307
|
-
? `"feat: add browseable mixes section with platinum-gated publishing"`
|
|
308
|
-
: `"fix: resolve token refresh race condition in AuthService"`;
|
|
309
|
-
|
|
310
|
-
const collateralGuard = `COLLATERAL DAMAGE GUARD — read BEFORE writing any "proposed" content:
|
|
311
|
-
Your default when modifying a file is to PRESERVE every existing line that is not directly related to the issue. Do not silently remove, reorganize, or simplify wrapper elements, imports, sibling components, or unrelated UI features (version displays, badges, icons, etc.).
|
|
312
|
-
If you believe existing code MUST be removed or restructured for the change to work, you MUST declare each removal in the top-level "removals" array. Each entry needs: the file path, a short description of what was removed, and why the removal is necessary. An empty "removals" array (or omitting it) means "I preserved everything unrelated."
|
|
313
|
-
The reviewer will see these as warnings and must acknowledge them before the change can be committed — so never silently drop existing functionality.`;
|
|
314
|
-
|
|
315
|
-
const discipline = isFeature
|
|
316
|
-
? `Feature implementation rules (read BEFORE writing any change):
|
|
317
|
-
1. Follow the issue specification precisely. If the issue says to modify a specific file, modify that file. If it says to create a new file, create it. Do NOT invent changes the issue doesn't ask for.
|
|
318
|
-
2. When the issue references existing components or patterns to reuse, base your implementation on the existing patterns in the "Relevant files" section.
|
|
319
|
-
3. For new files, follow the naming conventions, import style, and structure of the existing codebase.
|
|
320
|
-
4. Be aware of existing routes, tabs, and UI sections that may OVERLAP with what you're implementing. If the issue creates a new "/mixes" route, check whether the codebase already has mixes-related UI elsewhere (e.g. a tab inside another page) and note any conflict in "explanation".
|
|
321
|
-
5. In your "explanation", state (a) which existing patterns you followed, (b) what new files you created, and (c) any conflicts or considerations the reviewer should know about.
|
|
322
|
-
6. ${collateralGuard}`
|
|
323
|
-
: `Root-cause discipline (read BEFORE writing any change):
|
|
324
|
-
1. Identify the ORIGIN of the bad value, not just where it's observed. If a guard, assertion, or validation is failing, trace the value one or two frames up and name the caller that produced the bad input.
|
|
325
|
-
2. A fix that only makes an error message clearer (e.g. replacing \`assertIsDefined(x)\` with \`if (!x) toast.error(…)\`) is NOT a fix — it is a symptom suppressor. Reject it and propose the upstream fix instead.
|
|
326
|
-
3. In your "explanation", state (a) where the bad value originates, (b) why the fix closes the reported symptom end-to-end, and (c) what would have to be true for the reported error to still appear after your change.
|
|
327
|
-
4. If the origin is in a file that was not selected in pass 1, your "changes" should include that file. Do not constrain yourself to only the files listed under "Relevant files" when doing so would force a symptom-level patch.
|
|
328
|
-
5. ${collateralGuard}`;
|
|
329
|
-
|
|
330
|
-
return `Respond with ONLY a raw JSON object — no markdown, no prose, no code fences.
|
|
331
|
-
|
|
332
|
-
Schema: {"title": "${titlePrefix}: concise description of the actual change (max 72 chars)", "explanation": "...", "changes": [{"repo": "<repo id>", "file": "...", "action": "modify|create|delete", "proposed": "<full file content>", "summary": "..."}], "removals": [{"file": "...", "what": "short description of removed code", "why": "reason this removal is necessary"}]}
|
|
333
|
-
|
|
334
|
-
${isFeature ? "Implement this feature." : "Propose a targeted fix for this issue."}
|
|
335
|
-
|
|
336
|
-
${discipline}
|
|
337
|
-
|
|
338
|
-
## Issue #${issue.iid}: ${issue.title}
|
|
339
|
-
${issue.description || "(no description)"}
|
|
340
|
-
Labels: ${issue.labels?.join(", ") || "none"}${reposLine}${multiNote}${rulesBlock}${previousBlock}${feedbackBlock}${ciBlock}${userBlock}
|
|
341
|
-
|
|
342
|
-
## Relevant files
|
|
343
|
-
${filesBlock}`;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Renders the latest pre-flight validation state (lint/typecheck/test) so the
|
|
348
|
-
* refine model can ground answers like "are lint issues fixed?" in real data.
|
|
349
|
-
* Omits silently when no state is available — the prompt will tell the model
|
|
350
|
-
* to say "validation hasn't been run yet" in that case.
|
|
351
|
-
*/
|
|
352
|
-
function renderValidationState(state) {
|
|
353
|
-
if (!state) return "";
|
|
354
|
-
const lines = [`## Latest pre-flight validation (${state.checkedAt ?? "unknown time"})`];
|
|
355
|
-
if (state.allPassed === true) lines.push("Overall: **all checks passed**");
|
|
356
|
-
else if (state.allPassed === false) lines.push("Overall: **some checks failed**");
|
|
357
|
-
const steps = state.steps ?? {};
|
|
358
|
-
for (const name of ["lint", "typecheck", "test"]) {
|
|
359
|
-
const s = steps[name];
|
|
360
|
-
if (!s) continue;
|
|
361
|
-
const status = s.skipped ? "skipped" : s.status ?? (s.passed ? "passed" : "failed");
|
|
362
|
-
const detail = s.exitCode != null ? ` (exit ${s.exitCode})` : "";
|
|
363
|
-
lines.push(`- **${name}**: ${status}${detail}`);
|
|
364
|
-
if (!s.passed && !s.skipped && s.tail) {
|
|
365
|
-
lines.push(` Last output:\n\`\`\`\n${String(s.tail).split("\n").slice(-20).join("\n")}\n\`\`\``);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return "\n\n" + lines.join("\n");
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Single-prompt conversational refine: the model auto-classifies the user's
|
|
373
|
-
* message as a question about state or a directive to change code, then
|
|
374
|
-
* responds with EITHER a grounded answer OR a fresh changes[] payload.
|
|
375
|
-
*/
|
|
376
|
-
export function buildRefineOrAskPrompt(issue, fileContents, options = {}) {
|
|
377
|
-
const { feedback, previousChanges, validationState, repos, repoTrees, repoAccessMode, promptRules } = options;
|
|
378
|
-
|
|
379
|
-
const filesBlock = renderFileContents(fileContents);
|
|
380
|
-
const previousBlock = previousChanges?.length
|
|
381
|
-
? `\n\n## Proposed changes so far\n${renderPreviousChanges(previousChanges)}`
|
|
382
|
-
: "";
|
|
383
|
-
const validationBlock = renderValidationState(validationState);
|
|
384
|
-
const reposLine = repos?.length
|
|
385
|
-
? `\n\n## Available repositories\n${repos.join(", ")}\n\nEvery "repo" field in a refine response MUST be one of these exact ids.`
|
|
386
|
-
: "";
|
|
387
|
-
|
|
388
|
-
const treeBlock = repoTrees?.length
|
|
389
|
-
? `\n\n## Repository file trees\n${renderRepoTrees(repoTrees)}`
|
|
390
|
-
: "";
|
|
391
|
-
|
|
392
|
-
const accessBlock = repoAccessMode === "tools"
|
|
393
|
-
? `\n\n## Repo access\nThe current working directory is a checkout of the primary repo with the proposed changes already overlaid. Use the Read, Glob, and Grep tools to inspect any file you need before answering or refining. Do NOT claim you lack repo access.`
|
|
394
|
-
: `\n\n## Repo access\nYou have the full file tree above and the complete content of every file currently in scope. Treat these as authoritative. If you need a file not attached, name the path and ask the user to share it — do NOT claim you lack repo access.`;
|
|
395
|
-
|
|
396
|
-
const rulesBlock = promptRules?.trim()
|
|
397
|
-
? `\n\n## Custom rules (from project settings — always follow these)\n${promptRules.trim()}`
|
|
398
|
-
: "";
|
|
399
|
-
|
|
400
|
-
return `Respond with ONLY a raw JSON object — no markdown, no prose, no code fences.
|
|
401
|
-
|
|
402
|
-
Schema: {
|
|
403
|
-
"kind": "answer" | "refine",
|
|
404
|
-
"answer": "your user-facing reply (markdown ok)",
|
|
405
|
-
"title": "fix: ...", // refine only
|
|
406
|
-
"changes": [ { "repo": "...", "file": "...", "action": "modify|create|delete", "proposed": "<full file>", "summary": "..." } ] // refine only
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
## How to classify
|
|
410
|
-
|
|
411
|
-
Set kind="answer" when the user is asking about the current state of the work. Examples:
|
|
412
|
-
- "are lint issues fixed?"
|
|
413
|
-
- "is this safe to merge?"
|
|
414
|
-
- "what did you change?"
|
|
415
|
-
- "why this approach?"
|
|
416
|
-
Ground your answer in the validation state below, the proposed changes, and any file you read from the working tree. Be direct. If validation hasn't been run, say so and recommend running it. Do NOT produce a "changes" array on this path.
|
|
417
|
-
|
|
418
|
-
Set kind="refine" when the user is directing a code change. Examples:
|
|
419
|
-
- "use the existing log utility"
|
|
420
|
-
- "split this into two functions"
|
|
421
|
-
- "handle null here"
|
|
422
|
-
- "fix the typecheck error: Foo is not assignable to Bar"
|
|
423
|
-
Produce a complete updated "changes" array addressing the feedback, plus an "answer" that briefly summarises what you changed (one or two sentences). Every file you modify must be a COMPLETE file in "proposed" — never a diff.
|
|
424
|
-
|
|
425
|
-
If the message is ambiguous, prefer "answer" and ask a clarifying question.
|
|
426
|
-
|
|
427
|
-
## Issue #${issue.iid}: ${issue.title}
|
|
428
|
-
${issue.description || "(no description)"}
|
|
429
|
-
Labels: ${issue.labels?.join(", ") || "none"}${reposLine}${accessBlock}${treeBlock}${rulesBlock}${previousBlock}${validationBlock}
|
|
430
|
-
|
|
431
|
-
## Relevant files (current state as of the proposed changes)
|
|
432
|
-
${filesBlock}
|
|
433
|
-
|
|
434
|
-
## User message
|
|
435
|
-
${feedback?.trim() || "(empty)"}`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Slim continuation prompt for refine turns 2+ when the Claude session is
|
|
440
|
-
* being resumed via `--resume <id>`. The model already has the issue, repo
|
|
441
|
-
* access, prior proposed changes, and full file contents from the first
|
|
442
|
-
* turn — repeating them would waste tokens and dilute the cache hit. We only
|
|
443
|
-
* inject what's new: the user's message and the latest validation state.
|
|
444
|
-
*/
|
|
445
|
-
export function buildRefineResumePrompt(options = {}) {
|
|
446
|
-
const { feedback, validationState } = options;
|
|
447
|
-
const validationBlock = renderValidationState(validationState);
|
|
448
|
-
return `Continuing the same refine session. Same schema as before — respond with ONLY a raw JSON object matching { kind, answer, title?, changes? }. No prose, no code fences.
|
|
449
|
-
|
|
450
|
-
If you need a file you haven't read yet, use Read / Glob / Grep against the working tree as before.${validationBlock}
|
|
451
|
-
|
|
452
|
-
## User message
|
|
453
|
-
${feedback?.trim() || "(empty)"}`;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
export function buildCheckResolutionPrompt(issue, diffs) {
|
|
457
|
-
// Budget: 24K chars total for diffs, split evenly across files so small
|
|
458
|
-
// MRs (1-3 files) get enough per-file context to see route registrations,
|
|
459
|
-
// handler bodies, etc. — previously 800/file caused false negatives on
|
|
460
|
-
// any file >20 lines.
|
|
461
|
-
const TOTAL_DIFF_BUDGET = 24000;
|
|
462
|
-
const perFile = Math.max(2000, Math.floor(TOTAL_DIFF_BUDGET / diffs.length));
|
|
463
|
-
const diffBlock = diffs
|
|
464
|
-
.map((d) => {
|
|
465
|
-
const path = d.new_path !== d.old_path ? `${d.old_path} → ${d.new_path}` : d.new_path;
|
|
466
|
-
const label = d.new_file ? "created" : d.deleted_file ? "deleted" : "modified";
|
|
467
|
-
const snippet = d.diff ? d.diff.slice(0, perFile) + (d.diff.length > perFile ? "\n… (truncated)" : "") : "(binary or empty)";
|
|
468
|
-
return `### ${path} (${label})\n\`\`\`diff\n${snippet}\n\`\`\``;
|
|
469
|
-
})
|
|
470
|
-
.join("\n\n");
|
|
471
|
-
|
|
472
|
-
return `Respond with ONLY a raw JSON object — no markdown, no prose, no code fences.
|
|
473
|
-
|
|
474
|
-
Schema: {"resolved": true|false, "reasoning": "2-3 sentences"}
|
|
475
|
-
|
|
476
|
-
Determine whether the following code changes fully resolve the reported issue.
|
|
477
|
-
|
|
478
|
-
## Issue #${issue.iid}: ${issue.title}
|
|
479
|
-
${issue.description || "(no description)"}
|
|
480
|
-
|
|
481
|
-
## Changes made in the merged MR
|
|
482
|
-
${diffBlock}`;
|
|
483
|
-
}
|