@glrs-dev/cli 2.1.0 → 2.3.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/CHANGELOG.md +4 -0
- package/dist/{chunk-SB3MLROC.js → chunk-MIWZLETC.js} +7 -2
- package/dist/cli.js +1 -1
- package/dist/lib/auto-update.js +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/build.md +34 -4
- package/dist/vendor/harness-opencode/dist/agents/prompts/build.open.md +18 -4
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer-thorough.md +77 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer.md +80 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer.open.md +68 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/debriefer.md +55 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/gap-analyzer.md +2 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan-reviewer.md +5 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +119 -10
- package/dist/vendor/harness-opencode/dist/agents/prompts/prime.md +149 -88
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-auto.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-local.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-web.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research.md +2 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/scoper.md +129 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.md +53 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.open.md +56 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/index.ts +1 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/ui-evaluation-ladder.md +50 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/workflow-mechanics.md +5 -5
- package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +104 -0
- package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +259 -0
- package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +87 -0
- package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +544 -0
- package/dist/vendor/harness-opencode/dist/{chunk-VJUETC6A.js → chunk-PDMXYZM4.js} +53 -1
- package/dist/vendor/harness-opencode/dist/cli.js +1596 -1964
- package/dist/vendor/harness-opencode/dist/commands/prompts/fresh.md +27 -24
- package/dist/vendor/harness-opencode/dist/commands/prompts/review.md +3 -3
- package/dist/vendor/harness-opencode/dist/commands/prompts/ship.md +2 -0
- package/dist/vendor/harness-opencode/dist/index.js +188 -633
- package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +30 -0
- package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +22 -0
- package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +6 -0
- package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +117 -0
- package/dist/vendor/harness-opencode/dist/scoper-S77SOK7X.js +326 -0
- package/dist/vendor/harness-opencode/dist/skills/adversarial-review-rubric/SKILL.md +47 -0
- package/dist/vendor/harness-opencode/dist/skills/code-quality/SKILL.md +1 -1
- package/dist/vendor/harness-opencode/dist/skills/root-cause-diagnosis/SKILL.md +24 -0
- package/dist/vendor/harness-opencode/dist/skills/spear-protocol/SKILL.md +167 -0
- package/dist/vendor/harness-opencode/package.json +1 -1
- package/package.json +3 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-assessor.md +0 -77
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-builder.md +0 -40
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-planner.md +0 -56
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-scoper.md +0 -58
- package/dist/vendor/harness-opencode/dist/agents/prompts/qa-reviewer.md +0 -68
- package/dist/vendor/harness-opencode/dist/agents/prompts/qa-reviewer.open.md +0 -58
- package/dist/vendor/harness-opencode/dist/agents/prompts/qa-thorough.md +0 -63
- package/dist/vendor/harness-opencode/dist/bin/plan-check.sh +0 -255
- package/dist/vendor/harness-opencode/dist/chunk-6CZPRUMJ.js +0 -869
- package/dist/vendor/harness-opencode/dist/chunk-DZG4D3OH.js +0 -54
- package/dist/vendor/harness-opencode/dist/chunk-OYRKOEXK.js +0 -88
- package/dist/vendor/harness-opencode/dist/commands/prompts/autopilot.md +0 -96
- package/dist/vendor/harness-opencode/dist/install-6775ZBDG.js +0 -13
- package/dist/vendor/harness-opencode/dist/paths-WZ23ZQOV.js +0 -18
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// src/lib/opencode-server.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import {
|
|
5
|
+
createOpencodeServer,
|
|
6
|
+
createOpencodeClient
|
|
7
|
+
} from "@opencode-ai/sdk";
|
|
8
|
+
var execFileP = promisify(execFile);
|
|
9
|
+
var DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
|
|
10
|
+
async function ensureOpencodeOnPath() {
|
|
11
|
+
try {
|
|
12
|
+
await execFileP("opencode", ["--version"]);
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"opencode CLI not found on PATH.\n Install: https://opencode.ai\n Or: bunx opencode upgrade"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function startServer(opts) {
|
|
20
|
+
await ensureOpencodeOnPath();
|
|
21
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
22
|
+
const port = opts.port ?? 0;
|
|
23
|
+
const server = await createOpencodeServer({
|
|
24
|
+
port,
|
|
25
|
+
timeout: timeoutMs,
|
|
26
|
+
hostname: "127.0.0.1"
|
|
27
|
+
});
|
|
28
|
+
const client = createOpencodeClient({ baseUrl: server.url });
|
|
29
|
+
let shutdownCalled = false;
|
|
30
|
+
const shutdown = async () => {
|
|
31
|
+
if (shutdownCalled) return;
|
|
32
|
+
shutdownCalled = true;
|
|
33
|
+
try {
|
|
34
|
+
await server.close();
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return { url: server.url, client, shutdown };
|
|
39
|
+
}
|
|
40
|
+
async function selfTest(client) {
|
|
41
|
+
try {
|
|
42
|
+
await client.session.list();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`OpenCode server self-test failed \u2014 the server started but isn't responding to API calls.
|
|
46
|
+
Error: ${err instanceof Error ? err.message : String(err)}
|
|
47
|
+
Run \`opencode --version\` to verify your installation.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function createSession(client, opts) {
|
|
52
|
+
const result = await client.session.create({
|
|
53
|
+
query: { directory: opts.cwd },
|
|
54
|
+
body: {}
|
|
55
|
+
});
|
|
56
|
+
if (!result.data) {
|
|
57
|
+
throw new Error("session.create returned no data");
|
|
58
|
+
}
|
|
59
|
+
return result.data.id;
|
|
60
|
+
}
|
|
61
|
+
async function sendAndWait(client, opts) {
|
|
62
|
+
const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
|
|
63
|
+
const idlePromise = waitForIdle(client, {
|
|
64
|
+
sessionId: opts.sessionId,
|
|
65
|
+
stallMs,
|
|
66
|
+
abortSignal: opts.abortSignal,
|
|
67
|
+
onToolCall: opts.onToolCall,
|
|
68
|
+
onTextDelta: opts.onTextDelta,
|
|
69
|
+
onCostUpdate: opts.onCostUpdate,
|
|
70
|
+
autoRejectPermissions: opts.autoRejectPermissions,
|
|
71
|
+
serverUrl: opts.serverUrl,
|
|
72
|
+
onPermissionRejected: opts.onPermissionRejected
|
|
73
|
+
});
|
|
74
|
+
await client.session.prompt({
|
|
75
|
+
path: { id: opts.sessionId },
|
|
76
|
+
body: {
|
|
77
|
+
parts: [{ type: "text", text: opts.message }],
|
|
78
|
+
...opts.agentName ? { agent: opts.agentName } : {}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return idlePromise;
|
|
82
|
+
}
|
|
83
|
+
async function waitForIdle(client, opts) {
|
|
84
|
+
const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
|
|
85
|
+
const sse = await client.event.subscribe();
|
|
86
|
+
const reportedToolCalls = /* @__PURE__ */ new Set();
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let stallTimer = null;
|
|
89
|
+
let settled = false;
|
|
90
|
+
const settle = (result) => {
|
|
91
|
+
if (settled) return;
|
|
92
|
+
settled = true;
|
|
93
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
94
|
+
resolve(result);
|
|
95
|
+
};
|
|
96
|
+
const resetStall = () => {
|
|
97
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
98
|
+
stallTimer = setTimeout(() => settle({ kind: "stall", stallMs }), stallMs);
|
|
99
|
+
};
|
|
100
|
+
if (opts.abortSignal) {
|
|
101
|
+
if (opts.abortSignal.aborted) {
|
|
102
|
+
settle({ kind: "abort" });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
opts.abortSignal.addEventListener("abort", () => settle({ kind: "abort" }), { once: true });
|
|
106
|
+
}
|
|
107
|
+
resetStall();
|
|
108
|
+
(async () => {
|
|
109
|
+
try {
|
|
110
|
+
for await (const event of sse.stream) {
|
|
111
|
+
if (settled) break;
|
|
112
|
+
const ev = event;
|
|
113
|
+
const props = ev.properties ?? {};
|
|
114
|
+
const type = ev.type ?? "";
|
|
115
|
+
if (opts.onCostUpdate && type === "message.updated") {
|
|
116
|
+
const info = props["info"];
|
|
117
|
+
if (info && info.role === "assistant" && typeof info.cost === "number") {
|
|
118
|
+
resetStall();
|
|
119
|
+
try {
|
|
120
|
+
opts.onCostUpdate(
|
|
121
|
+
info.cost,
|
|
122
|
+
{
|
|
123
|
+
input: info.tokens?.input ?? 0,
|
|
124
|
+
output: info.tokens?.output ?? 0
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (opts.onTextDelta && (type === "message.part.delta" || type === "message.part.updated")) {
|
|
132
|
+
const delta = props["delta"];
|
|
133
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
134
|
+
resetStall();
|
|
135
|
+
try {
|
|
136
|
+
opts.onTextDelta(delta.length);
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (opts.onToolCall && type === "message.part.updated") {
|
|
142
|
+
const part = props["part"];
|
|
143
|
+
if (part && part.type === "tool" && part.sessionID === opts.sessionId && part.state?.status === "completed" && part.callID && !reportedToolCalls.has(part.callID)) {
|
|
144
|
+
reportedToolCalls.add(part.callID);
|
|
145
|
+
resetStall();
|
|
146
|
+
try {
|
|
147
|
+
opts.onToolCall(part.tool ?? "unknown");
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const eventSessionId = props["sessionID"];
|
|
154
|
+
if (opts.autoRejectPermissions && (type === "permission.updated" || type === "question.asked")) {
|
|
155
|
+
const permissionId = props["id"];
|
|
156
|
+
const permissionType = type === "question.asked" ? "question" : props["type"] ?? "unknown";
|
|
157
|
+
const permissionTitle = props["title"] ?? "";
|
|
158
|
+
if (opts.onPermissionRejected) {
|
|
159
|
+
try {
|
|
160
|
+
opts.onPermissionRejected({
|
|
161
|
+
id: permissionId ?? "unknown",
|
|
162
|
+
type: permissionType,
|
|
163
|
+
title: permissionTitle
|
|
164
|
+
});
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (type === "permission.updated" && permissionId) {
|
|
169
|
+
(async () => {
|
|
170
|
+
try {
|
|
171
|
+
await client.postSessionIdPermissionsPermissionId({
|
|
172
|
+
path: { id: opts.sessionId, permissionID: permissionId },
|
|
173
|
+
body: { response: "reject" }
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (type === "question.asked") {
|
|
181
|
+
if (opts.serverUrl && permissionId) {
|
|
182
|
+
(async () => {
|
|
183
|
+
try {
|
|
184
|
+
await fetch(`${opts.serverUrl}/question/${permissionId}/reject`, {
|
|
185
|
+
method: "POST"
|
|
186
|
+
});
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
})();
|
|
190
|
+
}
|
|
191
|
+
settle({
|
|
192
|
+
kind: "question_rejected",
|
|
193
|
+
title: permissionTitle
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (eventSessionId !== opts.sessionId) continue;
|
|
199
|
+
resetStall();
|
|
200
|
+
if (type === "session.idle") {
|
|
201
|
+
settle({ kind: "idle" });
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (type === "session.error") {
|
|
205
|
+
const msg = props["message"] ?? "session error";
|
|
206
|
+
settle({ kind: "error", message: msg });
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (!settled) {
|
|
212
|
+
settle({ kind: "error", message: err instanceof Error ? err.message : String(err) });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function getSessionCost(client, sessionId) {
|
|
219
|
+
try {
|
|
220
|
+
const result = await client.session.messages({ path: { id: sessionId } });
|
|
221
|
+
if (!result.data) return 0;
|
|
222
|
+
const messages = result.data;
|
|
223
|
+
let total = 0;
|
|
224
|
+
for (const m of messages) {
|
|
225
|
+
if (m.info.role === "assistant" && typeof m.info.cost === "number") {
|
|
226
|
+
total += m.info.cost;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return total;
|
|
230
|
+
} catch {
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function getLastAssistantMessage(client, sessionId) {
|
|
235
|
+
try {
|
|
236
|
+
const result = await client.session.messages({ path: { id: sessionId } });
|
|
237
|
+
if (!result.data) return "";
|
|
238
|
+
const messages = result.data;
|
|
239
|
+
const assistantMessages = messages.filter((m) => m.info.role === "assistant");
|
|
240
|
+
if (assistantMessages.length === 0) return "";
|
|
241
|
+
const last = assistantMessages[assistantMessages.length - 1];
|
|
242
|
+
if (!last) return "";
|
|
243
|
+
return last.parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
244
|
+
} catch {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export {
|
|
250
|
+
execFileP,
|
|
251
|
+
DEFAULT_STARTUP_TIMEOUT_MS,
|
|
252
|
+
startServer,
|
|
253
|
+
selfTest,
|
|
254
|
+
createSession,
|
|
255
|
+
sendAndWait,
|
|
256
|
+
waitForIdle,
|
|
257
|
+
getSessionCost,
|
|
258
|
+
getLastAssistantMessage
|
|
259
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/autopilot/plan-parser.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
var DEGRADED = {
|
|
5
|
+
type: "single",
|
|
6
|
+
totalItems: 0,
|
|
7
|
+
checkedItems: 0,
|
|
8
|
+
phaseCount: 0,
|
|
9
|
+
phasesCompleted: 0,
|
|
10
|
+
phases: []
|
|
11
|
+
};
|
|
12
|
+
function countCheckboxes(content) {
|
|
13
|
+
let total = 0;
|
|
14
|
+
let checked = 0;
|
|
15
|
+
const checkboxRe = /^[ \t]*-\s+\[([ xX])\]/gm;
|
|
16
|
+
let match;
|
|
17
|
+
while ((match = checkboxRe.exec(content)) !== null) {
|
|
18
|
+
total++;
|
|
19
|
+
if (match[1] !== " ") {
|
|
20
|
+
checked++;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { total, checked };
|
|
24
|
+
}
|
|
25
|
+
function parseSingleFile(filePath) {
|
|
26
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
27
|
+
return countCheckboxes(content);
|
|
28
|
+
}
|
|
29
|
+
function detectPhaseFiles(dir) {
|
|
30
|
+
const entries = fs.readdirSync(dir);
|
|
31
|
+
return entries.filter((f) => /^phase_\d+\.md$/.test(f)).sort((a, b) => {
|
|
32
|
+
const na = parseInt(a.replace(/[^0-9]/g, ""), 10);
|
|
33
|
+
const nb = parseInt(b.replace(/[^0-9]/g, ""), 10);
|
|
34
|
+
return na - nb;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function parseMultiFile(dir) {
|
|
38
|
+
const mainPath = path.join(dir, "main.md");
|
|
39
|
+
const mainContent = fs.readFileSync(mainPath, "utf8");
|
|
40
|
+
const mainCounts = countCheckboxes(mainContent);
|
|
41
|
+
const phaseFiles = detectPhaseFiles(dir);
|
|
42
|
+
const phases = [];
|
|
43
|
+
let phasesCompleted = 0;
|
|
44
|
+
for (const phaseFile of phaseFiles) {
|
|
45
|
+
const phasePath = path.join(dir, phaseFile);
|
|
46
|
+
const { total, checked } = parseSingleFile(phasePath);
|
|
47
|
+
phases.push({ file: phaseFile, totalItems: total, checkedItems: checked });
|
|
48
|
+
if (total > 0 && checked === total) {
|
|
49
|
+
phasesCompleted++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type: "multi",
|
|
54
|
+
totalItems: mainCounts.total,
|
|
55
|
+
checkedItems: mainCounts.checked,
|
|
56
|
+
phaseCount: phaseFiles.length,
|
|
57
|
+
phasesCompleted,
|
|
58
|
+
phases
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function parsePlanState(planPath) {
|
|
62
|
+
try {
|
|
63
|
+
const stat = fs.statSync(planPath);
|
|
64
|
+
if (stat.isDirectory()) {
|
|
65
|
+
const mainPath = path.join(planPath, "main.md");
|
|
66
|
+
if (fs.existsSync(mainPath)) {
|
|
67
|
+
return parseMultiFile(planPath);
|
|
68
|
+
}
|
|
69
|
+
return { ...DEGRADED, type: "multi" };
|
|
70
|
+
}
|
|
71
|
+
const { total, checked } = parseSingleFile(planPath);
|
|
72
|
+
return {
|
|
73
|
+
type: "single",
|
|
74
|
+
totalItems: total,
|
|
75
|
+
checkedItems: checked,
|
|
76
|
+
phaseCount: 0,
|
|
77
|
+
phasesCompleted: 0,
|
|
78
|
+
phases: []
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return { ...DEGRADED };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
parsePlanState
|
|
87
|
+
};
|