@lucasreiners/lead 0.1.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 +337 -0
- package/dist/agents/agent-builder.d.ts +30 -0
- package/dist/agents/architect/index.d.ts +4 -0
- package/dist/agents/builtin-agents.d.ts +32 -0
- package/dist/agents/code-analyst/index.d.ts +4 -0
- package/dist/agents/cto/index.d.ts +11 -0
- package/dist/agents/custom-agent-factory.d.ts +27 -0
- package/dist/agents/dynamic-prompt-builder.d.ts +22 -0
- package/dist/agents/engineer/index.d.ts +4 -0
- package/dist/agents/executor/index.d.ts +6 -0
- package/dist/agents/guardian/index.d.ts +4 -0
- package/dist/agents/index.d.ts +17 -0
- package/dist/agents/lead/index.d.ts +11 -0
- package/dist/agents/lead-dev/index.d.ts +6 -0
- package/dist/agents/model-resolution.d.ts +44 -0
- package/dist/agents/prompt-loader.d.ts +30 -0
- package/dist/agents/prompt-utils.d.ts +12 -0
- package/dist/agents/researcher/index.d.ts +4 -0
- package/dist/agents/reviewer/index.d.ts +4 -0
- package/dist/agents/scout/index.d.ts +4 -0
- package/dist/agents/tech-lead/index.d.ts +11 -0
- package/dist/agents/tester/index.d.ts +4 -0
- package/dist/agents/types.d.ts +37 -0
- package/dist/config/continuation.d.ts +13 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/merge.d.ts +8 -0
- package/dist/config/scaffold.d.ts +12 -0
- package/dist/config/schema.d.ts +140 -0
- package/dist/create-managers.d.ts +32 -0
- package/dist/create-tools.d.ts +24 -0
- package/dist/domain/plans/index.d.ts +2 -0
- package/dist/domain/policy/policy-result.d.ts +23 -0
- package/dist/domain/session/index.d.ts +41 -0
- package/dist/domain/workflows/index.d.ts +4 -0
- package/dist/domain/workflows/workflow-completion.d.ts +9 -0
- package/dist/domain/workflows/workflow-context.d.ts +9 -0
- package/dist/domain/workflows/workflow-repository.d.ts +19 -0
- package/dist/domain/workflows/workflow-service.d.ts +32 -0
- package/dist/features/skill-loader/discovery.d.ts +36 -0
- package/dist/features/skill-loader/index.d.ts +6 -0
- package/dist/features/skill-loader/loader.d.ts +15 -0
- package/dist/features/skill-loader/opencode-client.d.ts +6 -0
- package/dist/features/skill-loader/resolver.d.ts +8 -0
- package/dist/features/skill-loader/types.d.ts +14 -0
- package/dist/features/work-state/constants.d.ts +4 -0
- package/dist/features/work-state/index.d.ts +5 -0
- package/dist/features/work-state/storage.d.ts +20 -0
- package/dist/features/work-state/types.d.ts +16 -0
- package/dist/features/work-state/validation-types.d.ts +8 -0
- package/dist/features/work-state/validation.d.ts +6 -0
- package/dist/features/workflow/commands.d.ts +10 -0
- package/dist/features/workflow/completion.d.ts +5 -0
- package/dist/features/workflow/constants.d.ts +8 -0
- package/dist/features/workflow/context.d.ts +16 -0
- package/dist/features/workflow/discovery.d.ts +19 -0
- package/dist/features/workflow/engine.d.ts +19 -0
- package/dist/features/workflow/hook.d.ts +45 -0
- package/dist/features/workflow/index.d.ts +9 -0
- package/dist/features/workflow/schema.d.ts +43 -0
- package/dist/features/workflow/storage.d.ts +7 -0
- package/dist/features/workflow/types.d.ts +89 -0
- package/dist/hooks/architect-md-only.d.ts +11 -0
- package/dist/hooks/compaction-recovery.d.ts +20 -0
- package/dist/hooks/compaction-todo-preserver.d.ts +21 -0
- package/dist/hooks/context-window-monitor.d.ts +26 -0
- package/dist/hooks/create-hooks.d.ts +45 -0
- package/dist/hooks/first-message-variant.d.ts +23 -0
- package/dist/hooks/index.d.ts +28 -0
- package/dist/hooks/keyword-detector.d.ts +17 -0
- package/dist/hooks/rules-injector.d.ts +16 -0
- package/dist/hooks/session-token-state.d.ts +31 -0
- package/dist/hooks/start-implementation-hook.d.ts +26 -0
- package/dist/hooks/start-work-hook.d.ts +26 -0
- package/dist/hooks/todo-continuation-enforcer.d.ts +22 -0
- package/dist/hooks/todo-description-override.d.ts +10 -0
- package/dist/hooks/todo-writer.d.ts +20 -0
- package/dist/hooks/verification-reminder.d.ts +21 -0
- package/dist/hooks/work-continuation.d.ts +23 -0
- package/dist/hooks/write-existing-file-guard.d.ts +34 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3811 -0
- package/dist/infrastructure/fs/config-fs-loader.d.ts +6 -0
- package/dist/managers/background-manager.d.ts +48 -0
- package/dist/managers/config-handler.d.ts +31 -0
- package/dist/managers/index.d.ts +6 -0
- package/dist/managers/skill-mcp-manager.d.ts +31 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/plugin-interface.d.ts +7 -0
- package/dist/plugin/types.d.ts +18 -0
- package/dist/runtime/opencode/plugin-adapter.d.ts +17 -0
- package/dist/shared/agent-display-names.d.ts +3 -0
- package/dist/shared/index.d.ts +6 -0
- package/dist/shared/log.d.ts +9 -0
- package/dist/shared/resolve-safe-path.d.ts +7 -0
- package/dist/shared/types.d.ts +3 -0
- package/dist/shared/version.d.ts +1 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3811 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/shared/log.ts
|
|
5
|
+
var LEVEL_ORDER = {
|
|
6
|
+
DEBUG: 0,
|
|
7
|
+
INFO: 1,
|
|
8
|
+
WARN: 2,
|
|
9
|
+
ERROR: 3
|
|
10
|
+
};
|
|
11
|
+
var sdkClient;
|
|
12
|
+
var currentLevel = process.env.LEAD_LOG_LEVEL ?? "INFO";
|
|
13
|
+
function setClient(client) {
|
|
14
|
+
sdkClient = client;
|
|
15
|
+
}
|
|
16
|
+
function shouldLog(level) {
|
|
17
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];
|
|
18
|
+
}
|
|
19
|
+
function writeLog(level, message, ...args) {
|
|
20
|
+
if (!shouldLog(level))
|
|
21
|
+
return;
|
|
22
|
+
const extra = args.length > 0 ? ` ${args.map((a) => JSON.stringify(a)).join(" ")}` : "";
|
|
23
|
+
const full = message + extra;
|
|
24
|
+
if (sdkClient) {
|
|
25
|
+
try {
|
|
26
|
+
sdkClient.app.log({
|
|
27
|
+
body: {
|
|
28
|
+
service: "lead",
|
|
29
|
+
level: level.toLowerCase(),
|
|
30
|
+
message: full
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
process.stderr.write(`[lead:${level}] ${full}
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
process.stderr.write(`[lead:${level}] ${full}
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function debug(message, ...args) {
|
|
43
|
+
writeLog("DEBUG", message, ...args);
|
|
44
|
+
}
|
|
45
|
+
function info(message, ...args) {
|
|
46
|
+
writeLog("INFO", message, ...args);
|
|
47
|
+
}
|
|
48
|
+
function warn(message, ...args) {
|
|
49
|
+
writeLog("WARN", message, ...args);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
53
|
+
function createScanner(text, ignoreTrivia = false) {
|
|
54
|
+
const len = text.length;
|
|
55
|
+
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
56
|
+
function scanHexDigits(count, exact) {
|
|
57
|
+
let digits = 0;
|
|
58
|
+
let value2 = 0;
|
|
59
|
+
while (digits < count || !exact) {
|
|
60
|
+
let ch = text.charCodeAt(pos);
|
|
61
|
+
if (ch >= 48 && ch <= 57) {
|
|
62
|
+
value2 = value2 * 16 + ch - 48;
|
|
63
|
+
} else if (ch >= 65 && ch <= 70) {
|
|
64
|
+
value2 = value2 * 16 + ch - 65 + 10;
|
|
65
|
+
} else if (ch >= 97 && ch <= 102) {
|
|
66
|
+
value2 = value2 * 16 + ch - 97 + 10;
|
|
67
|
+
} else {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
pos++;
|
|
71
|
+
digits++;
|
|
72
|
+
}
|
|
73
|
+
if (digits < count) {
|
|
74
|
+
value2 = -1;
|
|
75
|
+
}
|
|
76
|
+
return value2;
|
|
77
|
+
}
|
|
78
|
+
function setPosition(newPosition) {
|
|
79
|
+
pos = newPosition;
|
|
80
|
+
value = "";
|
|
81
|
+
tokenOffset = 0;
|
|
82
|
+
token = 16;
|
|
83
|
+
scanError = 0;
|
|
84
|
+
}
|
|
85
|
+
function scanNumber() {
|
|
86
|
+
let start = pos;
|
|
87
|
+
if (text.charCodeAt(pos) === 48) {
|
|
88
|
+
pos++;
|
|
89
|
+
} else {
|
|
90
|
+
pos++;
|
|
91
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
92
|
+
pos++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
96
|
+
pos++;
|
|
97
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
98
|
+
pos++;
|
|
99
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
100
|
+
pos++;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
scanError = 3;
|
|
104
|
+
return text.substring(start, pos);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
let end = pos;
|
|
108
|
+
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
109
|
+
pos++;
|
|
110
|
+
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
111
|
+
pos++;
|
|
112
|
+
}
|
|
113
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
114
|
+
pos++;
|
|
115
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
116
|
+
pos++;
|
|
117
|
+
}
|
|
118
|
+
end = pos;
|
|
119
|
+
} else {
|
|
120
|
+
scanError = 3;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return text.substring(start, end);
|
|
124
|
+
}
|
|
125
|
+
function scanString() {
|
|
126
|
+
let result = "", start = pos;
|
|
127
|
+
while (true) {
|
|
128
|
+
if (pos >= len) {
|
|
129
|
+
result += text.substring(start, pos);
|
|
130
|
+
scanError = 2;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
const ch = text.charCodeAt(pos);
|
|
134
|
+
if (ch === 34) {
|
|
135
|
+
result += text.substring(start, pos);
|
|
136
|
+
pos++;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
if (ch === 92) {
|
|
140
|
+
result += text.substring(start, pos);
|
|
141
|
+
pos++;
|
|
142
|
+
if (pos >= len) {
|
|
143
|
+
scanError = 2;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
const ch2 = text.charCodeAt(pos++);
|
|
147
|
+
switch (ch2) {
|
|
148
|
+
case 34:
|
|
149
|
+
result += '"';
|
|
150
|
+
break;
|
|
151
|
+
case 92:
|
|
152
|
+
result += "\\";
|
|
153
|
+
break;
|
|
154
|
+
case 47:
|
|
155
|
+
result += "/";
|
|
156
|
+
break;
|
|
157
|
+
case 98:
|
|
158
|
+
result += "\b";
|
|
159
|
+
break;
|
|
160
|
+
case 102:
|
|
161
|
+
result += "\f";
|
|
162
|
+
break;
|
|
163
|
+
case 110:
|
|
164
|
+
result += `
|
|
165
|
+
`;
|
|
166
|
+
break;
|
|
167
|
+
case 114:
|
|
168
|
+
result += "\r";
|
|
169
|
+
break;
|
|
170
|
+
case 116:
|
|
171
|
+
result += "\t";
|
|
172
|
+
break;
|
|
173
|
+
case 117:
|
|
174
|
+
const ch3 = scanHexDigits(4, true);
|
|
175
|
+
if (ch3 >= 0) {
|
|
176
|
+
result += String.fromCharCode(ch3);
|
|
177
|
+
} else {
|
|
178
|
+
scanError = 4;
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
default:
|
|
182
|
+
scanError = 5;
|
|
183
|
+
}
|
|
184
|
+
start = pos;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (ch >= 0 && ch <= 31) {
|
|
188
|
+
if (isLineBreak(ch)) {
|
|
189
|
+
result += text.substring(start, pos);
|
|
190
|
+
scanError = 2;
|
|
191
|
+
break;
|
|
192
|
+
} else {
|
|
193
|
+
scanError = 6;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
pos++;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
function scanNext() {
|
|
201
|
+
value = "";
|
|
202
|
+
scanError = 0;
|
|
203
|
+
tokenOffset = pos;
|
|
204
|
+
lineStartOffset = lineNumber;
|
|
205
|
+
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
206
|
+
if (pos >= len) {
|
|
207
|
+
tokenOffset = len;
|
|
208
|
+
return token = 17;
|
|
209
|
+
}
|
|
210
|
+
let code = text.charCodeAt(pos);
|
|
211
|
+
if (isWhiteSpace(code)) {
|
|
212
|
+
do {
|
|
213
|
+
pos++;
|
|
214
|
+
value += String.fromCharCode(code);
|
|
215
|
+
code = text.charCodeAt(pos);
|
|
216
|
+
} while (isWhiteSpace(code));
|
|
217
|
+
return token = 15;
|
|
218
|
+
}
|
|
219
|
+
if (isLineBreak(code)) {
|
|
220
|
+
pos++;
|
|
221
|
+
value += String.fromCharCode(code);
|
|
222
|
+
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
223
|
+
pos++;
|
|
224
|
+
value += `
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
lineNumber++;
|
|
228
|
+
tokenLineStartOffset = pos;
|
|
229
|
+
return token = 14;
|
|
230
|
+
}
|
|
231
|
+
switch (code) {
|
|
232
|
+
case 123:
|
|
233
|
+
pos++;
|
|
234
|
+
return token = 1;
|
|
235
|
+
case 125:
|
|
236
|
+
pos++;
|
|
237
|
+
return token = 2;
|
|
238
|
+
case 91:
|
|
239
|
+
pos++;
|
|
240
|
+
return token = 3;
|
|
241
|
+
case 93:
|
|
242
|
+
pos++;
|
|
243
|
+
return token = 4;
|
|
244
|
+
case 58:
|
|
245
|
+
pos++;
|
|
246
|
+
return token = 6;
|
|
247
|
+
case 44:
|
|
248
|
+
pos++;
|
|
249
|
+
return token = 5;
|
|
250
|
+
case 34:
|
|
251
|
+
pos++;
|
|
252
|
+
value = scanString();
|
|
253
|
+
return token = 10;
|
|
254
|
+
case 47:
|
|
255
|
+
const start = pos - 1;
|
|
256
|
+
if (text.charCodeAt(pos + 1) === 47) {
|
|
257
|
+
pos += 2;
|
|
258
|
+
while (pos < len) {
|
|
259
|
+
if (isLineBreak(text.charCodeAt(pos))) {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
pos++;
|
|
263
|
+
}
|
|
264
|
+
value = text.substring(start, pos);
|
|
265
|
+
return token = 12;
|
|
266
|
+
}
|
|
267
|
+
if (text.charCodeAt(pos + 1) === 42) {
|
|
268
|
+
pos += 2;
|
|
269
|
+
const safeLength = len - 1;
|
|
270
|
+
let commentClosed = false;
|
|
271
|
+
while (pos < safeLength) {
|
|
272
|
+
const ch = text.charCodeAt(pos);
|
|
273
|
+
if (ch === 42 && text.charCodeAt(pos + 1) === 47) {
|
|
274
|
+
pos += 2;
|
|
275
|
+
commentClosed = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
pos++;
|
|
279
|
+
if (isLineBreak(ch)) {
|
|
280
|
+
if (ch === 13 && text.charCodeAt(pos) === 10) {
|
|
281
|
+
pos++;
|
|
282
|
+
}
|
|
283
|
+
lineNumber++;
|
|
284
|
+
tokenLineStartOffset = pos;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (!commentClosed) {
|
|
288
|
+
pos++;
|
|
289
|
+
scanError = 1;
|
|
290
|
+
}
|
|
291
|
+
value = text.substring(start, pos);
|
|
292
|
+
return token = 13;
|
|
293
|
+
}
|
|
294
|
+
value += String.fromCharCode(code);
|
|
295
|
+
pos++;
|
|
296
|
+
return token = 16;
|
|
297
|
+
case 45:
|
|
298
|
+
value += String.fromCharCode(code);
|
|
299
|
+
pos++;
|
|
300
|
+
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
|
301
|
+
return token = 16;
|
|
302
|
+
}
|
|
303
|
+
case 48:
|
|
304
|
+
case 49:
|
|
305
|
+
case 50:
|
|
306
|
+
case 51:
|
|
307
|
+
case 52:
|
|
308
|
+
case 53:
|
|
309
|
+
case 54:
|
|
310
|
+
case 55:
|
|
311
|
+
case 56:
|
|
312
|
+
case 57:
|
|
313
|
+
value += scanNumber();
|
|
314
|
+
return token = 11;
|
|
315
|
+
default:
|
|
316
|
+
while (pos < len && isUnknownContentCharacter(code)) {
|
|
317
|
+
pos++;
|
|
318
|
+
code = text.charCodeAt(pos);
|
|
319
|
+
}
|
|
320
|
+
if (tokenOffset !== pos) {
|
|
321
|
+
value = text.substring(tokenOffset, pos);
|
|
322
|
+
switch (value) {
|
|
323
|
+
case "true":
|
|
324
|
+
return token = 8;
|
|
325
|
+
case "false":
|
|
326
|
+
return token = 9;
|
|
327
|
+
case "null":
|
|
328
|
+
return token = 7;
|
|
329
|
+
}
|
|
330
|
+
return token = 16;
|
|
331
|
+
}
|
|
332
|
+
value += String.fromCharCode(code);
|
|
333
|
+
pos++;
|
|
334
|
+
return token = 16;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function isUnknownContentCharacter(code) {
|
|
338
|
+
if (isWhiteSpace(code) || isLineBreak(code)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
switch (code) {
|
|
342
|
+
case 125:
|
|
343
|
+
case 93:
|
|
344
|
+
case 123:
|
|
345
|
+
case 91:
|
|
346
|
+
case 34:
|
|
347
|
+
case 58:
|
|
348
|
+
case 44:
|
|
349
|
+
case 47:
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
function scanNextNonTrivia() {
|
|
355
|
+
let result;
|
|
356
|
+
do {
|
|
357
|
+
result = scanNext();
|
|
358
|
+
} while (result >= 12 && result <= 15);
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
setPosition,
|
|
363
|
+
getPosition: () => pos,
|
|
364
|
+
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
|
365
|
+
getToken: () => token,
|
|
366
|
+
getTokenValue: () => value,
|
|
367
|
+
getTokenOffset: () => tokenOffset,
|
|
368
|
+
getTokenLength: () => pos - tokenOffset,
|
|
369
|
+
getTokenStartLine: () => lineStartOffset,
|
|
370
|
+
getTokenStartCharacter: () => tokenOffset - prevTokenLineStartOffset,
|
|
371
|
+
getTokenError: () => scanError
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function isWhiteSpace(ch) {
|
|
375
|
+
return ch === 32 || ch === 9;
|
|
376
|
+
}
|
|
377
|
+
function isLineBreak(ch) {
|
|
378
|
+
return ch === 10 || ch === 13;
|
|
379
|
+
}
|
|
380
|
+
function isDigit(ch) {
|
|
381
|
+
return ch >= 48 && ch <= 57;
|
|
382
|
+
}
|
|
383
|
+
var CharacterCodes;
|
|
384
|
+
(function(CharacterCodes2) {
|
|
385
|
+
CharacterCodes2[CharacterCodes2["lineFeed"] = 10] = "lineFeed";
|
|
386
|
+
CharacterCodes2[CharacterCodes2["carriageReturn"] = 13] = "carriageReturn";
|
|
387
|
+
CharacterCodes2[CharacterCodes2["space"] = 32] = "space";
|
|
388
|
+
CharacterCodes2[CharacterCodes2["_0"] = 48] = "_0";
|
|
389
|
+
CharacterCodes2[CharacterCodes2["_1"] = 49] = "_1";
|
|
390
|
+
CharacterCodes2[CharacterCodes2["_2"] = 50] = "_2";
|
|
391
|
+
CharacterCodes2[CharacterCodes2["_3"] = 51] = "_3";
|
|
392
|
+
CharacterCodes2[CharacterCodes2["_4"] = 52] = "_4";
|
|
393
|
+
CharacterCodes2[CharacterCodes2["_5"] = 53] = "_5";
|
|
394
|
+
CharacterCodes2[CharacterCodes2["_6"] = 54] = "_6";
|
|
395
|
+
CharacterCodes2[CharacterCodes2["_7"] = 55] = "_7";
|
|
396
|
+
CharacterCodes2[CharacterCodes2["_8"] = 56] = "_8";
|
|
397
|
+
CharacterCodes2[CharacterCodes2["_9"] = 57] = "_9";
|
|
398
|
+
CharacterCodes2[CharacterCodes2["a"] = 97] = "a";
|
|
399
|
+
CharacterCodes2[CharacterCodes2["b"] = 98] = "b";
|
|
400
|
+
CharacterCodes2[CharacterCodes2["c"] = 99] = "c";
|
|
401
|
+
CharacterCodes2[CharacterCodes2["d"] = 100] = "d";
|
|
402
|
+
CharacterCodes2[CharacterCodes2["e"] = 101] = "e";
|
|
403
|
+
CharacterCodes2[CharacterCodes2["f"] = 102] = "f";
|
|
404
|
+
CharacterCodes2[CharacterCodes2["g"] = 103] = "g";
|
|
405
|
+
CharacterCodes2[CharacterCodes2["h"] = 104] = "h";
|
|
406
|
+
CharacterCodes2[CharacterCodes2["i"] = 105] = "i";
|
|
407
|
+
CharacterCodes2[CharacterCodes2["j"] = 106] = "j";
|
|
408
|
+
CharacterCodes2[CharacterCodes2["k"] = 107] = "k";
|
|
409
|
+
CharacterCodes2[CharacterCodes2["l"] = 108] = "l";
|
|
410
|
+
CharacterCodes2[CharacterCodes2["m"] = 109] = "m";
|
|
411
|
+
CharacterCodes2[CharacterCodes2["n"] = 110] = "n";
|
|
412
|
+
CharacterCodes2[CharacterCodes2["o"] = 111] = "o";
|
|
413
|
+
CharacterCodes2[CharacterCodes2["p"] = 112] = "p";
|
|
414
|
+
CharacterCodes2[CharacterCodes2["q"] = 113] = "q";
|
|
415
|
+
CharacterCodes2[CharacterCodes2["r"] = 114] = "r";
|
|
416
|
+
CharacterCodes2[CharacterCodes2["s"] = 115] = "s";
|
|
417
|
+
CharacterCodes2[CharacterCodes2["t"] = 116] = "t";
|
|
418
|
+
CharacterCodes2[CharacterCodes2["u"] = 117] = "u";
|
|
419
|
+
CharacterCodes2[CharacterCodes2["v"] = 118] = "v";
|
|
420
|
+
CharacterCodes2[CharacterCodes2["w"] = 119] = "w";
|
|
421
|
+
CharacterCodes2[CharacterCodes2["x"] = 120] = "x";
|
|
422
|
+
CharacterCodes2[CharacterCodes2["y"] = 121] = "y";
|
|
423
|
+
CharacterCodes2[CharacterCodes2["z"] = 122] = "z";
|
|
424
|
+
CharacterCodes2[CharacterCodes2["A"] = 65] = "A";
|
|
425
|
+
CharacterCodes2[CharacterCodes2["B"] = 66] = "B";
|
|
426
|
+
CharacterCodes2[CharacterCodes2["C"] = 67] = "C";
|
|
427
|
+
CharacterCodes2[CharacterCodes2["D"] = 68] = "D";
|
|
428
|
+
CharacterCodes2[CharacterCodes2["E"] = 69] = "E";
|
|
429
|
+
CharacterCodes2[CharacterCodes2["F"] = 70] = "F";
|
|
430
|
+
CharacterCodes2[CharacterCodes2["G"] = 71] = "G";
|
|
431
|
+
CharacterCodes2[CharacterCodes2["H"] = 72] = "H";
|
|
432
|
+
CharacterCodes2[CharacterCodes2["I"] = 73] = "I";
|
|
433
|
+
CharacterCodes2[CharacterCodes2["J"] = 74] = "J";
|
|
434
|
+
CharacterCodes2[CharacterCodes2["K"] = 75] = "K";
|
|
435
|
+
CharacterCodes2[CharacterCodes2["L"] = 76] = "L";
|
|
436
|
+
CharacterCodes2[CharacterCodes2["M"] = 77] = "M";
|
|
437
|
+
CharacterCodes2[CharacterCodes2["N"] = 78] = "N";
|
|
438
|
+
CharacterCodes2[CharacterCodes2["O"] = 79] = "O";
|
|
439
|
+
CharacterCodes2[CharacterCodes2["P"] = 80] = "P";
|
|
440
|
+
CharacterCodes2[CharacterCodes2["Q"] = 81] = "Q";
|
|
441
|
+
CharacterCodes2[CharacterCodes2["R"] = 82] = "R";
|
|
442
|
+
CharacterCodes2[CharacterCodes2["S"] = 83] = "S";
|
|
443
|
+
CharacterCodes2[CharacterCodes2["T"] = 84] = "T";
|
|
444
|
+
CharacterCodes2[CharacterCodes2["U"] = 85] = "U";
|
|
445
|
+
CharacterCodes2[CharacterCodes2["V"] = 86] = "V";
|
|
446
|
+
CharacterCodes2[CharacterCodes2["W"] = 87] = "W";
|
|
447
|
+
CharacterCodes2[CharacterCodes2["X"] = 88] = "X";
|
|
448
|
+
CharacterCodes2[CharacterCodes2["Y"] = 89] = "Y";
|
|
449
|
+
CharacterCodes2[CharacterCodes2["Z"] = 90] = "Z";
|
|
450
|
+
CharacterCodes2[CharacterCodes2["asterisk"] = 42] = "asterisk";
|
|
451
|
+
CharacterCodes2[CharacterCodes2["backslash"] = 92] = "backslash";
|
|
452
|
+
CharacterCodes2[CharacterCodes2["closeBrace"] = 125] = "closeBrace";
|
|
453
|
+
CharacterCodes2[CharacterCodes2["closeBracket"] = 93] = "closeBracket";
|
|
454
|
+
CharacterCodes2[CharacterCodes2["colon"] = 58] = "colon";
|
|
455
|
+
CharacterCodes2[CharacterCodes2["comma"] = 44] = "comma";
|
|
456
|
+
CharacterCodes2[CharacterCodes2["dot"] = 46] = "dot";
|
|
457
|
+
CharacterCodes2[CharacterCodes2["doubleQuote"] = 34] = "doubleQuote";
|
|
458
|
+
CharacterCodes2[CharacterCodes2["minus"] = 45] = "minus";
|
|
459
|
+
CharacterCodes2[CharacterCodes2["openBrace"] = 123] = "openBrace";
|
|
460
|
+
CharacterCodes2[CharacterCodes2["openBracket"] = 91] = "openBracket";
|
|
461
|
+
CharacterCodes2[CharacterCodes2["plus"] = 43] = "plus";
|
|
462
|
+
CharacterCodes2[CharacterCodes2["slash"] = 47] = "slash";
|
|
463
|
+
CharacterCodes2[CharacterCodes2["formFeed"] = 12] = "formFeed";
|
|
464
|
+
CharacterCodes2[CharacterCodes2["tab"] = 9] = "tab";
|
|
465
|
+
})(CharacterCodes || (CharacterCodes = {}));
|
|
466
|
+
|
|
467
|
+
// node_modules/jsonc-parser/lib/esm/impl/string-intern.js
|
|
468
|
+
var cachedSpaces = new Array(20).fill(0).map((_, index) => {
|
|
469
|
+
return " ".repeat(index);
|
|
470
|
+
});
|
|
471
|
+
var maxCachedValues = 200;
|
|
472
|
+
var cachedBreakLinesWithSpaces = {
|
|
473
|
+
" ": {
|
|
474
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
475
|
+
return `
|
|
476
|
+
` + " ".repeat(index);
|
|
477
|
+
}),
|
|
478
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
479
|
+
return "\r" + " ".repeat(index);
|
|
480
|
+
}),
|
|
481
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
482
|
+
return `\r
|
|
483
|
+
` + " ".repeat(index);
|
|
484
|
+
})
|
|
485
|
+
},
|
|
486
|
+
"\t": {
|
|
487
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
488
|
+
return `
|
|
489
|
+
` + "\t".repeat(index);
|
|
490
|
+
}),
|
|
491
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
492
|
+
return "\r" + "\t".repeat(index);
|
|
493
|
+
}),
|
|
494
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
495
|
+
return `\r
|
|
496
|
+
` + "\t".repeat(index);
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// node_modules/jsonc-parser/lib/esm/impl/parser.js
|
|
502
|
+
var ParseOptions;
|
|
503
|
+
(function(ParseOptions2) {
|
|
504
|
+
ParseOptions2.DEFAULT = {
|
|
505
|
+
allowTrailingComma: false
|
|
506
|
+
};
|
|
507
|
+
})(ParseOptions || (ParseOptions = {}));
|
|
508
|
+
function parse(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
509
|
+
let currentProperty = null;
|
|
510
|
+
let currentParent = [];
|
|
511
|
+
const previousParents = [];
|
|
512
|
+
function onValue(value) {
|
|
513
|
+
if (Array.isArray(currentParent)) {
|
|
514
|
+
currentParent.push(value);
|
|
515
|
+
} else if (currentProperty !== null) {
|
|
516
|
+
currentParent[currentProperty] = value;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const visitor = {
|
|
520
|
+
onObjectBegin: () => {
|
|
521
|
+
const object = {};
|
|
522
|
+
onValue(object);
|
|
523
|
+
previousParents.push(currentParent);
|
|
524
|
+
currentParent = object;
|
|
525
|
+
currentProperty = null;
|
|
526
|
+
},
|
|
527
|
+
onObjectProperty: (name) => {
|
|
528
|
+
currentProperty = name;
|
|
529
|
+
},
|
|
530
|
+
onObjectEnd: () => {
|
|
531
|
+
currentParent = previousParents.pop();
|
|
532
|
+
},
|
|
533
|
+
onArrayBegin: () => {
|
|
534
|
+
const array = [];
|
|
535
|
+
onValue(array);
|
|
536
|
+
previousParents.push(currentParent);
|
|
537
|
+
currentParent = array;
|
|
538
|
+
currentProperty = null;
|
|
539
|
+
},
|
|
540
|
+
onArrayEnd: () => {
|
|
541
|
+
currentParent = previousParents.pop();
|
|
542
|
+
},
|
|
543
|
+
onLiteralValue: onValue,
|
|
544
|
+
onError: (error, offset, length) => {
|
|
545
|
+
errors.push({ error, offset, length });
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
visit(text, visitor, options);
|
|
549
|
+
return currentParent[0];
|
|
550
|
+
}
|
|
551
|
+
function visit(text, visitor, options = ParseOptions.DEFAULT) {
|
|
552
|
+
const _scanner = createScanner(text, false);
|
|
553
|
+
const _jsonPath = [];
|
|
554
|
+
let suppressedCallbacks = 0;
|
|
555
|
+
function toNoArgVisit(visitFunction) {
|
|
556
|
+
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
557
|
+
}
|
|
558
|
+
function toOneArgVisit(visitFunction) {
|
|
559
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
560
|
+
}
|
|
561
|
+
function toOneArgVisitWithPath(visitFunction) {
|
|
562
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
|
|
563
|
+
}
|
|
564
|
+
function toBeginVisit(visitFunction) {
|
|
565
|
+
return visitFunction ? () => {
|
|
566
|
+
if (suppressedCallbacks > 0) {
|
|
567
|
+
suppressedCallbacks++;
|
|
568
|
+
} else {
|
|
569
|
+
let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());
|
|
570
|
+
if (cbReturn === false) {
|
|
571
|
+
suppressedCallbacks = 1;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} : () => true;
|
|
575
|
+
}
|
|
576
|
+
function toEndVisit(visitFunction) {
|
|
577
|
+
return visitFunction ? () => {
|
|
578
|
+
if (suppressedCallbacks > 0) {
|
|
579
|
+
suppressedCallbacks--;
|
|
580
|
+
}
|
|
581
|
+
if (suppressedCallbacks === 0) {
|
|
582
|
+
visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
583
|
+
}
|
|
584
|
+
} : () => true;
|
|
585
|
+
}
|
|
586
|
+
const onObjectBegin = toBeginVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty), onObjectEnd = toEndVisit(visitor.onObjectEnd), onArrayBegin = toBeginVisit(visitor.onArrayBegin), onArrayEnd = toEndVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
|
|
587
|
+
const disallowComments = options && options.disallowComments;
|
|
588
|
+
const allowTrailingComma = options && options.allowTrailingComma;
|
|
589
|
+
function scanNext() {
|
|
590
|
+
while (true) {
|
|
591
|
+
const token = _scanner.scan();
|
|
592
|
+
switch (_scanner.getTokenError()) {
|
|
593
|
+
case 4:
|
|
594
|
+
handleError(14);
|
|
595
|
+
break;
|
|
596
|
+
case 5:
|
|
597
|
+
handleError(15);
|
|
598
|
+
break;
|
|
599
|
+
case 3:
|
|
600
|
+
handleError(13);
|
|
601
|
+
break;
|
|
602
|
+
case 1:
|
|
603
|
+
if (!disallowComments) {
|
|
604
|
+
handleError(11);
|
|
605
|
+
}
|
|
606
|
+
break;
|
|
607
|
+
case 2:
|
|
608
|
+
handleError(12);
|
|
609
|
+
break;
|
|
610
|
+
case 6:
|
|
611
|
+
handleError(16);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
switch (token) {
|
|
615
|
+
case 12:
|
|
616
|
+
case 13:
|
|
617
|
+
if (disallowComments) {
|
|
618
|
+
handleError(10);
|
|
619
|
+
} else {
|
|
620
|
+
onComment();
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
case 16:
|
|
624
|
+
handleError(1);
|
|
625
|
+
break;
|
|
626
|
+
case 15:
|
|
627
|
+
case 14:
|
|
628
|
+
break;
|
|
629
|
+
default:
|
|
630
|
+
return token;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function handleError(error, skipUntilAfter = [], skipUntil = []) {
|
|
635
|
+
onError(error);
|
|
636
|
+
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
637
|
+
let token = _scanner.getToken();
|
|
638
|
+
while (token !== 17) {
|
|
639
|
+
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
640
|
+
scanNext();
|
|
641
|
+
break;
|
|
642
|
+
} else if (skipUntil.indexOf(token) !== -1) {
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
token = scanNext();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function parseString(isValue) {
|
|
650
|
+
const value = _scanner.getTokenValue();
|
|
651
|
+
if (isValue) {
|
|
652
|
+
onLiteralValue(value);
|
|
653
|
+
} else {
|
|
654
|
+
onObjectProperty(value);
|
|
655
|
+
_jsonPath.push(value);
|
|
656
|
+
}
|
|
657
|
+
scanNext();
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
function parseLiteral() {
|
|
661
|
+
switch (_scanner.getToken()) {
|
|
662
|
+
case 11:
|
|
663
|
+
const tokenValue = _scanner.getTokenValue();
|
|
664
|
+
let value = Number(tokenValue);
|
|
665
|
+
if (isNaN(value)) {
|
|
666
|
+
handleError(2);
|
|
667
|
+
value = 0;
|
|
668
|
+
}
|
|
669
|
+
onLiteralValue(value);
|
|
670
|
+
break;
|
|
671
|
+
case 7:
|
|
672
|
+
onLiteralValue(null);
|
|
673
|
+
break;
|
|
674
|
+
case 8:
|
|
675
|
+
onLiteralValue(true);
|
|
676
|
+
break;
|
|
677
|
+
case 9:
|
|
678
|
+
onLiteralValue(false);
|
|
679
|
+
break;
|
|
680
|
+
default:
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
scanNext();
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
function parseProperty() {
|
|
687
|
+
if (_scanner.getToken() !== 10) {
|
|
688
|
+
handleError(3, [], [2, 5]);
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
parseString(false);
|
|
692
|
+
if (_scanner.getToken() === 6) {
|
|
693
|
+
onSeparator(":");
|
|
694
|
+
scanNext();
|
|
695
|
+
if (!parseValue()) {
|
|
696
|
+
handleError(4, [], [2, 5]);
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
handleError(5, [], [2, 5]);
|
|
700
|
+
}
|
|
701
|
+
_jsonPath.pop();
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
function parseObject() {
|
|
705
|
+
onObjectBegin();
|
|
706
|
+
scanNext();
|
|
707
|
+
let needsComma = false;
|
|
708
|
+
while (_scanner.getToken() !== 2 && _scanner.getToken() !== 17) {
|
|
709
|
+
if (_scanner.getToken() === 5) {
|
|
710
|
+
if (!needsComma) {
|
|
711
|
+
handleError(4, [], []);
|
|
712
|
+
}
|
|
713
|
+
onSeparator(",");
|
|
714
|
+
scanNext();
|
|
715
|
+
if (_scanner.getToken() === 2 && allowTrailingComma) {
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
} else if (needsComma) {
|
|
719
|
+
handleError(6, [], []);
|
|
720
|
+
}
|
|
721
|
+
if (!parseProperty()) {
|
|
722
|
+
handleError(4, [], [2, 5]);
|
|
723
|
+
}
|
|
724
|
+
needsComma = true;
|
|
725
|
+
}
|
|
726
|
+
onObjectEnd();
|
|
727
|
+
if (_scanner.getToken() !== 2) {
|
|
728
|
+
handleError(7, [2], []);
|
|
729
|
+
} else {
|
|
730
|
+
scanNext();
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
function parseArray() {
|
|
735
|
+
onArrayBegin();
|
|
736
|
+
scanNext();
|
|
737
|
+
let isFirstElement = true;
|
|
738
|
+
let needsComma = false;
|
|
739
|
+
while (_scanner.getToken() !== 4 && _scanner.getToken() !== 17) {
|
|
740
|
+
if (_scanner.getToken() === 5) {
|
|
741
|
+
if (!needsComma) {
|
|
742
|
+
handleError(4, [], []);
|
|
743
|
+
}
|
|
744
|
+
onSeparator(",");
|
|
745
|
+
scanNext();
|
|
746
|
+
if (_scanner.getToken() === 4 && allowTrailingComma) {
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
} else if (needsComma) {
|
|
750
|
+
handleError(6, [], []);
|
|
751
|
+
}
|
|
752
|
+
if (isFirstElement) {
|
|
753
|
+
_jsonPath.push(0);
|
|
754
|
+
isFirstElement = false;
|
|
755
|
+
} else {
|
|
756
|
+
_jsonPath[_jsonPath.length - 1]++;
|
|
757
|
+
}
|
|
758
|
+
if (!parseValue()) {
|
|
759
|
+
handleError(4, [], [4, 5]);
|
|
760
|
+
}
|
|
761
|
+
needsComma = true;
|
|
762
|
+
}
|
|
763
|
+
onArrayEnd();
|
|
764
|
+
if (!isFirstElement) {
|
|
765
|
+
_jsonPath.pop();
|
|
766
|
+
}
|
|
767
|
+
if (_scanner.getToken() !== 4) {
|
|
768
|
+
handleError(8, [4], []);
|
|
769
|
+
} else {
|
|
770
|
+
scanNext();
|
|
771
|
+
}
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
function parseValue() {
|
|
775
|
+
switch (_scanner.getToken()) {
|
|
776
|
+
case 3:
|
|
777
|
+
return parseArray();
|
|
778
|
+
case 1:
|
|
779
|
+
return parseObject();
|
|
780
|
+
case 10:
|
|
781
|
+
return parseString(true);
|
|
782
|
+
default:
|
|
783
|
+
return parseLiteral();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
scanNext();
|
|
787
|
+
if (_scanner.getToken() === 17) {
|
|
788
|
+
if (options.allowEmptyContent) {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
handleError(4, [], []);
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
if (!parseValue()) {
|
|
795
|
+
handleError(4, [], []);
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
if (_scanner.getToken() !== 17) {
|
|
799
|
+
handleError(9, [], []);
|
|
800
|
+
}
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// node_modules/jsonc-parser/lib/esm/main.js
|
|
805
|
+
var ScanError;
|
|
806
|
+
(function(ScanError2) {
|
|
807
|
+
ScanError2[ScanError2["None"] = 0] = "None";
|
|
808
|
+
ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
809
|
+
ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
|
|
810
|
+
ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
|
|
811
|
+
ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
|
|
812
|
+
ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
|
|
813
|
+
ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
|
|
814
|
+
})(ScanError || (ScanError = {}));
|
|
815
|
+
var SyntaxKind;
|
|
816
|
+
(function(SyntaxKind2) {
|
|
817
|
+
SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
|
|
818
|
+
SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
|
|
819
|
+
SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
|
|
820
|
+
SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
|
|
821
|
+
SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
|
|
822
|
+
SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
|
|
823
|
+
SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
|
|
824
|
+
SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
|
|
825
|
+
SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
|
|
826
|
+
SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
|
|
827
|
+
SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
|
|
828
|
+
SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
|
|
829
|
+
SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
|
|
830
|
+
SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
|
|
831
|
+
SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
|
|
832
|
+
SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
|
|
833
|
+
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
834
|
+
})(SyntaxKind || (SyntaxKind = {}));
|
|
835
|
+
var parse2 = parse;
|
|
836
|
+
var ParseErrorCode;
|
|
837
|
+
(function(ParseErrorCode2) {
|
|
838
|
+
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
839
|
+
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
840
|
+
ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
|
|
841
|
+
ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
|
|
842
|
+
ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
|
|
843
|
+
ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
|
|
844
|
+
ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
|
|
845
|
+
ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
|
|
846
|
+
ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
|
|
847
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
|
|
848
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
|
|
849
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
|
|
850
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
|
|
851
|
+
ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
|
|
852
|
+
ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
|
|
853
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
854
|
+
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
855
|
+
|
|
856
|
+
// src/infrastructure/fs/config-fs-loader.ts
|
|
857
|
+
import { existsSync, readFileSync } from "fs";
|
|
858
|
+
import { join } from "path";
|
|
859
|
+
import { homedir } from "os";
|
|
860
|
+
|
|
861
|
+
// src/config/schema.ts
|
|
862
|
+
import { z } from "zod";
|
|
863
|
+
var ToolPermissionMapSchema = z.record(z.string(), z.boolean());
|
|
864
|
+
var AgentOverrideConfigSchema = z.object({
|
|
865
|
+
model: z.string().optional(),
|
|
866
|
+
skills: z.array(z.string()).optional(),
|
|
867
|
+
tools: ToolPermissionMapSchema.optional(),
|
|
868
|
+
display_name: z.string().optional()
|
|
869
|
+
});
|
|
870
|
+
var CustomAgentConfigSchema = z.object({
|
|
871
|
+
prompt: z.string().optional(),
|
|
872
|
+
prompt_file: z.string().optional(),
|
|
873
|
+
model: z.string().optional(),
|
|
874
|
+
skills: z.array(z.string()).optional(),
|
|
875
|
+
tools: ToolPermissionMapSchema.optional(),
|
|
876
|
+
display_name: z.string().optional(),
|
|
877
|
+
mode: z.enum(["subagent", "primary", "all"]).optional(),
|
|
878
|
+
triggers: z.array(z.object({
|
|
879
|
+
domain: z.string(),
|
|
880
|
+
trigger: z.string()
|
|
881
|
+
})).optional(),
|
|
882
|
+
description: z.string().optional()
|
|
883
|
+
});
|
|
884
|
+
var CategoryConfigSchema = z.object({
|
|
885
|
+
model: z.string().optional(),
|
|
886
|
+
skills: z.array(z.string()).optional()
|
|
887
|
+
});
|
|
888
|
+
var CategoriesConfigSchema = z.record(z.string(), CategoryConfigSchema);
|
|
889
|
+
var BackgroundConfigSchema = z.object({
|
|
890
|
+
max_concurrent: z.number().int().positive().optional()
|
|
891
|
+
});
|
|
892
|
+
var ContinuationRecoveryConfigSchema = z.object({
|
|
893
|
+
compaction: z.boolean().optional()
|
|
894
|
+
});
|
|
895
|
+
var ContinuationIdleConfigSchema = z.object({
|
|
896
|
+
enabled: z.boolean().optional(),
|
|
897
|
+
work: z.boolean().optional(),
|
|
898
|
+
workflow: z.boolean().optional(),
|
|
899
|
+
todo_prompt: z.boolean().optional()
|
|
900
|
+
});
|
|
901
|
+
var ContinuationConfigSchema = z.object({
|
|
902
|
+
recovery: ContinuationRecoveryConfigSchema.optional(),
|
|
903
|
+
idle: ContinuationIdleConfigSchema.optional()
|
|
904
|
+
});
|
|
905
|
+
var WorkflowConfigSchema = z.object({
|
|
906
|
+
disabled_workflows: z.array(z.string()).optional(),
|
|
907
|
+
directories: z.array(z.string()).optional()
|
|
908
|
+
});
|
|
909
|
+
var ExperimentalConfigSchema = z.object({
|
|
910
|
+
enabled: z.boolean().optional()
|
|
911
|
+
});
|
|
912
|
+
var LeadConfigSchema = z.object({
|
|
913
|
+
$schema: z.string().optional(),
|
|
914
|
+
agents: z.record(z.string(), AgentOverrideConfigSchema).optional(),
|
|
915
|
+
custom_agents: z.record(z.string(), CustomAgentConfigSchema).optional(),
|
|
916
|
+
categories: CategoriesConfigSchema.optional(),
|
|
917
|
+
disabled_hooks: z.array(z.string()).optional(),
|
|
918
|
+
disabled_tools: z.array(z.string()).optional(),
|
|
919
|
+
disabled_agents: z.array(z.string()).optional(),
|
|
920
|
+
disabled_skills: z.array(z.string()).optional(),
|
|
921
|
+
skill_directories: z.array(z.string()).optional(),
|
|
922
|
+
background: BackgroundConfigSchema.optional(),
|
|
923
|
+
continuation: ContinuationConfigSchema.optional(),
|
|
924
|
+
workflows: WorkflowConfigSchema.optional(),
|
|
925
|
+
experimental: ExperimentalConfigSchema.optional(),
|
|
926
|
+
log_level: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).optional()
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// src/infrastructure/fs/config-fs-loader.ts
|
|
930
|
+
var CONFIG_FILENAME = "lead.jsonc";
|
|
931
|
+
function readJsoncFile(filePath) {
|
|
932
|
+
if (!existsSync(filePath))
|
|
933
|
+
return {};
|
|
934
|
+
try {
|
|
935
|
+
const content = readFileSync(filePath, "utf-8");
|
|
936
|
+
const errors = [];
|
|
937
|
+
const parsed = parse2(content, errors);
|
|
938
|
+
if (errors.length > 0) {
|
|
939
|
+
warn(`Config file has parse errors: ${filePath}`, errors);
|
|
940
|
+
}
|
|
941
|
+
if (parsed === null || parsed === undefined || typeof parsed !== "object") {
|
|
942
|
+
warn(`Config file is not a valid object: ${filePath}`);
|
|
943
|
+
return {};
|
|
944
|
+
}
|
|
945
|
+
const result = LeadConfigSchema.safeParse(parsed);
|
|
946
|
+
if (!result.success) {
|
|
947
|
+
warn(`Config file validation warnings in ${filePath}:`, result.error.issues);
|
|
948
|
+
return parsed ?? {};
|
|
949
|
+
}
|
|
950
|
+
return result.data;
|
|
951
|
+
} catch (err) {
|
|
952
|
+
warn(`Failed to read config file: ${filePath}`, err);
|
|
953
|
+
return {};
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
function createConfigFsLoader() {
|
|
957
|
+
return {
|
|
958
|
+
loadUserConfig(homeDir) {
|
|
959
|
+
const home = homeDir ?? homedir();
|
|
960
|
+
const configPath = join(home, ".config", "opencode", CONFIG_FILENAME);
|
|
961
|
+
return readJsoncFile(configPath);
|
|
962
|
+
},
|
|
963
|
+
loadProjectConfig(projectDir) {
|
|
964
|
+
const configPath = join(projectDir, ".opencode", CONFIG_FILENAME);
|
|
965
|
+
return readJsoncFile(configPath);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/config/merge.ts
|
|
971
|
+
function mergeConfigs(user, project) {
|
|
972
|
+
const merged = { ...user };
|
|
973
|
+
if (project.log_level !== undefined)
|
|
974
|
+
merged.log_level = project.log_level;
|
|
975
|
+
merged.disabled_agents = mergeArrays(user.disabled_agents, project.disabled_agents);
|
|
976
|
+
merged.disabled_tools = mergeArrays(user.disabled_tools, project.disabled_tools);
|
|
977
|
+
merged.disabled_hooks = mergeArrays(user.disabled_hooks, project.disabled_hooks);
|
|
978
|
+
merged.disabled_skills = mergeArrays(user.disabled_skills, project.disabled_skills);
|
|
979
|
+
merged.skill_directories = mergeArrays(user.skill_directories, project.skill_directories);
|
|
980
|
+
merged.agents = mergeRecords(user.agents, project.agents);
|
|
981
|
+
merged.custom_agents = mergeRecords(user.custom_agents, project.custom_agents);
|
|
982
|
+
merged.categories = mergeRecords(user.categories, project.categories);
|
|
983
|
+
if (project.background !== undefined) {
|
|
984
|
+
merged.background = { ...user.background, ...project.background };
|
|
985
|
+
}
|
|
986
|
+
if (project.continuation !== undefined) {
|
|
987
|
+
merged.continuation = {
|
|
988
|
+
recovery: { ...user.continuation?.recovery, ...project.continuation.recovery },
|
|
989
|
+
idle: { ...user.continuation?.idle, ...project.continuation.idle }
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
if (project.workflows !== undefined) {
|
|
993
|
+
merged.workflows = {
|
|
994
|
+
disabled_workflows: mergeArrays(user.workflows?.disabled_workflows, project.workflows.disabled_workflows),
|
|
995
|
+
directories: mergeArrays(user.workflows?.directories, project.workflows.directories)
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (project.experimental !== undefined) {
|
|
999
|
+
merged.experimental = { ...user.experimental, ...project.experimental };
|
|
1000
|
+
}
|
|
1001
|
+
return merged;
|
|
1002
|
+
}
|
|
1003
|
+
function mergeArrays(a, b) {
|
|
1004
|
+
if (!a && !b)
|
|
1005
|
+
return;
|
|
1006
|
+
const combined = [...a ?? [], ...b ?? []];
|
|
1007
|
+
return [...new Set(combined)];
|
|
1008
|
+
}
|
|
1009
|
+
function mergeRecords(a, b) {
|
|
1010
|
+
if (!a && !b)
|
|
1011
|
+
return;
|
|
1012
|
+
const result = { ...a ?? {} };
|
|
1013
|
+
for (const [key, val] of Object.entries(b ?? {})) {
|
|
1014
|
+
result[key] = { ...result[key] ?? {}, ...val };
|
|
1015
|
+
}
|
|
1016
|
+
return result;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/config/loader.ts
|
|
1020
|
+
var _loader;
|
|
1021
|
+
function getLoader() {
|
|
1022
|
+
if (!_loader)
|
|
1023
|
+
_loader = createConfigFsLoader();
|
|
1024
|
+
return _loader;
|
|
1025
|
+
}
|
|
1026
|
+
function loadLeadConfig(directory, options) {
|
|
1027
|
+
const loader = getLoader();
|
|
1028
|
+
const userConfig = loader.loadUserConfig(options?.homeDir);
|
|
1029
|
+
const projectConfig = loader.loadProjectConfig(directory);
|
|
1030
|
+
const merged = mergeConfigs(userConfig, projectConfig);
|
|
1031
|
+
info(`Config loaded for directory: ${directory}`);
|
|
1032
|
+
return merged;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/config/continuation.ts
|
|
1036
|
+
var DEFAULTS = {
|
|
1037
|
+
recovery: {
|
|
1038
|
+
compaction: true
|
|
1039
|
+
},
|
|
1040
|
+
idle: {
|
|
1041
|
+
enabled: true,
|
|
1042
|
+
work: true,
|
|
1043
|
+
workflow: true,
|
|
1044
|
+
todo_prompt: true
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
function resolveContinuationConfig(raw) {
|
|
1048
|
+
if (!raw)
|
|
1049
|
+
return DEFAULTS;
|
|
1050
|
+
return {
|
|
1051
|
+
recovery: {
|
|
1052
|
+
compaction: raw.recovery?.compaction ?? DEFAULTS.recovery.compaction
|
|
1053
|
+
},
|
|
1054
|
+
idle: {
|
|
1055
|
+
enabled: raw.idle?.enabled ?? DEFAULTS.idle.enabled,
|
|
1056
|
+
work: raw.idle?.work ?? DEFAULTS.idle.work,
|
|
1057
|
+
workflow: raw.idle?.workflow ?? DEFAULTS.idle.workflow,
|
|
1058
|
+
todo_prompt: raw.idle?.todo_prompt ?? DEFAULTS.idle.todo_prompt
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/features/skill-loader/opencode-client.ts
|
|
1064
|
+
async function fetchSkillsFromApi(serverUrl, directory, timeoutMs = 3000) {
|
|
1065
|
+
try {
|
|
1066
|
+
const url = new URL("/skill", serverUrl);
|
|
1067
|
+
url.searchParams.set("directory", directory);
|
|
1068
|
+
const controller = new AbortController;
|
|
1069
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1070
|
+
let response;
|
|
1071
|
+
try {
|
|
1072
|
+
response = await fetch(url.toString(), { signal: controller.signal });
|
|
1073
|
+
} finally {
|
|
1074
|
+
clearTimeout(timer);
|
|
1075
|
+
}
|
|
1076
|
+
if (!response.ok) {
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
const json = await response.json();
|
|
1080
|
+
if (!Array.isArray(json)) {
|
|
1081
|
+
return [];
|
|
1082
|
+
}
|
|
1083
|
+
const skills = [];
|
|
1084
|
+
for (const item of json) {
|
|
1085
|
+
if (item && typeof item === "object" && typeof item.name === "string" && typeof item.content === "string") {
|
|
1086
|
+
const raw = item;
|
|
1087
|
+
const location = typeof raw.location === "string" ? raw.location : "";
|
|
1088
|
+
const source = location.includes(".opencode") ? "project" : "user";
|
|
1089
|
+
skills.push({
|
|
1090
|
+
name: raw.name,
|
|
1091
|
+
content: raw.content,
|
|
1092
|
+
source,
|
|
1093
|
+
metadata: {
|
|
1094
|
+
description: typeof raw.description === "string" ? raw.description : undefined
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return skills;
|
|
1100
|
+
} catch {
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/features/skill-loader/discovery.ts
|
|
1106
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
1107
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
1108
|
+
import { homedir as homedir2 } from "os";
|
|
1109
|
+
|
|
1110
|
+
// src/shared/resolve-safe-path.ts
|
|
1111
|
+
import { resolve, relative, isAbsolute } from "path";
|
|
1112
|
+
function resolveSafePath(base, relativePath) {
|
|
1113
|
+
if (isAbsolute(relativePath)) {
|
|
1114
|
+
throw new Error(`Path traversal rejected: absolute path not allowed: ${relativePath}`);
|
|
1115
|
+
}
|
|
1116
|
+
if (relativePath.includes("..")) {
|
|
1117
|
+
throw new Error(`Path traversal rejected: ".." not allowed in path: ${relativePath}`);
|
|
1118
|
+
}
|
|
1119
|
+
const resolved = resolve(base, relativePath);
|
|
1120
|
+
const rel = relative(base, resolved);
|
|
1121
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1122
|
+
throw new Error(`Path traversal rejected: path escapes sandbox: ${relativePath}`);
|
|
1123
|
+
}
|
|
1124
|
+
return resolved;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/features/skill-loader/discovery.ts
|
|
1128
|
+
function parseFrontmatter(text) {
|
|
1129
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
|
|
1130
|
+
if (!match) {
|
|
1131
|
+
return { metadata: {}, content: text };
|
|
1132
|
+
}
|
|
1133
|
+
const frontmatter = match[1];
|
|
1134
|
+
const content = match[2];
|
|
1135
|
+
const metadata = {};
|
|
1136
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
1137
|
+
let i = 0;
|
|
1138
|
+
while (i < lines.length) {
|
|
1139
|
+
const line = lines[i];
|
|
1140
|
+
const colonIdx = line.indexOf(":");
|
|
1141
|
+
if (colonIdx === -1) {
|
|
1142
|
+
i++;
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
const key = line.slice(0, colonIdx).trim();
|
|
1146
|
+
const rawValue = line.slice(colonIdx + 1).trim();
|
|
1147
|
+
if (rawValue.startsWith("[")) {
|
|
1148
|
+
const inner = rawValue.slice(1, rawValue.lastIndexOf("]"));
|
|
1149
|
+
const items = inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
1150
|
+
if (key === "tools") {
|
|
1151
|
+
metadata.tools = items;
|
|
1152
|
+
}
|
|
1153
|
+
i++;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (rawValue === "") {
|
|
1157
|
+
const items = [];
|
|
1158
|
+
i++;
|
|
1159
|
+
while (i < lines.length && lines[i].trim().startsWith("-")) {
|
|
1160
|
+
items.push(lines[i].trim().slice(1).trim());
|
|
1161
|
+
i++;
|
|
1162
|
+
}
|
|
1163
|
+
if (key === "tools") {
|
|
1164
|
+
metadata.tools = items;
|
|
1165
|
+
}
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
const value = rawValue.replace(/^["']|["']$/g, "");
|
|
1169
|
+
if (key === "name" || key === "description" || key === "model") {
|
|
1170
|
+
metadata[key] = value;
|
|
1171
|
+
} else if (key === "tools") {
|
|
1172
|
+
metadata.tools = [value];
|
|
1173
|
+
}
|
|
1174
|
+
i++;
|
|
1175
|
+
}
|
|
1176
|
+
return { metadata, content };
|
|
1177
|
+
}
|
|
1178
|
+
function loadSkillFile(filePath, source) {
|
|
1179
|
+
try {
|
|
1180
|
+
const text = readFileSync2(filePath, "utf-8");
|
|
1181
|
+
const { metadata, content } = parseFrontmatter(text);
|
|
1182
|
+
const name = metadata.name ?? filePath.split(/[\\/]/).slice(-2, -1)[0]?.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1183
|
+
if (!name)
|
|
1184
|
+
return null;
|
|
1185
|
+
const tools = metadata.tools == null ? undefined : Array.isArray(metadata.tools) ? metadata.tools : [metadata.tools];
|
|
1186
|
+
return {
|
|
1187
|
+
name,
|
|
1188
|
+
content: content.trim(),
|
|
1189
|
+
source,
|
|
1190
|
+
metadata: {
|
|
1191
|
+
description: metadata.description,
|
|
1192
|
+
model: metadata.model,
|
|
1193
|
+
tools
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
} catch {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function scanDirectory(dir, source) {
|
|
1201
|
+
if (!existsSync2(dir))
|
|
1202
|
+
return [];
|
|
1203
|
+
const skills = [];
|
|
1204
|
+
try {
|
|
1205
|
+
const entries = readdirSync(dir);
|
|
1206
|
+
for (const entry of entries) {
|
|
1207
|
+
const fullPath = join2(dir, entry);
|
|
1208
|
+
let stat;
|
|
1209
|
+
try {
|
|
1210
|
+
stat = statSync(fullPath);
|
|
1211
|
+
} catch {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (stat.isDirectory()) {
|
|
1215
|
+
const skillPath = join2(fullPath, "SKILL.md");
|
|
1216
|
+
if (existsSync2(skillPath)) {
|
|
1217
|
+
const skill = loadSkillFile(skillPath, source);
|
|
1218
|
+
if (skill)
|
|
1219
|
+
skills.push(skill);
|
|
1220
|
+
}
|
|
1221
|
+
skills.push(...scanDirectory(fullPath, source));
|
|
1222
|
+
} else if (entry === "SKILL.md") {
|
|
1223
|
+
const skill = loadSkillFile(fullPath, source);
|
|
1224
|
+
if (skill)
|
|
1225
|
+
skills.push(skill);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
} catch {}
|
|
1229
|
+
return skills;
|
|
1230
|
+
}
|
|
1231
|
+
function discoverSkillsFromDirs(options) {
|
|
1232
|
+
const { projectDirectory, customDirs = [] } = options;
|
|
1233
|
+
const results = [];
|
|
1234
|
+
const userSkillsDir = join2(homedir2(), ".config", "opencode", "skills");
|
|
1235
|
+
results.push(...scanDirectory(userSkillsDir, "user"));
|
|
1236
|
+
const projectSkillsDir = join2(projectDirectory, ".opencode", "skills");
|
|
1237
|
+
results.push(...scanDirectory(projectSkillsDir, "project"));
|
|
1238
|
+
for (const customDir of customDirs) {
|
|
1239
|
+
try {
|
|
1240
|
+
const safeDir = resolveSafePath(projectDirectory, customDir);
|
|
1241
|
+
const fullPath = resolve2(projectDirectory, safeDir);
|
|
1242
|
+
results.push(...scanDirectory(fullPath, "custom"));
|
|
1243
|
+
} catch {}
|
|
1244
|
+
}
|
|
1245
|
+
return results;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/features/skill-loader/loader.ts
|
|
1249
|
+
async function loadSkills(options) {
|
|
1250
|
+
const {
|
|
1251
|
+
serverUrl,
|
|
1252
|
+
projectDirectory,
|
|
1253
|
+
customDirs = [],
|
|
1254
|
+
disabledSkills = []
|
|
1255
|
+
} = options;
|
|
1256
|
+
const errors = [];
|
|
1257
|
+
const disabledSet = new Set(disabledSkills);
|
|
1258
|
+
let apiSkills = [];
|
|
1259
|
+
if (serverUrl) {
|
|
1260
|
+
try {
|
|
1261
|
+
apiSkills = await fetchSkillsFromApi(serverUrl, projectDirectory);
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
errors.push(`API skill fetch failed: ${String(err)}`);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
let fsSkills = [];
|
|
1267
|
+
try {
|
|
1268
|
+
fsSkills = discoverSkillsFromDirs({ projectDirectory, customDirs });
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
errors.push(`Filesystem skill discovery failed: ${String(err)}`);
|
|
1271
|
+
}
|
|
1272
|
+
const merged = new Map;
|
|
1273
|
+
for (const skill of fsSkills) {
|
|
1274
|
+
merged.set(skill.name, skill);
|
|
1275
|
+
}
|
|
1276
|
+
for (const skill of apiSkills) {
|
|
1277
|
+
merged.set(skill.name, skill);
|
|
1278
|
+
}
|
|
1279
|
+
const skills = [];
|
|
1280
|
+
for (const skill of merged.values()) {
|
|
1281
|
+
if (!disabledSet.has(skill.name)) {
|
|
1282
|
+
skills.push(skill);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return { skills, errors };
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/features/skill-loader/resolver.ts
|
|
1289
|
+
function createSkillResolver(loadResult) {
|
|
1290
|
+
const skillMap = new Map;
|
|
1291
|
+
for (const skill of loadResult.skills) {
|
|
1292
|
+
skillMap.set(skill.name, skill);
|
|
1293
|
+
}
|
|
1294
|
+
return (skillNames, disabledSkills) => {
|
|
1295
|
+
const parts = [];
|
|
1296
|
+
for (const name of skillNames) {
|
|
1297
|
+
if (disabledSkills?.has(name))
|
|
1298
|
+
continue;
|
|
1299
|
+
const skill = skillMap.get(name);
|
|
1300
|
+
if (skill?.content) {
|
|
1301
|
+
parts.push(skill.content);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return parts.join(`
|
|
1305
|
+
|
|
1306
|
+
`);
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/create-tools.ts
|
|
1311
|
+
async function createTools(options) {
|
|
1312
|
+
const { ctx, pluginConfig } = options;
|
|
1313
|
+
const skillResult = await loadSkills({
|
|
1314
|
+
serverUrl: ctx.serverUrl,
|
|
1315
|
+
projectDirectory: ctx.directory,
|
|
1316
|
+
disabledSkills: pluginConfig.disabled_skills,
|
|
1317
|
+
customDirs: pluginConfig.skill_directories
|
|
1318
|
+
});
|
|
1319
|
+
const resolveSkills = createSkillResolver(skillResult);
|
|
1320
|
+
return {
|
|
1321
|
+
availableSkills: skillResult.skills,
|
|
1322
|
+
resolveSkills,
|
|
1323
|
+
tools: {}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// src/agents/types.ts
|
|
1328
|
+
function isFactory(source) {
|
|
1329
|
+
return typeof source === "function";
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// src/agents/agent-builder.ts
|
|
1333
|
+
var AGENT_NAME_VARIANTS = {
|
|
1334
|
+
"tech-lead": ["tech-lead", "Tech Lead"],
|
|
1335
|
+
"lead-dev": ["lead-dev", "Lead Developer"],
|
|
1336
|
+
engineer: ["engineer", "Engineer", "Software Engineer"],
|
|
1337
|
+
architect: ["architect", "Architect", "Software Architect"],
|
|
1338
|
+
"code-analyst": ["code-analyst", "Code Analyst"],
|
|
1339
|
+
researcher: ["researcher", "Researcher", "Technical Researcher"],
|
|
1340
|
+
reviewer: ["reviewer", "Reviewer", "Code Reviewer"],
|
|
1341
|
+
tester: ["tester", "Tester"],
|
|
1342
|
+
guardian: ["guardian", "Guardian", "Security Guardian"]
|
|
1343
|
+
};
|
|
1344
|
+
function stripDisabledAgentReferences(prompt, disabledAgents) {
|
|
1345
|
+
if (disabledAgents.size === 0)
|
|
1346
|
+
return prompt;
|
|
1347
|
+
const disabledVariants = new Set;
|
|
1348
|
+
for (const name of disabledAgents) {
|
|
1349
|
+
const variants = AGENT_NAME_VARIANTS[name] ?? [name];
|
|
1350
|
+
for (const v of variants) {
|
|
1351
|
+
disabledVariants.add(v);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const lines = prompt.split(`
|
|
1355
|
+
`);
|
|
1356
|
+
const filtered = lines.filter((line) => {
|
|
1357
|
+
for (const variant of disabledVariants) {
|
|
1358
|
+
if (line.includes(variant))
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
return true;
|
|
1362
|
+
});
|
|
1363
|
+
return filtered.join(`
|
|
1364
|
+
`);
|
|
1365
|
+
}
|
|
1366
|
+
function applyCategoryConfig(base, categories) {
|
|
1367
|
+
const category = base["category"];
|
|
1368
|
+
if (!category)
|
|
1369
|
+
return base;
|
|
1370
|
+
const categoryConfig = categories[category];
|
|
1371
|
+
if (!categoryConfig)
|
|
1372
|
+
return base;
|
|
1373
|
+
return {
|
|
1374
|
+
...base,
|
|
1375
|
+
...categoryConfig.model !== undefined && { model: categoryConfig.model },
|
|
1376
|
+
...categoryConfig.temperature !== undefined && { temperature: categoryConfig.temperature },
|
|
1377
|
+
...categoryConfig.tools !== undefined && {
|
|
1378
|
+
tools: { ...base.tools ?? {}, ...categoryConfig.tools }
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function buildAgent(source, model, options) {
|
|
1383
|
+
let config = isFactory(source) ? { ...source(model) } : { ...source };
|
|
1384
|
+
if (options?.categories) {
|
|
1385
|
+
config = applyCategoryConfig(config, options.categories);
|
|
1386
|
+
}
|
|
1387
|
+
const skills = config["skills"];
|
|
1388
|
+
if (skills?.length && options?.resolveSkills) {
|
|
1389
|
+
const skillContent = options.resolveSkills(skills, options.disabledSkills);
|
|
1390
|
+
if (skillContent) {
|
|
1391
|
+
config.prompt = skillContent + (config.prompt ? `
|
|
1392
|
+
|
|
1393
|
+
` + config.prompt : "");
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (options?.disabledAgents && options.disabledAgents.size > 0 && config.prompt) {
|
|
1397
|
+
config.prompt = stripDisabledAgentReferences(config.prompt, options.disabledAgents);
|
|
1398
|
+
}
|
|
1399
|
+
return config;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/agents/model-resolution.ts
|
|
1403
|
+
var STRATEGIC_AGENTS = new Set([
|
|
1404
|
+
"tech-lead",
|
|
1405
|
+
"architect",
|
|
1406
|
+
"lead-dev"
|
|
1407
|
+
]);
|
|
1408
|
+
var STRATEGIC_MODEL_CHAIN = [
|
|
1409
|
+
"github-copilot/claude-opus-4.6",
|
|
1410
|
+
"github-copilot/claude-opus-4.5",
|
|
1411
|
+
"github-copilot/claude-sonnet-4.6",
|
|
1412
|
+
"github-copilot/claude-sonnet-4.5",
|
|
1413
|
+
"anthropic/claude-opus-4",
|
|
1414
|
+
"anthropic/claude-sonnet-4",
|
|
1415
|
+
"github-copilot/gpt-5.4",
|
|
1416
|
+
"github-copilot/gpt-4o"
|
|
1417
|
+
];
|
|
1418
|
+
var ENGINEERING_MODEL_CHAIN = [
|
|
1419
|
+
"github-copilot/claude-sonnet-4.6",
|
|
1420
|
+
"github-copilot/claude-sonnet-4.5",
|
|
1421
|
+
"github-copilot/claude-sonnet-4",
|
|
1422
|
+
"anthropic/claude-sonnet-4",
|
|
1423
|
+
"github-copilot/gpt-5.4-mini",
|
|
1424
|
+
"github-copilot/gpt-4o"
|
|
1425
|
+
];
|
|
1426
|
+
function getModelChain(agentName) {
|
|
1427
|
+
return STRATEGIC_AGENTS.has(agentName) ? STRATEGIC_MODEL_CHAIN : ENGINEERING_MODEL_CHAIN;
|
|
1428
|
+
}
|
|
1429
|
+
function getDefaultModel(agentName) {
|
|
1430
|
+
return getModelChain(agentName)[0];
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/agents/prompt-loader.ts
|
|
1434
|
+
function readPromptMd(importMetaUrl) {
|
|
1435
|
+
try {
|
|
1436
|
+
const url = new URL("prompt.md", importMetaUrl);
|
|
1437
|
+
const content = __require("fs").readFileSync(new URL(url).pathname, "utf-8");
|
|
1438
|
+
return content;
|
|
1439
|
+
} catch {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/agents/prompt-utils.ts
|
|
1445
|
+
function trimPrompt(text) {
|
|
1446
|
+
const lines = text.split(`
|
|
1447
|
+
`);
|
|
1448
|
+
const trimmedLines = lines.map((line) => line.trimEnd());
|
|
1449
|
+
const collapsed = [];
|
|
1450
|
+
let previousWasBlank = false;
|
|
1451
|
+
for (const line of trimmedLines) {
|
|
1452
|
+
const isBlank = line.trim() === "";
|
|
1453
|
+
if (isBlank && previousWasBlank)
|
|
1454
|
+
continue;
|
|
1455
|
+
collapsed.push(line);
|
|
1456
|
+
previousWasBlank = isBlank;
|
|
1457
|
+
}
|
|
1458
|
+
return collapsed.join(`
|
|
1459
|
+
`).trim();
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/agents/dynamic-prompt-builder.ts
|
|
1463
|
+
function buildDelegationTable(agents) {
|
|
1464
|
+
const rows = agents.map((a) => `| **${a.displayName}** | ${a.description} | ${a.useWhen ?? "Complex tasks in domain"} |`).join(`
|
|
1465
|
+
`);
|
|
1466
|
+
return `## Team Members
|
|
1467
|
+
|
|
1468
|
+
| Agent | Role | Delegate When |
|
|
1469
|
+
|-------|------|---------------|
|
|
1470
|
+
${rows}`;
|
|
1471
|
+
}
|
|
1472
|
+
function buildDynamicLeadPrompt(options) {
|
|
1473
|
+
const { enabledAgents, fingerprint, customAgentCount = 0 } = options;
|
|
1474
|
+
const sections = [];
|
|
1475
|
+
sections.push(buildDelegationTable(enabledAgents));
|
|
1476
|
+
if (customAgentCount > 0) {
|
|
1477
|
+
sections.push(`> **Note**: ${customAgentCount} custom agent(s) are also available. Check the full agent list when routing.`);
|
|
1478
|
+
}
|
|
1479
|
+
if (fingerprint) {
|
|
1480
|
+
sections.push(`<!-- fingerprint: ${fingerprint} -->`);
|
|
1481
|
+
}
|
|
1482
|
+
return trimPrompt(sections.join(`
|
|
1483
|
+
|
|
1484
|
+
`));
|
|
1485
|
+
}
|
|
1486
|
+
var BUILTIN_AGENT_DISPLAY = {
|
|
1487
|
+
"tech-lead": {
|
|
1488
|
+
name: "tech-lead",
|
|
1489
|
+
displayName: "Tech Lead",
|
|
1490
|
+
description: "Sets technical direction, orchestrates complex multi-step tasks, routes to specialists",
|
|
1491
|
+
useWhen: "Complex tasks needing full orchestration across multiple domains",
|
|
1492
|
+
avoidWhen: "Simple single-domain tasks"
|
|
1493
|
+
},
|
|
1494
|
+
"lead-dev": {
|
|
1495
|
+
name: "lead-dev",
|
|
1496
|
+
displayName: "Lead Developer",
|
|
1497
|
+
description: "Owns implementation — drives plan execution step-by-step, tracks progress",
|
|
1498
|
+
useWhen: "Executing an existing implementation plan",
|
|
1499
|
+
avoidWhen: "No plan exists yet"
|
|
1500
|
+
},
|
|
1501
|
+
engineer: {
|
|
1502
|
+
name: "engineer",
|
|
1503
|
+
displayName: "Software Engineer",
|
|
1504
|
+
description: "Domain-specific implementation work",
|
|
1505
|
+
useWhen: "Writing code, fixing bugs, implementing features",
|
|
1506
|
+
avoidWhen: "Planning or research phases"
|
|
1507
|
+
},
|
|
1508
|
+
architect: {
|
|
1509
|
+
name: "architect",
|
|
1510
|
+
displayName: "Software Architect",
|
|
1511
|
+
description: "Strategic planning and task breakdown",
|
|
1512
|
+
useWhen: "Creating implementation plans, breaking down complex features",
|
|
1513
|
+
avoidWhen: "Direct code writing"
|
|
1514
|
+
},
|
|
1515
|
+
"code-analyst": {
|
|
1516
|
+
name: "code-analyst",
|
|
1517
|
+
displayName: "Code Analyst",
|
|
1518
|
+
description: "Read-only codebase exploration and pattern discovery",
|
|
1519
|
+
useWhen: "Understanding existing code, finding patterns, mapping structure",
|
|
1520
|
+
avoidWhen: "Writing or modifying files"
|
|
1521
|
+
},
|
|
1522
|
+
researcher: {
|
|
1523
|
+
name: "researcher",
|
|
1524
|
+
displayName: "Technical Researcher",
|
|
1525
|
+
description: "External documentation, libraries, and best practices",
|
|
1526
|
+
useWhen: "Looking up APIs, frameworks, or external resources",
|
|
1527
|
+
avoidWhen: "Internal codebase tasks"
|
|
1528
|
+
},
|
|
1529
|
+
reviewer: {
|
|
1530
|
+
name: "reviewer",
|
|
1531
|
+
displayName: "Code Reviewer",
|
|
1532
|
+
description: "Quality review and acceptance validation",
|
|
1533
|
+
useWhen: "Reviewing PRs, validating implementations, quality checks",
|
|
1534
|
+
avoidWhen: "Initial implementation"
|
|
1535
|
+
},
|
|
1536
|
+
tester: {
|
|
1537
|
+
name: "tester",
|
|
1538
|
+
displayName: "Tester",
|
|
1539
|
+
description: "Runs tests, linters, and type checks to verify implementation quality",
|
|
1540
|
+
useWhen: "Verifying code compiles, tests pass, linting is clean",
|
|
1541
|
+
avoidWhen: "Writing code — tester only observes and reports"
|
|
1542
|
+
},
|
|
1543
|
+
guardian: {
|
|
1544
|
+
name: "guardian",
|
|
1545
|
+
displayName: "Security Guardian",
|
|
1546
|
+
description: "Security review and spec compliance",
|
|
1547
|
+
useWhen: "Security audits, vulnerability checks, compliance review",
|
|
1548
|
+
avoidWhen: "Non-security tasks"
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// src/agents/tech-lead/index.ts
|
|
1553
|
+
var INLINE_PROMPT = `<Role>
|
|
1554
|
+
You are the Tech Lead of the L.E.A.D. team — an enterprise software engineering team
|
|
1555
|
+
that delivers high-quality, secure, production-grade software.
|
|
1556
|
+
|
|
1557
|
+
You set technical direction, analyze incoming requests, determine the right specialist
|
|
1558
|
+
to handle them, and orchestrate complex multi-step work across the team.
|
|
1559
|
+
You see the big picture and make the high-level decisions.
|
|
1560
|
+
</Role>
|
|
1561
|
+
|
|
1562
|
+
<Clarification>
|
|
1563
|
+
Before acting on any non-trivial request, verify you have enough context:
|
|
1564
|
+
|
|
1565
|
+
- Is the user's intent clear and unambiguous? If not — ask.
|
|
1566
|
+
- Is critical context missing (e.g. no project familiarity, unclear scope, ambiguous requirements)? Ask.
|
|
1567
|
+
- Are there implicit assumptions that could lead the team down the wrong path? Surface them.
|
|
1568
|
+
- Could this be interpreted multiple ways? Confirm the interpretation before proceeding.
|
|
1569
|
+
|
|
1570
|
+
Use the question tool when available. Keep questions concise and specific — one focused
|
|
1571
|
+
question is better than a wall of five. Don't over-ask on obvious requests; use judgment.
|
|
1572
|
+
|
|
1573
|
+
Examples of when to ask:
|
|
1574
|
+
- "Add authentication" → what kind? OAuth? API keys? Session-based?
|
|
1575
|
+
- "Fix the bug" → which bug? Can you reproduce? Expected vs actual behavior?
|
|
1576
|
+
- "Refactor the API" → what's the goal? Performance? Maintainability? New requirements?
|
|
1577
|
+
|
|
1578
|
+
Examples of when NOT to ask:
|
|
1579
|
+
- "Fix the typo in README.md" → just do it
|
|
1580
|
+
- "Add a unit test for the login function" → clear enough, proceed
|
|
1581
|
+
- "Run the tests" → just route to engineer
|
|
1582
|
+
</Clarification>
|
|
1583
|
+
|
|
1584
|
+
<Discipline>
|
|
1585
|
+
- Break complex tasks into well-defined subtasks and delegate to the right specialist
|
|
1586
|
+
- Always identify the user's true intent before routing
|
|
1587
|
+
- Route planning tasks to the architect
|
|
1588
|
+
- Route codebase exploration to the code-analyst
|
|
1589
|
+
- Route research tasks to the researcher
|
|
1590
|
+
- Route implementation to the engineer or lead-dev
|
|
1591
|
+
- Route review tasks to the reviewer
|
|
1592
|
+
- Route security concerns to the guardian
|
|
1593
|
+
- For simple single-step tasks, handle them directly
|
|
1594
|
+
</Discipline>
|
|
1595
|
+
|
|
1596
|
+
<TicketLinking>
|
|
1597
|
+
Before delegating planning tasks to the architect, determine if the work is linked to a ticket:
|
|
1598
|
+
|
|
1599
|
+
1. Check the user's request for ticket references (e.g. PROJ-123, #45, GH-123, Jira keys like ABC-456)
|
|
1600
|
+
2. If NO ticket is mentioned, ask the user:
|
|
1601
|
+
"Does this feature have a ticket number (GitHub issue, Jira, etc.) for reference?"
|
|
1602
|
+
3. Pass the ticket reference to the architect so the plan is stored in .lead/<ticket>/
|
|
1603
|
+
4. If the user says there's no ticket, tell the architect this is an ad-hoc task (plan goes in .lead/_adhoc/)
|
|
1604
|
+
|
|
1605
|
+
This ensures plans are organized by ticket for version control traceability.
|
|
1606
|
+
</TicketLinking>
|
|
1607
|
+
|
|
1608
|
+
<PlanApproval>
|
|
1609
|
+
You are the gatekeeper between planning and implementation.
|
|
1610
|
+
|
|
1611
|
+
When the architect produces a plan:
|
|
1612
|
+
1. REVIEW it — read the plan file yourself
|
|
1613
|
+
2. Check: Is it complete? Are acceptance criteria clear? Are tasks independently executable?
|
|
1614
|
+
3. Check: Does it match the user's original intent? (Refer back to what was asked)
|
|
1615
|
+
4. If the plan is solid → tell the user it's ready and offer to start implementation
|
|
1616
|
+
5. If the plan has gaps → send it back to the architect with specific feedback, or ask the user for clarification
|
|
1617
|
+
|
|
1618
|
+
NEVER hand a plan to the lead-dev for implementation unless you've reviewed it and are confident it's ready.
|
|
1619
|
+
The lead-dev runs autonomously — a bad plan means bad output with no course correction.
|
|
1620
|
+
</PlanApproval>
|
|
1621
|
+
|
|
1622
|
+
<Style>
|
|
1623
|
+
- Terse, professional communication
|
|
1624
|
+
- No filler phrases or meta-commentary
|
|
1625
|
+
- Focused on deliverables and outcomes
|
|
1626
|
+
- Enterprise-grade quality standards
|
|
1627
|
+
</Style>`;
|
|
1628
|
+
var BASE_PROMPT = readPromptMd(import.meta.url) ?? INLINE_PROMPT;
|
|
1629
|
+
var LEAD_DEFAULTS = {
|
|
1630
|
+
description: "Tech Lead",
|
|
1631
|
+
temperature: 1,
|
|
1632
|
+
tools: {
|
|
1633
|
+
bash: false,
|
|
1634
|
+
edit: false,
|
|
1635
|
+
write: false
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
function createLeadAgentWithOptions(model, options = {}) {
|
|
1639
|
+
const { disabledAgents = new Set, fingerprint, customAgents = [] } = options;
|
|
1640
|
+
const enabledBuiltins = Object.values(BUILTIN_AGENT_DISPLAY).filter((a) => !disabledAgents.has(a.name) && a.name !== "tech-lead");
|
|
1641
|
+
const allAgents = [...enabledBuiltins, ...customAgents];
|
|
1642
|
+
const dynamicSection = buildDynamicLeadPrompt({
|
|
1643
|
+
enabledAgents: allAgents,
|
|
1644
|
+
fingerprint,
|
|
1645
|
+
customAgentCount: customAgents.length
|
|
1646
|
+
});
|
|
1647
|
+
const fullPrompt = [BASE_PROMPT, dynamicSection].filter(Boolean).join(`
|
|
1648
|
+
|
|
1649
|
+
`);
|
|
1650
|
+
return {
|
|
1651
|
+
...LEAD_DEFAULTS,
|
|
1652
|
+
model,
|
|
1653
|
+
prompt: fullPrompt
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
var createLeadAgent = Object.assign((model) => createLeadAgentWithOptions(model), { mode: "primary" });
|
|
1657
|
+
|
|
1658
|
+
// src/agents/lead-dev/index.ts
|
|
1659
|
+
var INLINE_PROMPT2 = `<Role>
|
|
1660
|
+
You are the Lead Developer of the L.E.A.D. team.
|
|
1661
|
+
You own the implementation — taking approved plans and driving them to completion
|
|
1662
|
+
step-by-step, tracking progress, running verifications, and ensuring quality at each stage.
|
|
1663
|
+
</Role>
|
|
1664
|
+
|
|
1665
|
+
<AutonomousExecution>
|
|
1666
|
+
You run autonomously. The plan you receive has been reviewed and approved by the Tech Lead.
|
|
1667
|
+
Trust it. Execute it. Do not second-guess scope or re-negotiate requirements.
|
|
1668
|
+
|
|
1669
|
+
If you encounter a genuine blocker (broken dependency, missing API, contradictory requirements),
|
|
1670
|
+
report it clearly and stop — but do NOT stop for minor ambiguities you can resolve yourself.
|
|
1671
|
+
Use your engineering judgment for implementation details the plan leaves open.
|
|
1672
|
+
</AutonomousExecution>
|
|
1673
|
+
|
|
1674
|
+
<Discipline>
|
|
1675
|
+
TODO OBSESSION (NON-NEGOTIABLE):
|
|
1676
|
+
- Load existing todos first — never re-plan if a plan exists
|
|
1677
|
+
- Mark in_progress before starting EACH task (ONE at a time)
|
|
1678
|
+
- Mark completed IMMEDIATELY after finishing
|
|
1679
|
+
- NEVER skip steps, NEVER batch completions
|
|
1680
|
+
|
|
1681
|
+
Execution without todos = lost work.
|
|
1682
|
+
</Discipline>
|
|
1683
|
+
|
|
1684
|
+
<PlanExecution>
|
|
1685
|
+
When activated by /implement with a plan file:
|
|
1686
|
+
|
|
1687
|
+
1. READ the plan file first — understand the full scope
|
|
1688
|
+
2. FIND the first unchecked \`- [ ]\` task
|
|
1689
|
+
3. For each task:
|
|
1690
|
+
a. Read the task description, files, and acceptance criteria
|
|
1691
|
+
b. Execute the work (write code, run commands, create files)
|
|
1692
|
+
c. DELEGATE to the **tester** agent for verification (see Verification Cycle below)
|
|
1693
|
+
d. If tester returns [PASS]: mark complete (\`- [ ]\` → \`- [x]\`) and report "Completed task N/M: [title]"
|
|
1694
|
+
e. If tester returns [FAIL]: fix the issues, then delegate to tester again. Repeat until [PASS].
|
|
1695
|
+
4. CONTINUE to the next unchecked task
|
|
1696
|
+
5. When ALL checkboxes are checked, report final summary
|
|
1697
|
+
|
|
1698
|
+
NEVER stop mid-plan unless explicitly told to or completely blocked.
|
|
1699
|
+
</PlanExecution>
|
|
1700
|
+
|
|
1701
|
+
<VerificationCycle>
|
|
1702
|
+
After implementing each task, delegate to the **tester** agent to run project-specific
|
|
1703
|
+
verification (type checks, linting, tests). This is your implement → verify → fix loop:
|
|
1704
|
+
|
|
1705
|
+
implement → tester [PASS] → mark complete → next task
|
|
1706
|
+
implement → tester [FAIL] → fix → tester [PASS] → mark complete → next task
|
|
1707
|
+
|
|
1708
|
+
Rules:
|
|
1709
|
+
- ALWAYS delegate to tester after implementing a task — do not self-verify
|
|
1710
|
+
- Only mark \`- [x]\` after receiving [PASS] from tester
|
|
1711
|
+
- If tester returns [FAIL], fix the reported issues and delegate to tester again
|
|
1712
|
+
- Maximum 3 fix cycles per task — if still failing after 3 attempts, note the failure and move on
|
|
1713
|
+
|
|
1714
|
+
EXCEPTION — Compilation blockers:
|
|
1715
|
+
If code does not compile at all (e.g., missing types from a not-yet-implemented task),
|
|
1716
|
+
you may batch a logical group of related tasks before running verification.
|
|
1717
|
+
Complete the group, THEN delegate to tester for the batch.
|
|
1718
|
+
Mark individual tasks only after the group passes verification.
|
|
1719
|
+
</VerificationCycle>
|
|
1720
|
+
|
|
1721
|
+
<Style>
|
|
1722
|
+
- Terse status updates only
|
|
1723
|
+
- No meta-commentary
|
|
1724
|
+
- Dense > verbose
|
|
1725
|
+
</Style>`;
|
|
1726
|
+
var BASE_PROMPT2 = readPromptMd(import.meta.url) ?? INLINE_PROMPT2;
|
|
1727
|
+
var EXECUTOR_DEFAULTS = {
|
|
1728
|
+
description: "Lead Developer",
|
|
1729
|
+
temperature: 1
|
|
1730
|
+
};
|
|
1731
|
+
function createExecutorAgentWithOptions(model, _disabledAgents, _continuation) {
|
|
1732
|
+
return {
|
|
1733
|
+
...EXECUTOR_DEFAULTS,
|
|
1734
|
+
model,
|
|
1735
|
+
prompt: BASE_PROMPT2
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
var createExecutorAgent = Object.assign((model) => createExecutorAgentWithOptions(model), { mode: "subagent" });
|
|
1739
|
+
|
|
1740
|
+
// src/agents/engineer/index.ts
|
|
1741
|
+
var INLINE_PROMPT3 = `<Role>
|
|
1742
|
+
You are a Software Engineer on the L.E.A.D. team.
|
|
1743
|
+
You implement features, fix bugs, write tests, and deliver working, production-grade code.
|
|
1744
|
+
</Role>
|
|
1745
|
+
|
|
1746
|
+
<Discipline>
|
|
1747
|
+
- Write clean, well-typed, maintainable code
|
|
1748
|
+
- Follow existing patterns and conventions in the codebase
|
|
1749
|
+
- Add tests for all new logic
|
|
1750
|
+
- Verify your implementation compiles and tests pass before finishing
|
|
1751
|
+
- Prefer existing utilities and libraries over custom implementations
|
|
1752
|
+
</Discipline>
|
|
1753
|
+
|
|
1754
|
+
<QualityStandards>
|
|
1755
|
+
- Enterprise-grade error handling
|
|
1756
|
+
- Comprehensive TypeScript types
|
|
1757
|
+
- No console.log or debug artifacts in production code
|
|
1758
|
+
- Security-conscious by default
|
|
1759
|
+
</QualityStandards>
|
|
1760
|
+
|
|
1761
|
+
<Style>
|
|
1762
|
+
- Professional and concise
|
|
1763
|
+
- Show implementation evidence (file paths, test output)
|
|
1764
|
+
- No meta-commentary
|
|
1765
|
+
</Style>`;
|
|
1766
|
+
var BASE_PROMPT3 = readPromptMd(import.meta.url) ?? INLINE_PROMPT3;
|
|
1767
|
+
var ENGINEER_DEFAULTS = {
|
|
1768
|
+
description: "Software Engineer",
|
|
1769
|
+
temperature: 1
|
|
1770
|
+
};
|
|
1771
|
+
var createEngineerAgent = Object.assign((model) => ({
|
|
1772
|
+
...ENGINEER_DEFAULTS,
|
|
1773
|
+
model,
|
|
1774
|
+
prompt: BASE_PROMPT3
|
|
1775
|
+
}), { mode: "all" });
|
|
1776
|
+
|
|
1777
|
+
// src/agents/architect/index.ts
|
|
1778
|
+
var INLINE_PROMPT4 = `<Role>
|
|
1779
|
+
You are the Software Architect of the L.E.A.D. team.
|
|
1780
|
+
You produce strategic implementation plans, break down complex features,
|
|
1781
|
+
and define the technical approach before any code is written.
|
|
1782
|
+
</Role>
|
|
1783
|
+
|
|
1784
|
+
<Constraints>
|
|
1785
|
+
- ONLY write .md files inside the .lead/ directory
|
|
1786
|
+
- NEVER write code files (.ts, .js, .py, .go, etc.)
|
|
1787
|
+
- NEVER edit source code
|
|
1788
|
+
</Constraints>
|
|
1789
|
+
|
|
1790
|
+
<PlanLocation>
|
|
1791
|
+
Plan storage depends on whether a ticket is linked:
|
|
1792
|
+
|
|
1793
|
+
- **Ticket-linked**: \`.lead/<ticket>/plan.md\` (e.g. \`.lead/PROJ-123/plan.md\`, \`.lead/GH-45/plan.md\`)
|
|
1794
|
+
- **Ad-hoc (no ticket)**: \`.lead/_adhoc/{slug}.md\` (e.g. \`.lead/_adhoc/build-auth.md\`)
|
|
1795
|
+
|
|
1796
|
+
The Tech Lead will tell you which applies. If a ticket reference is provided, use it as the directory name.
|
|
1797
|
+
The plan file inside a ticket directory is always named \`plan.md\`.
|
|
1798
|
+
</PlanLocation>
|
|
1799
|
+
|
|
1800
|
+
<PlanFormat>
|
|
1801
|
+
Each plan must contain:
|
|
1802
|
+
- TL;DR with summary and estimated effort
|
|
1803
|
+
- Context section with key findings
|
|
1804
|
+
- Objectives with deliverables and acceptance criteria
|
|
1805
|
+
- TODOs with task descriptions, file targets, and acceptance criteria per task
|
|
1806
|
+
- Verification checklist
|
|
1807
|
+
|
|
1808
|
+
Plans must be actionable — each TODO must be independently executable.
|
|
1809
|
+
</PlanFormat>
|
|
1810
|
+
|
|
1811
|
+
<Style>
|
|
1812
|
+
- Precise and unambiguous
|
|
1813
|
+
- Every task has clear acceptance criteria
|
|
1814
|
+
- Plans are complete enough to hand off without follow-up questions
|
|
1815
|
+
</Style>`;
|
|
1816
|
+
var BASE_PROMPT4 = readPromptMd(import.meta.url) ?? INLINE_PROMPT4;
|
|
1817
|
+
var ARCHITECT_DEFAULTS = {
|
|
1818
|
+
description: "Software Architect",
|
|
1819
|
+
temperature: 1,
|
|
1820
|
+
tools: {
|
|
1821
|
+
bash: false,
|
|
1822
|
+
edit: false
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
var createArchitectAgent = Object.assign((model) => ({
|
|
1826
|
+
...ARCHITECT_DEFAULTS,
|
|
1827
|
+
model,
|
|
1828
|
+
prompt: BASE_PROMPT4
|
|
1829
|
+
}), { mode: "subagent" });
|
|
1830
|
+
|
|
1831
|
+
// src/agents/code-analyst/index.ts
|
|
1832
|
+
var INLINE_PROMPT5 = `<Role>
|
|
1833
|
+
You are the Code Analyst of the L.E.A.D. team.
|
|
1834
|
+
You explore, map, and understand codebases — reading files, tracing dependencies,
|
|
1835
|
+
identifying patterns, and surfacing insights for other team members.
|
|
1836
|
+
</Role>
|
|
1837
|
+
|
|
1838
|
+
<Constraints>
|
|
1839
|
+
- READ ONLY — never write, edit, or delete files
|
|
1840
|
+
- Never execute code or run commands that modify state
|
|
1841
|
+
- Return findings as structured summaries
|
|
1842
|
+
</Constraints>
|
|
1843
|
+
|
|
1844
|
+
<Discipline>
|
|
1845
|
+
- Always start with a high-level structural scan before diving into details
|
|
1846
|
+
- Trace call chains and data flows to understand behavior
|
|
1847
|
+
- Identify patterns: naming conventions, error handling, test structure
|
|
1848
|
+
- Note anomalies, inconsistencies, and technical debt
|
|
1849
|
+
- Provide file paths and line numbers for all findings
|
|
1850
|
+
</Discipline>
|
|
1851
|
+
|
|
1852
|
+
<Style>
|
|
1853
|
+
- Dense, structured output (use headers, code blocks, file paths)
|
|
1854
|
+
- Lead with the most important findings
|
|
1855
|
+
- No speculation — only what the code actually shows
|
|
1856
|
+
</Style>`;
|
|
1857
|
+
var BASE_PROMPT5 = readPromptMd(import.meta.url) ?? INLINE_PROMPT5;
|
|
1858
|
+
var CODE_ANALYST_DEFAULTS = {
|
|
1859
|
+
description: "Code Analyst",
|
|
1860
|
+
temperature: 1,
|
|
1861
|
+
tools: {
|
|
1862
|
+
bash: false,
|
|
1863
|
+
edit: false,
|
|
1864
|
+
write: false
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
var createCodeAnalystAgent = Object.assign((model) => ({
|
|
1868
|
+
...CODE_ANALYST_DEFAULTS,
|
|
1869
|
+
model,
|
|
1870
|
+
prompt: BASE_PROMPT5
|
|
1871
|
+
}), { mode: "subagent" });
|
|
1872
|
+
|
|
1873
|
+
// src/agents/researcher/index.ts
|
|
1874
|
+
var INLINE_PROMPT6 = `<Role>
|
|
1875
|
+
You are the Technical Researcher of the L.E.A.D. team.
|
|
1876
|
+
You research external documentation, libraries, APIs, and best practices —
|
|
1877
|
+
providing accurate, up-to-date information to inform technical decisions.
|
|
1878
|
+
</Role>
|
|
1879
|
+
|
|
1880
|
+
<Discipline>
|
|
1881
|
+
- Always fetch from authoritative sources (official docs, npm, GitHub)
|
|
1882
|
+
- Verify version compatibility before recommending dependencies
|
|
1883
|
+
- Prefer stable, well-maintained packages
|
|
1884
|
+
- Return structured summaries with code examples
|
|
1885
|
+
- Cite sources with URLs and version numbers
|
|
1886
|
+
</Discipline>
|
|
1887
|
+
|
|
1888
|
+
<Style>
|
|
1889
|
+
- Structured output with clear sections
|
|
1890
|
+
- Include code examples from docs
|
|
1891
|
+
- Note caveats, gotchas, and version differences
|
|
1892
|
+
- Always indicate confidence level and source recency
|
|
1893
|
+
</Style>`;
|
|
1894
|
+
var BASE_PROMPT6 = readPromptMd(import.meta.url) ?? INLINE_PROMPT6;
|
|
1895
|
+
var RESEARCHER_DEFAULTS = {
|
|
1896
|
+
description: "Technical Researcher",
|
|
1897
|
+
temperature: 1,
|
|
1898
|
+
tools: {
|
|
1899
|
+
edit: false,
|
|
1900
|
+
write: false,
|
|
1901
|
+
bash: false
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
var createResearcherAgent = Object.assign((model) => ({
|
|
1905
|
+
...RESEARCHER_DEFAULTS,
|
|
1906
|
+
model,
|
|
1907
|
+
prompt: BASE_PROMPT6
|
|
1908
|
+
}), { mode: "subagent" });
|
|
1909
|
+
|
|
1910
|
+
// src/agents/reviewer/index.ts
|
|
1911
|
+
var INLINE_PROMPT7 = `<Role>
|
|
1912
|
+
You are the Code Reviewer of the L.E.A.D. team.
|
|
1913
|
+
You perform thorough, objective code reviews — validating implementations against
|
|
1914
|
+
requirements, ensuring quality, and identifying issues before they reach production.
|
|
1915
|
+
</Role>
|
|
1916
|
+
|
|
1917
|
+
<ReviewProcess>
|
|
1918
|
+
For each review:
|
|
1919
|
+
1. Verify the implementation matches the stated requirements
|
|
1920
|
+
2. Check for correctness: logic errors, edge cases, error handling
|
|
1921
|
+
3. Assess code quality: readability, naming, structure
|
|
1922
|
+
4. Verify tests exist and are meaningful
|
|
1923
|
+
5. Check for security issues (injection, auth, data exposure)
|
|
1924
|
+
6. Return a verdict: [APPROVE] or [REJECT]
|
|
1925
|
+
</ReviewProcess>
|
|
1926
|
+
|
|
1927
|
+
<Verdict>
|
|
1928
|
+
Always end your review with one of:
|
|
1929
|
+
- [APPROVE] — implementation is correct and meets quality standards
|
|
1930
|
+
- [REJECT] — implementation has blocking issues (list each one)
|
|
1931
|
+
|
|
1932
|
+
A rejection must list every blocking issue clearly.
|
|
1933
|
+
</Verdict>
|
|
1934
|
+
|
|
1935
|
+
<Style>
|
|
1936
|
+
- Objective and evidence-based
|
|
1937
|
+
- Reference specific files and line numbers
|
|
1938
|
+
- No praise without substance — focus on findings
|
|
1939
|
+
</Style>`;
|
|
1940
|
+
var BASE_PROMPT7 = readPromptMd(import.meta.url) ?? INLINE_PROMPT7;
|
|
1941
|
+
var REVIEWER_DEFAULTS = {
|
|
1942
|
+
description: "Code Reviewer",
|
|
1943
|
+
temperature: 1,
|
|
1944
|
+
tools: {
|
|
1945
|
+
edit: false,
|
|
1946
|
+
write: false,
|
|
1947
|
+
bash: false
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
var createReviewerAgent = Object.assign((model) => ({
|
|
1951
|
+
...REVIEWER_DEFAULTS,
|
|
1952
|
+
model,
|
|
1953
|
+
prompt: BASE_PROMPT7
|
|
1954
|
+
}), { mode: "subagent" });
|
|
1955
|
+
|
|
1956
|
+
// src/agents/guardian/index.ts
|
|
1957
|
+
var INLINE_PROMPT8 = `<Role>
|
|
1958
|
+
You are the Security Guardian of the L.E.A.D. team.
|
|
1959
|
+
You perform security reviews, identify vulnerabilities, and ensure enterprise-grade
|
|
1960
|
+
security posture across all implementations.
|
|
1961
|
+
</Role>
|
|
1962
|
+
|
|
1963
|
+
<SecurityFramework>
|
|
1964
|
+
Evaluate against OWASP Top 10 and enterprise security requirements:
|
|
1965
|
+
1. Injection (SQL, command, path traversal)
|
|
1966
|
+
2. Broken authentication and session management
|
|
1967
|
+
3. Sensitive data exposure
|
|
1968
|
+
4. Insecure direct object references
|
|
1969
|
+
5. Security misconfiguration
|
|
1970
|
+
6. Cryptographic failures
|
|
1971
|
+
7. Vulnerable dependencies
|
|
1972
|
+
8. Insufficient logging and monitoring
|
|
1973
|
+
9. SSRF and request forgery
|
|
1974
|
+
10. Supply chain risks
|
|
1975
|
+
</SecurityFramework>
|
|
1976
|
+
|
|
1977
|
+
<ReviewProcess>
|
|
1978
|
+
For each security review:
|
|
1979
|
+
1. Map the attack surface: inputs, outputs, trust boundaries
|
|
1980
|
+
2. Check authentication and authorization flows
|
|
1981
|
+
3. Verify data validation and sanitization
|
|
1982
|
+
4. Assess secret and credential handling
|
|
1983
|
+
5. Review dependency security posture
|
|
1984
|
+
6. Return verdict: [APPROVE] or [REJECT]
|
|
1985
|
+
</ReviewProcess>
|
|
1986
|
+
|
|
1987
|
+
<Verdict>
|
|
1988
|
+
Always end your review with:
|
|
1989
|
+
- [APPROVE] — no critical or high security issues found
|
|
1990
|
+
- [REJECT] — critical or high issues found (list each with severity)
|
|
1991
|
+
|
|
1992
|
+
Self-triage: if no security-relevant changes, fast-exit with [APPROVE].
|
|
1993
|
+
</Verdict>
|
|
1994
|
+
|
|
1995
|
+
<Style>
|
|
1996
|
+
- Severity-first: CRITICAL > HIGH > MEDIUM > LOW
|
|
1997
|
+
- Include CVE references where applicable
|
|
1998
|
+
- Provide remediation guidance for each finding
|
|
1999
|
+
</Style>`;
|
|
2000
|
+
var BASE_PROMPT8 = readPromptMd(import.meta.url) ?? INLINE_PROMPT8;
|
|
2001
|
+
var GUARDIAN_DEFAULTS = {
|
|
2002
|
+
description: "Security Guardian",
|
|
2003
|
+
temperature: 1,
|
|
2004
|
+
tools: {
|
|
2005
|
+
edit: false,
|
|
2006
|
+
write: false,
|
|
2007
|
+
bash: false
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
var createGuardianAgent = Object.assign((model) => ({
|
|
2011
|
+
...GUARDIAN_DEFAULTS,
|
|
2012
|
+
model,
|
|
2013
|
+
prompt: BASE_PROMPT8
|
|
2014
|
+
}), { mode: "subagent" });
|
|
2015
|
+
|
|
2016
|
+
// src/agents/tester/index.ts
|
|
2017
|
+
var INLINE_PROMPT9 = `<Role>
|
|
2018
|
+
You are the Tester of the L.E.A.D. team.
|
|
2019
|
+
You verify implementation quality by running project-specific tests, linters, and type checks.
|
|
2020
|
+
You report objective pass/fail results — you never fix code yourself.
|
|
2021
|
+
</Role>
|
|
2022
|
+
|
|
2023
|
+
<ProjectContext>
|
|
2024
|
+
ALWAYS look for an AGENTS.md file in the project root first. This file contains
|
|
2025
|
+
project-specific instructions for testing, linting, building, and verification commands.
|
|
2026
|
+
|
|
2027
|
+
If AGENTS.md exists, follow its verification instructions exactly.
|
|
2028
|
+
If AGENTS.md does not exist, use standard detection:
|
|
2029
|
+
1. Check for common config files (package.json, build.gradle, Cargo.toml, Makefile, etc.)
|
|
2030
|
+
2. Infer the appropriate test/lint/build commands from the project structure
|
|
2031
|
+
3. Run what's available — don't fail just because a specific tool is missing
|
|
2032
|
+
</ProjectContext>
|
|
2033
|
+
|
|
2034
|
+
<VerificationProcess>
|
|
2035
|
+
For each verification request:
|
|
2036
|
+
|
|
2037
|
+
1. READ the AGENTS.md file (if it exists) for project-specific commands
|
|
2038
|
+
2. RUN the verification steps in order:
|
|
2039
|
+
a. Type check / Compile — ensure the code compiles without errors
|
|
2040
|
+
b. Lint — run the project's linter if configured
|
|
2041
|
+
c. Unit tests — run the test suite (or relevant subset)
|
|
2042
|
+
d. Targeted check — if specific files were changed, focus tests on those areas
|
|
2043
|
+
3. ANALYZE the output — distinguish real failures from pre-existing issues
|
|
2044
|
+
4. REPORT with a clear verdict
|
|
2045
|
+
|
|
2046
|
+
If a command fails to run (tool not installed, config missing), note it as SKIPPED, not FAIL.
|
|
2047
|
+
Only report FAIL for actual code quality or correctness issues introduced by the recent changes.
|
|
2048
|
+
</VerificationProcess>
|
|
2049
|
+
|
|
2050
|
+
<Verdict>
|
|
2051
|
+
Always end your verification with exactly one of:
|
|
2052
|
+
|
|
2053
|
+
- [PASS] — all verification steps passed or had only pre-existing issues
|
|
2054
|
+
- [FAIL] — new issues found that need fixing
|
|
2055
|
+
|
|
2056
|
+
A [FAIL] verdict MUST include:
|
|
2057
|
+
1. Which step failed (typecheck, lint, test)
|
|
2058
|
+
2. The exact error output (trimmed to relevant lines)
|
|
2059
|
+
3. Which file(s) and line(s) are affected
|
|
2060
|
+
|
|
2061
|
+
Keep it concise — the engineer needs actionable information, not a wall of logs.
|
|
2062
|
+
</Verdict>
|
|
2063
|
+
|
|
2064
|
+
<Style>
|
|
2065
|
+
- Report results, not process
|
|
2066
|
+
- Quote exact error messages
|
|
2067
|
+
- No opinions on code style — only objective failures
|
|
2068
|
+
- Dense > verbose
|
|
2069
|
+
</Style>`;
|
|
2070
|
+
var BASE_PROMPT9 = readPromptMd(import.meta.url) ?? INLINE_PROMPT9;
|
|
2071
|
+
var TESTER_DEFAULTS = {
|
|
2072
|
+
description: "Tester",
|
|
2073
|
+
temperature: 1,
|
|
2074
|
+
tools: {
|
|
2075
|
+
edit: false,
|
|
2076
|
+
write: false
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
var createTesterAgent = Object.assign((model) => ({
|
|
2080
|
+
...TESTER_DEFAULTS,
|
|
2081
|
+
model,
|
|
2082
|
+
prompt: BASE_PROMPT9
|
|
2083
|
+
}), { mode: "subagent" });
|
|
2084
|
+
|
|
2085
|
+
// src/agents/builtin-agents.ts
|
|
2086
|
+
var AGENT_FACTORIES = {
|
|
2087
|
+
"tech-lead": createLeadAgent,
|
|
2088
|
+
"lead-dev": createExecutorAgent,
|
|
2089
|
+
engineer: createEngineerAgent,
|
|
2090
|
+
architect: createArchitectAgent,
|
|
2091
|
+
"code-analyst": createCodeAnalystAgent,
|
|
2092
|
+
researcher: createResearcherAgent,
|
|
2093
|
+
reviewer: createReviewerAgent,
|
|
2094
|
+
tester: createTesterAgent,
|
|
2095
|
+
guardian: createGuardianAgent
|
|
2096
|
+
};
|
|
2097
|
+
var customAgentMetadata = new Map;
|
|
2098
|
+
function registerCustomAgentMetadata(name, metadata) {
|
|
2099
|
+
customAgentMetadata.set(name, metadata);
|
|
2100
|
+
}
|
|
2101
|
+
function createBuiltinAgents(options = {}) {
|
|
2102
|
+
const {
|
|
2103
|
+
uiModel,
|
|
2104
|
+
disabledAgents = new Set,
|
|
2105
|
+
configOverrides = {},
|
|
2106
|
+
buildOptions = {}
|
|
2107
|
+
} = options;
|
|
2108
|
+
const result = {};
|
|
2109
|
+
for (const [name, factory] of Object.entries(AGENT_FACTORIES)) {
|
|
2110
|
+
if (disabledAgents.has(name))
|
|
2111
|
+
continue;
|
|
2112
|
+
const resolvedModel = factory.mode === "primary" && uiModel ? uiModel : getDefaultModel(name);
|
|
2113
|
+
let config = buildAgent(factory, resolvedModel, {
|
|
2114
|
+
...buildOptions,
|
|
2115
|
+
disabledAgents
|
|
2116
|
+
});
|
|
2117
|
+
config.mode = factory.mode;
|
|
2118
|
+
const override = configOverrides[name];
|
|
2119
|
+
if (override) {
|
|
2120
|
+
config = {
|
|
2121
|
+
...config,
|
|
2122
|
+
...override.model !== undefined && { model: override.model },
|
|
2123
|
+
...override.tools !== undefined && {
|
|
2124
|
+
tools: { ...config.tools ?? {}, ...override.tools }
|
|
2125
|
+
}
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
result[name] = config;
|
|
2129
|
+
}
|
|
2130
|
+
return result;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// src/agents/custom-agent-factory.ts
|
|
2134
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2135
|
+
function buildCustomAgent(name, config, options = {}) {
|
|
2136
|
+
const { directory } = options;
|
|
2137
|
+
let promptText = config.prompt;
|
|
2138
|
+
if (!promptText && config.prompt_file && directory) {
|
|
2139
|
+
const safePath = resolveSafePath(directory, config.prompt_file);
|
|
2140
|
+
if (safePath) {
|
|
2141
|
+
try {
|
|
2142
|
+
promptText = readFileSync3(safePath, "utf-8");
|
|
2143
|
+
} catch {}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const baseConfig = {
|
|
2147
|
+
description: config.description ?? name,
|
|
2148
|
+
model: config.model,
|
|
2149
|
+
mode: config.mode,
|
|
2150
|
+
prompt: promptText,
|
|
2151
|
+
tools: config.tools,
|
|
2152
|
+
skills: config.skills
|
|
2153
|
+
};
|
|
2154
|
+
for (const key of Object.keys(baseConfig)) {
|
|
2155
|
+
if (baseConfig[key] === undefined) {
|
|
2156
|
+
delete baseConfig[key];
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return buildAgent(baseConfig, options.buildOptions?.model ?? config.model ?? "", {
|
|
2160
|
+
...options,
|
|
2161
|
+
disabledAgents: options.disabledAgents,
|
|
2162
|
+
resolveSkills: options.resolveSkills,
|
|
2163
|
+
disabledSkills: options.disabledSkills
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
function buildCustomAgentMetadata(name, config) {
|
|
2167
|
+
return {
|
|
2168
|
+
category: "custom",
|
|
2169
|
+
cost: "MODERATE",
|
|
2170
|
+
triggers: (config.triggers ?? []).map((t) => ({
|
|
2171
|
+
domain: t.domain,
|
|
2172
|
+
trigger: t.trigger
|
|
2173
|
+
})),
|
|
2174
|
+
useWhen: config.description ?? `Custom agent: ${name}`
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/shared/agent-display-names.ts
|
|
2179
|
+
var BUILTIN_DISPLAY_NAMES = {
|
|
2180
|
+
"tech-lead": "Tech Lead",
|
|
2181
|
+
engineer: "Engineer",
|
|
2182
|
+
"lead-dev": "Lead Developer",
|
|
2183
|
+
architect: "Architect",
|
|
2184
|
+
"code-analyst": "Code Analyst",
|
|
2185
|
+
researcher: "Researcher",
|
|
2186
|
+
reviewer: "Reviewer",
|
|
2187
|
+
tester: "Tester",
|
|
2188
|
+
guardian: "Guardian"
|
|
2189
|
+
};
|
|
2190
|
+
var overrides = {};
|
|
2191
|
+
function getAgentDisplayName(key) {
|
|
2192
|
+
return overrides[key] ?? BUILTIN_DISPLAY_NAMES[key] ?? key;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/managers/config-handler.ts
|
|
2196
|
+
class ConfigHandler {
|
|
2197
|
+
builtinAgents;
|
|
2198
|
+
customAgents;
|
|
2199
|
+
constructor(options) {
|
|
2200
|
+
this.builtinAgents = options.builtinAgents;
|
|
2201
|
+
this.customAgents = options.customAgents ?? {};
|
|
2202
|
+
}
|
|
2203
|
+
handle(config) {
|
|
2204
|
+
const existing = config.agent ?? {};
|
|
2205
|
+
const merged = {};
|
|
2206
|
+
for (const [name, agentConfig] of Object.entries(this.builtinAgents)) {
|
|
2207
|
+
const displayName = getAgentDisplayName(name);
|
|
2208
|
+
merged[displayName] = {
|
|
2209
|
+
...agentConfig,
|
|
2210
|
+
description: agentConfig.description ?? displayName
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
for (const [name, agentConfig] of Object.entries(this.customAgents)) {
|
|
2214
|
+
const displayName = getAgentDisplayName(name);
|
|
2215
|
+
merged[displayName] = agentConfig;
|
|
2216
|
+
}
|
|
2217
|
+
for (const [name, agentConfig] of Object.entries(existing)) {
|
|
2218
|
+
if (!(name in merged)) {
|
|
2219
|
+
merged[name] = agentConfig;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
config.agent = merged;
|
|
2223
|
+
config.default_agent = getAgentDisplayName("tech-lead");
|
|
2224
|
+
if (!config.permission || typeof config.permission === "string") {
|
|
2225
|
+
config.permission = { question: "allow" };
|
|
2226
|
+
} else if (!config.permission.question) {
|
|
2227
|
+
config.permission.question = "allow";
|
|
2228
|
+
}
|
|
2229
|
+
const dcpPlugin = "@tarquinen/opencode-dcp@latest";
|
|
2230
|
+
if (!config.plugin) {
|
|
2231
|
+
config.plugin = [dcpPlugin];
|
|
2232
|
+
} else {
|
|
2233
|
+
const alreadyPresent = config.plugin.some((entry) => typeof entry === "string" ? entry.startsWith("@tarquinen/opencode-dcp") : Array.isArray(entry) && entry[0]?.startsWith("@tarquinen/opencode-dcp"));
|
|
2234
|
+
if (!alreadyPresent) {
|
|
2235
|
+
config.plugin.push(dcpPlugin);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
getAgentNames() {
|
|
2240
|
+
return [...Object.keys(this.builtinAgents), ...Object.keys(this.customAgents)];
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// src/managers/background-manager.ts
|
|
2245
|
+
var taskCounter = 0;
|
|
2246
|
+
function generateTaskId() {
|
|
2247
|
+
taskCounter++;
|
|
2248
|
+
return `task-${Date.now()}-${taskCounter}`;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
class BackgroundManager {
|
|
2252
|
+
tasks = new Map;
|
|
2253
|
+
maxConcurrent;
|
|
2254
|
+
constructor(maxConcurrent = 3) {
|
|
2255
|
+
this.maxConcurrent = maxConcurrent;
|
|
2256
|
+
}
|
|
2257
|
+
spawn(options) {
|
|
2258
|
+
const id = generateTaskId();
|
|
2259
|
+
const record = {
|
|
2260
|
+
id,
|
|
2261
|
+
status: this.getRunningCount() < this.maxConcurrent ? "running" : "pending",
|
|
2262
|
+
options,
|
|
2263
|
+
startedAt: new Date
|
|
2264
|
+
};
|
|
2265
|
+
this.tasks.set(id, record);
|
|
2266
|
+
return id;
|
|
2267
|
+
}
|
|
2268
|
+
getTask(taskId) {
|
|
2269
|
+
return this.tasks.get(taskId);
|
|
2270
|
+
}
|
|
2271
|
+
cancel(taskId) {
|
|
2272
|
+
const task = this.tasks.get(taskId);
|
|
2273
|
+
if (!task || task.status === "completed" || task.status === "failed") {
|
|
2274
|
+
return false;
|
|
2275
|
+
}
|
|
2276
|
+
task.status = "cancelled";
|
|
2277
|
+
task.completedAt = new Date;
|
|
2278
|
+
return true;
|
|
2279
|
+
}
|
|
2280
|
+
cancelAll() {
|
|
2281
|
+
for (const task of this.tasks.values()) {
|
|
2282
|
+
if (task.status === "pending" || task.status === "running") {
|
|
2283
|
+
task.status = "cancelled";
|
|
2284
|
+
task.completedAt = new Date;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
list(filter) {
|
|
2289
|
+
const all = Array.from(this.tasks.values());
|
|
2290
|
+
if (!filter?.status)
|
|
2291
|
+
return all;
|
|
2292
|
+
return all.filter((t) => t.status === filter.status);
|
|
2293
|
+
}
|
|
2294
|
+
getRunningCount() {
|
|
2295
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === "running").length;
|
|
2296
|
+
}
|
|
2297
|
+
complete(taskId, result) {
|
|
2298
|
+
const task = this.tasks.get(taskId);
|
|
2299
|
+
if (!task || task.status !== "running")
|
|
2300
|
+
return false;
|
|
2301
|
+
task.status = "completed";
|
|
2302
|
+
task.result = result;
|
|
2303
|
+
task.completedAt = new Date;
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2306
|
+
fail(taskId, error) {
|
|
2307
|
+
const task = this.tasks.get(taskId);
|
|
2308
|
+
if (!task || task.status !== "running")
|
|
2309
|
+
return false;
|
|
2310
|
+
task.status = "failed";
|
|
2311
|
+
task.error = error;
|
|
2312
|
+
task.completedAt = new Date;
|
|
2313
|
+
return true;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/managers/skill-mcp-manager.ts
|
|
2318
|
+
class SkillMcpManager {
|
|
2319
|
+
async getOrCreateClient(_info, _config) {
|
|
2320
|
+
throw new Error("SkillMcpManager.getOrCreateClient not implemented in v0.1");
|
|
2321
|
+
}
|
|
2322
|
+
async callTool(_info, _config, _name, _args) {
|
|
2323
|
+
throw new Error("SkillMcpManager.callTool not implemented in v0.1");
|
|
2324
|
+
}
|
|
2325
|
+
async disconnectSession(_sessionId) {}
|
|
2326
|
+
async disconnectAll() {}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// src/create-managers.ts
|
|
2330
|
+
async function createManagers(options) {
|
|
2331
|
+
const { ctx, pluginConfig, resolveSkills } = options;
|
|
2332
|
+
const disabledAgents = new Set(pluginConfig.disabled_agents ?? []);
|
|
2333
|
+
const disabledSkills = new Set(pluginConfig.disabled_skills ?? []);
|
|
2334
|
+
const builtinAgents = createBuiltinAgents({
|
|
2335
|
+
disabledAgents,
|
|
2336
|
+
configOverrides: pluginConfig.agents ?? {},
|
|
2337
|
+
buildOptions: {
|
|
2338
|
+
resolveSkills,
|
|
2339
|
+
disabledSkills
|
|
2340
|
+
}
|
|
2341
|
+
});
|
|
2342
|
+
const customAgents = {};
|
|
2343
|
+
if (pluginConfig.custom_agents) {
|
|
2344
|
+
for (const [name, customConfig] of Object.entries(pluginConfig.custom_agents)) {
|
|
2345
|
+
if (disabledAgents.has(name))
|
|
2346
|
+
continue;
|
|
2347
|
+
const agentConfig = await buildCustomAgent(name, customConfig, {
|
|
2348
|
+
directory: ctx.directory,
|
|
2349
|
+
resolveSkills,
|
|
2350
|
+
disabledSkills
|
|
2351
|
+
});
|
|
2352
|
+
customAgents[name] = agentConfig;
|
|
2353
|
+
const metadata = buildCustomAgentMetadata(name, customConfig);
|
|
2354
|
+
registerCustomAgentMetadata(name, metadata);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const allAgents = { ...builtinAgents, ...customAgents };
|
|
2358
|
+
for (const [name, config] of Object.entries(allAgents)) {
|
|
2359
|
+
const displayName = getAgentDisplayName(name);
|
|
2360
|
+
if (displayName !== name && !config.description) {
|
|
2361
|
+
config.description = displayName;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
const configHandler = new ConfigHandler({
|
|
2365
|
+
builtinAgents,
|
|
2366
|
+
customAgents
|
|
2367
|
+
});
|
|
2368
|
+
const backgroundManager = new BackgroundManager(pluginConfig.background?.max_concurrent ?? 3);
|
|
2369
|
+
const skillMcpManager = new SkillMcpManager;
|
|
2370
|
+
return {
|
|
2371
|
+
configHandler,
|
|
2372
|
+
backgroundManager,
|
|
2373
|
+
skillMcpManager,
|
|
2374
|
+
agents: allAgents
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// src/hooks/context-window-monitor.ts
|
|
2379
|
+
var DEFAULT_WARNING = 0.75;
|
|
2380
|
+
var DEFAULT_CRITICAL = 0.9;
|
|
2381
|
+
function checkContextWindow(usage, thresholds = {}) {
|
|
2382
|
+
const warningPercent = thresholds.warningPercent ?? DEFAULT_WARNING;
|
|
2383
|
+
const criticalPercent = thresholds.criticalPercent ?? DEFAULT_CRITICAL;
|
|
2384
|
+
if (usage.contextWindow <= 0) {
|
|
2385
|
+
return { severity: "ok", usagePercent: 0 };
|
|
2386
|
+
}
|
|
2387
|
+
const totalUsed = usage.inputTokens + usage.outputTokens;
|
|
2388
|
+
const usagePercent = totalUsed / usage.contextWindow;
|
|
2389
|
+
if (usagePercent >= criticalPercent) {
|
|
2390
|
+
return {
|
|
2391
|
+
severity: "critical",
|
|
2392
|
+
usagePercent,
|
|
2393
|
+
message: `⚠️ Critical: Context window ${Math.round(usagePercent * 100)}% full (${totalUsed.toLocaleString()}/${usage.contextWindow.toLocaleString()} tokens). Compact context soon to avoid truncation.`
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
if (usagePercent >= warningPercent) {
|
|
2397
|
+
return {
|
|
2398
|
+
severity: "warning",
|
|
2399
|
+
usagePercent,
|
|
2400
|
+
message: `ℹ️ Context window ${Math.round(usagePercent * 100)}% full (${totalUsed.toLocaleString()}/${usage.contextWindow.toLocaleString()} tokens). Consider compacting context.`
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
return { severity: "ok", usagePercent };
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/hooks/write-existing-file-guard.ts
|
|
2407
|
+
var WRITE_TOOLS = new Set(["write", "Write", "edit", "Edit"]);
|
|
2408
|
+
var READ_TOOLS = new Set(["read", "Read", "cat"]);
|
|
2409
|
+
function createWriteGuard() {
|
|
2410
|
+
const state = {
|
|
2411
|
+
readFiles: new Set,
|
|
2412
|
+
writtenFiles: new Set
|
|
2413
|
+
};
|
|
2414
|
+
function markRead(filePath) {
|
|
2415
|
+
state.readFiles.add(filePath);
|
|
2416
|
+
}
|
|
2417
|
+
function markWritten(filePath) {
|
|
2418
|
+
state.writtenFiles.add(filePath);
|
|
2419
|
+
}
|
|
2420
|
+
async function checkWrite(filePath) {
|
|
2421
|
+
if (state.writtenFiles.has(filePath)) {
|
|
2422
|
+
return { allowed: true };
|
|
2423
|
+
}
|
|
2424
|
+
if (state.readFiles.has(filePath)) {
|
|
2425
|
+
return { allowed: true };
|
|
2426
|
+
}
|
|
2427
|
+
try {
|
|
2428
|
+
const exists = await Bun.file(filePath).exists();
|
|
2429
|
+
if (exists) {
|
|
2430
|
+
return {
|
|
2431
|
+
allowed: false,
|
|
2432
|
+
reason: `File ${filePath} already exists. Read it first with the Read tool before overwriting.`
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
} catch {}
|
|
2436
|
+
return { allowed: true };
|
|
2437
|
+
}
|
|
2438
|
+
function processToolCall(toolName, args) {
|
|
2439
|
+
const path = args["path"] || args["filePath"];
|
|
2440
|
+
if (!path)
|
|
2441
|
+
return;
|
|
2442
|
+
if (READ_TOOLS.has(toolName)) {
|
|
2443
|
+
markRead(path);
|
|
2444
|
+
} else if (WRITE_TOOLS.has(toolName)) {
|
|
2445
|
+
markWritten(path);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
return {
|
|
2449
|
+
markRead,
|
|
2450
|
+
markWritten,
|
|
2451
|
+
checkWrite,
|
|
2452
|
+
processToolCall,
|
|
2453
|
+
getState: () => ({ ...state })
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/hooks/first-message-variant.ts
|
|
2458
|
+
var appliedSessions = new Set;
|
|
2459
|
+
var createdSessions = new Set;
|
|
2460
|
+
function shouldApplyVariant(sessionId, _agentName) {
|
|
2461
|
+
return createdSessions.has(sessionId) && !appliedSessions.has(sessionId);
|
|
2462
|
+
}
|
|
2463
|
+
function markApplied(sessionId) {
|
|
2464
|
+
appliedSessions.add(sessionId);
|
|
2465
|
+
}
|
|
2466
|
+
function markSessionCreated(sessionId) {
|
|
2467
|
+
createdSessions.add(sessionId);
|
|
2468
|
+
}
|
|
2469
|
+
function clearSession(sessionId) {
|
|
2470
|
+
appliedSessions.delete(sessionId);
|
|
2471
|
+
createdSessions.delete(sessionId);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/hooks/keyword-detector.ts
|
|
2475
|
+
var KEYWORD_PATTERNS = [
|
|
2476
|
+
{
|
|
2477
|
+
keyword: "run-workflow",
|
|
2478
|
+
patterns: [/^\/run-workflow\b/i, /^run-workflow\b/i]
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
keyword: "implement",
|
|
2482
|
+
patterns: [/^\/implement\b/i, /^implement\b/i]
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
keyword: "workflow-pause",
|
|
2486
|
+
patterns: [/\bworkflow\s+pause\b/i, /\bpause\s+workflow\b/i]
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
keyword: "workflow-skip",
|
|
2490
|
+
patterns: [/\bworkflow\s+skip\b/i, /\bskip\s+step\b/i]
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
keyword: "workflow-abort",
|
|
2494
|
+
patterns: [/\bworkflow\s+abort\b/i, /\babort\s+workflow\b/i]
|
|
2495
|
+
},
|
|
2496
|
+
{
|
|
2497
|
+
keyword: "workflow-status",
|
|
2498
|
+
patterns: [/\bworkflow\s+status\b/i]
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
keyword: "workflow-resume",
|
|
2502
|
+
patterns: [/\bworkflow\s+resume\b/i, /\bresume\s+workflow\b/i]
|
|
2503
|
+
}
|
|
2504
|
+
];
|
|
2505
|
+
function processMessageForKeywords(message) {
|
|
2506
|
+
const detected = [];
|
|
2507
|
+
let primary;
|
|
2508
|
+
let args;
|
|
2509
|
+
for (const { keyword, patterns } of KEYWORD_PATTERNS) {
|
|
2510
|
+
for (const pattern of patterns) {
|
|
2511
|
+
if (pattern.test(message)) {
|
|
2512
|
+
detected.push(keyword);
|
|
2513
|
+
if (!primary) {
|
|
2514
|
+
primary = keyword;
|
|
2515
|
+
const match = message.match(pattern);
|
|
2516
|
+
if (match) {
|
|
2517
|
+
const afterKeyword = message.slice(match.index + match[0].length).trim();
|
|
2518
|
+
if (afterKeyword) {
|
|
2519
|
+
args = afterKeyword;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return { keywords: detected, primary, args };
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/domain/policy/policy-result.ts
|
|
2531
|
+
function allow(reason) {
|
|
2532
|
+
return { verdict: "allow", reason };
|
|
2533
|
+
}
|
|
2534
|
+
function deny(reason, metadata) {
|
|
2535
|
+
return { verdict: "deny", reason, metadata };
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/hooks/architect-md-only.ts
|
|
2539
|
+
var ARCHITECT_AGENT = "architect";
|
|
2540
|
+
var ALLOWED_DIR = ".lead/";
|
|
2541
|
+
var WRITE_TOOLS2 = new Set(["write", "Write", "edit", "Edit"]);
|
|
2542
|
+
function checkArchitectWrite(input) {
|
|
2543
|
+
const { toolName, args, agentName } = input;
|
|
2544
|
+
if (agentName !== ARCHITECT_AGENT) {
|
|
2545
|
+
return allow();
|
|
2546
|
+
}
|
|
2547
|
+
if (!WRITE_TOOLS2.has(toolName)) {
|
|
2548
|
+
return allow();
|
|
2549
|
+
}
|
|
2550
|
+
const filePath = args["path"] || args["filePath"] || "";
|
|
2551
|
+
if (!filePath) {
|
|
2552
|
+
return allow();
|
|
2553
|
+
}
|
|
2554
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
2555
|
+
if (!normalizedPath.endsWith(".md")) {
|
|
2556
|
+
return deny(`Architect agent may only write Markdown (.md) files in the ${ALLOWED_DIR} directory. Attempted: ${filePath}`, { toolName, filePath });
|
|
2557
|
+
}
|
|
2558
|
+
if (!normalizedPath.includes(ALLOWED_DIR) && !normalizedPath.includes("/" + ALLOWED_DIR.replace(/\/$/, ""))) {
|
|
2559
|
+
return deny(`Architect agent may only write files in the ${ALLOWED_DIR} directory. Attempted: ${filePath}`, { toolName, filePath });
|
|
2560
|
+
}
|
|
2561
|
+
return allow();
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/hooks/start-implementation-hook.ts
|
|
2565
|
+
import { join as join4 } from "path";
|
|
2566
|
+
|
|
2567
|
+
// src/features/work-state/storage.ts
|
|
2568
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync, mkdirSync } from "fs";
|
|
2569
|
+
import { join as join3, dirname } from "path";
|
|
2570
|
+
|
|
2571
|
+
// src/features/work-state/constants.ts
|
|
2572
|
+
var STATE_DIR = ".lead";
|
|
2573
|
+
var STATE_FILE = ".lead/state.json";
|
|
2574
|
+
var ADHOC_DIR = ".lead/_adhoc";
|
|
2575
|
+
|
|
2576
|
+
// src/features/work-state/storage.ts
|
|
2577
|
+
function readWorkState(dir) {
|
|
2578
|
+
const filePath = join3(dir, STATE_FILE);
|
|
2579
|
+
if (!existsSync3(filePath))
|
|
2580
|
+
return null;
|
|
2581
|
+
try {
|
|
2582
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2583
|
+
return JSON.parse(raw);
|
|
2584
|
+
} catch {
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
function writeWorkState(dir, state) {
|
|
2589
|
+
const filePath = join3(dir, STATE_FILE);
|
|
2590
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
2591
|
+
writeFileSync(filePath, JSON.stringify(state, null, 2));
|
|
2592
|
+
}
|
|
2593
|
+
function getPlanProgress(planPath) {
|
|
2594
|
+
if (!existsSync3(planPath)) {
|
|
2595
|
+
return { total: 0, completed: 0, isComplete: false };
|
|
2596
|
+
}
|
|
2597
|
+
try {
|
|
2598
|
+
const content = readFileSync4(planPath, "utf-8");
|
|
2599
|
+
const unchecked = (content.match(/- \[ \]/g) ?? []).length;
|
|
2600
|
+
const checked = (content.match(/- \[x\]/gi) ?? []).length;
|
|
2601
|
+
const total = unchecked + checked;
|
|
2602
|
+
return {
|
|
2603
|
+
total,
|
|
2604
|
+
completed: checked,
|
|
2605
|
+
isComplete: total === 0 || checked === total
|
|
2606
|
+
};
|
|
2607
|
+
} catch {
|
|
2608
|
+
return { total: 0, completed: 0, isComplete: false };
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// src/hooks/start-implementation-hook.ts
|
|
2613
|
+
async function findPlanFile(directory, planNameOrPath) {
|
|
2614
|
+
if (planNameOrPath.startsWith("/")) {
|
|
2615
|
+
try {
|
|
2616
|
+
if (await Bun.file(planNameOrPath).exists())
|
|
2617
|
+
return planNameOrPath;
|
|
2618
|
+
} catch {}
|
|
2619
|
+
return null;
|
|
2620
|
+
}
|
|
2621
|
+
const directPath = join4(directory, planNameOrPath);
|
|
2622
|
+
try {
|
|
2623
|
+
if (await Bun.file(directPath).exists())
|
|
2624
|
+
return directPath;
|
|
2625
|
+
} catch {}
|
|
2626
|
+
const ticketPlanPath = join4(directory, STATE_DIR, planNameOrPath, "plan.md");
|
|
2627
|
+
try {
|
|
2628
|
+
if (await Bun.file(ticketPlanPath).exists())
|
|
2629
|
+
return ticketPlanPath;
|
|
2630
|
+
} catch {}
|
|
2631
|
+
const adhocName = planNameOrPath.endsWith(".md") ? planNameOrPath : `${planNameOrPath}.md`;
|
|
2632
|
+
const adhocPath = join4(directory, ADHOC_DIR, adhocName);
|
|
2633
|
+
try {
|
|
2634
|
+
if (await Bun.file(adhocPath).exists())
|
|
2635
|
+
return adhocPath;
|
|
2636
|
+
} catch {}
|
|
2637
|
+
try {
|
|
2638
|
+
const glob = new Bun.Glob("**/*.md");
|
|
2639
|
+
const leadDir = join4(directory, STATE_DIR);
|
|
2640
|
+
const needle = planNameOrPath.toLowerCase();
|
|
2641
|
+
for await (const entry of glob.scan(leadDir)) {
|
|
2642
|
+
if (entry === "state.json")
|
|
2643
|
+
continue;
|
|
2644
|
+
const basename = entry.replace(/\.md$/, "").toLowerCase();
|
|
2645
|
+
const parts = basename.split("/");
|
|
2646
|
+
const leaf = parts[parts.length - 1];
|
|
2647
|
+
if (leaf === needle || leaf.includes(needle) || parts.some((p) => p === needle || p.includes(needle))) {
|
|
2648
|
+
return join4(leadDir, entry);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
} catch {}
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
async function handleStartImplementation(options) {
|
|
2655
|
+
const { args, sessionId, directory, startSha } = options;
|
|
2656
|
+
const trimmedArgs = args.trim();
|
|
2657
|
+
if (!trimmedArgs) {
|
|
2658
|
+
const existingState = readWorkState(directory);
|
|
2659
|
+
if (existingState && !existingState.paused) {
|
|
2660
|
+
const progress2 = getPlanProgress(existingState.active_plan);
|
|
2661
|
+
return {
|
|
2662
|
+
prompt: buildContinuationPrompt(existingState, progress2)
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
return {
|
|
2666
|
+
prompt: null,
|
|
2667
|
+
error: `No plan specified. Usage: /implement <plan-name-or-ticket>
|
|
2668
|
+
|
|
2669
|
+
Plans are in .lead/<ticket>/plan.md or .lead/_adhoc/`
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
const planPath = await findPlanFile(directory, trimmedArgs);
|
|
2673
|
+
if (!planPath) {
|
|
2674
|
+
return {
|
|
2675
|
+
prompt: null,
|
|
2676
|
+
error: `Plan not found: "${trimmedArgs}"
|
|
2677
|
+
|
|
2678
|
+
Looked in:
|
|
2679
|
+
- .lead/${trimmedArgs}/plan.md
|
|
2680
|
+
- .lead/_adhoc/${trimmedArgs}.md
|
|
2681
|
+
|
|
2682
|
+
Create a plan first with the architect agent.`
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
let planContent = "";
|
|
2686
|
+
try {
|
|
2687
|
+
planContent = await Bun.file(planPath).text();
|
|
2688
|
+
} catch {
|
|
2689
|
+
return { prompt: null, error: `Could not read plan file: ${planPath}` };
|
|
2690
|
+
}
|
|
2691
|
+
const titleMatch = planContent.match(/^#\s+(.+)$/m);
|
|
2692
|
+
const planName = titleMatch?.[1]?.trim() ?? trimmedArgs;
|
|
2693
|
+
const workState = {
|
|
2694
|
+
active_plan: planPath,
|
|
2695
|
+
started_at: new Date().toISOString(),
|
|
2696
|
+
session_ids: [sessionId],
|
|
2697
|
+
plan_name: planName,
|
|
2698
|
+
start_sha: startSha,
|
|
2699
|
+
paused: false,
|
|
2700
|
+
continuation_completed_snapshot: 0,
|
|
2701
|
+
stale_continuation_count: 0
|
|
2702
|
+
};
|
|
2703
|
+
writeWorkState(directory, workState);
|
|
2704
|
+
const progress = getPlanProgress(planPath);
|
|
2705
|
+
const prompt = buildStartPrompt(workState, progress, planContent);
|
|
2706
|
+
return { prompt };
|
|
2707
|
+
}
|
|
2708
|
+
function buildStartPrompt(state, progress, _planContent) {
|
|
2709
|
+
const remaining = progress.total - progress.completed;
|
|
2710
|
+
return `You are now the **Executor** — your job is to execute the plan at \`${state.active_plan}\`.
|
|
2711
|
+
|
|
2712
|
+
**Plan**: ${state.plan_name}
|
|
2713
|
+
**Progress**: ${progress.completed}/${progress.total} tasks complete (${remaining} remaining)
|
|
2714
|
+
|
|
2715
|
+
Start by reading the plan file to understand the current state. Find the first unchecked \`- [ ]\` task and begin executing it. Work through tasks sequentially, marking each \`- [x]\` when complete.
|
|
2716
|
+
|
|
2717
|
+
**Execution protocol**:
|
|
2718
|
+
1. Read the plan file to find the first unchecked task
|
|
2719
|
+
2. Mark it in_progress in the sidebar todos
|
|
2720
|
+
3. Execute the task step by step
|
|
2721
|
+
4. Verify acceptance criteria are met
|
|
2722
|
+
5. Mark \`- [ ]\` → \`- [x]\` in the plan file
|
|
2723
|
+
6. Continue to the next task
|
|
2724
|
+
|
|
2725
|
+
Do not stop until all tasks are complete or you are explicitly blocked.`;
|
|
2726
|
+
}
|
|
2727
|
+
function buildContinuationPrompt(state, progress) {
|
|
2728
|
+
const remaining = progress.total - progress.completed;
|
|
2729
|
+
return `Resuming work on plan: **${state.plan_name}**
|
|
2730
|
+
|
|
2731
|
+
**Progress**: ${progress.completed}/${progress.total} tasks complete (${remaining} remaining)
|
|
2732
|
+
**Plan file**: \`${state.active_plan}\`
|
|
2733
|
+
|
|
2734
|
+
Read the plan file, find the next unchecked \`- [ ]\` task, and continue execution.`;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/hooks/work-continuation.ts
|
|
2738
|
+
var MAX_STALE_CONTINUATIONS = 3;
|
|
2739
|
+
function checkContinuation(options) {
|
|
2740
|
+
const { sessionId, directory, lastAssistantMessage } = options;
|
|
2741
|
+
const state = readWorkState(directory);
|
|
2742
|
+
if (!state || state.paused) {
|
|
2743
|
+
return { continuationPrompt: null };
|
|
2744
|
+
}
|
|
2745
|
+
const progress = getPlanProgress(state.active_plan);
|
|
2746
|
+
if (progress.isComplete) {
|
|
2747
|
+
return {
|
|
2748
|
+
continuationPrompt: `✅ Plan **${state.plan_name}** is complete! All ${progress.total} tasks done.`,
|
|
2749
|
+
planComplete: true
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
const isStaleContinuation = lastAssistantMessage?.includes(state.active_plan) && lastAssistantMessage?.includes("- [ ]");
|
|
2753
|
+
const completedSnapshot = state.continuation_completed_snapshot ?? 0;
|
|
2754
|
+
const isNoProgress = progress.completed === completedSnapshot;
|
|
2755
|
+
if (isStaleContinuation && isNoProgress) {
|
|
2756
|
+
const staleCount = (state.stale_continuation_count ?? 0) + 1;
|
|
2757
|
+
if (staleCount >= MAX_STALE_CONTINUATIONS) {
|
|
2758
|
+
writeWorkState(directory, { ...state, paused: true });
|
|
2759
|
+
return {
|
|
2760
|
+
continuationPrompt: `⚠️ Work on plan **${state.plan_name}** has been paused after ${staleCount} continuations with no progress. Resume manually with \`/implement ${state.plan_name}\`.`
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
writeWorkState(directory, {
|
|
2764
|
+
...state,
|
|
2765
|
+
stale_continuation_count: staleCount,
|
|
2766
|
+
session_ids: state.session_ids.includes(sessionId) ? state.session_ids : [...state.session_ids, sessionId]
|
|
2767
|
+
});
|
|
2768
|
+
} else {
|
|
2769
|
+
writeWorkState(directory, {
|
|
2770
|
+
...state,
|
|
2771
|
+
continuation_completed_snapshot: progress.completed,
|
|
2772
|
+
stale_continuation_count: 0,
|
|
2773
|
+
session_ids: state.session_ids.includes(sessionId) ? state.session_ids : [...state.session_ids, sessionId]
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
const remaining = progress.total - progress.completed;
|
|
2777
|
+
return {
|
|
2778
|
+
continuationPrompt: `Continue working on plan: **${state.plan_name}**
|
|
2779
|
+
|
|
2780
|
+
**Progress**: ${progress.completed}/${progress.total} tasks complete (${remaining} remaining)
|
|
2781
|
+
**Plan file**: \`${state.active_plan}\`
|
|
2782
|
+
|
|
2783
|
+
Read the plan file, find the next unchecked \`- [ ]\` task, and continue execution. Do not stop until all tasks are complete.`
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/features/workflow/storage.ts
|
|
2788
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
2789
|
+
import { join as join5 } from "path";
|
|
2790
|
+
|
|
2791
|
+
// src/features/workflow/constants.ts
|
|
2792
|
+
var WORKFLOW_STATE_DIR = ".lead/workflows";
|
|
2793
|
+
var ACTIVE_INSTANCE_FILE = ".lead/workflows/active-instance.json";
|
|
2794
|
+
var WORKFLOW_CONTINUATION_MARKER = "<!-- lead:workflow-continuation -->";
|
|
2795
|
+
var WORKFLOW_STEP_COMPLETE_SIGNAL = "<!-- workflow:step-complete -->";
|
|
2796
|
+
|
|
2797
|
+
// src/features/workflow/storage.ts
|
|
2798
|
+
function ensureWorkflowDir(dir) {
|
|
2799
|
+
const stateDir = join5(dir, WORKFLOW_STATE_DIR);
|
|
2800
|
+
mkdirSync2(stateDir, { recursive: true });
|
|
2801
|
+
}
|
|
2802
|
+
function saveWorkflowInstance(dir, instance) {
|
|
2803
|
+
ensureWorkflowDir(dir);
|
|
2804
|
+
const filePath = join5(dir, WORKFLOW_STATE_DIR, `${instance.instance_id}.json`);
|
|
2805
|
+
writeFileSync2(filePath, JSON.stringify(instance, null, 2));
|
|
2806
|
+
}
|
|
2807
|
+
function loadWorkflowInstance(dir, instanceId) {
|
|
2808
|
+
const filePath = join5(dir, WORKFLOW_STATE_DIR, `${instanceId}.json`);
|
|
2809
|
+
if (!existsSync4(filePath))
|
|
2810
|
+
return null;
|
|
2811
|
+
try {
|
|
2812
|
+
return JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
2813
|
+
} catch {
|
|
2814
|
+
return null;
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
function loadActiveInstance(dir) {
|
|
2818
|
+
const pointerPath = join5(dir, ACTIVE_INSTANCE_FILE);
|
|
2819
|
+
if (!existsSync4(pointerPath))
|
|
2820
|
+
return null;
|
|
2821
|
+
try {
|
|
2822
|
+
const pointer = JSON.parse(readFileSync5(pointerPath, "utf-8"));
|
|
2823
|
+
return loadWorkflowInstance(dir, pointer.instance_id);
|
|
2824
|
+
} catch {
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
function setActiveInstance(dir, instanceId) {
|
|
2829
|
+
ensureWorkflowDir(dir);
|
|
2830
|
+
const pointerPath = join5(dir, ACTIVE_INSTANCE_FILE);
|
|
2831
|
+
const pointer = { instance_id: instanceId };
|
|
2832
|
+
writeFileSync2(pointerPath, JSON.stringify(pointer, null, 2));
|
|
2833
|
+
}
|
|
2834
|
+
function clearActiveInstance(dir) {
|
|
2835
|
+
const pointerPath = join5(dir, ACTIVE_INSTANCE_FILE);
|
|
2836
|
+
if (existsSync4(pointerPath)) {
|
|
2837
|
+
try {
|
|
2838
|
+
const { unlinkSync } = __require("fs");
|
|
2839
|
+
unlinkSync(pointerPath);
|
|
2840
|
+
} catch {}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// src/hooks/compaction-recovery.ts
|
|
2845
|
+
function checkCompactionRecovery(options) {
|
|
2846
|
+
const { directory } = options;
|
|
2847
|
+
const parts = [];
|
|
2848
|
+
const workState = readWorkState(directory);
|
|
2849
|
+
if (workState && !workState.paused) {
|
|
2850
|
+
const progress = getPlanProgress(workState.active_plan);
|
|
2851
|
+
if (!progress.isComplete) {
|
|
2852
|
+
parts.push(`## Active Plan: ${workState.plan_name}
|
|
2853
|
+
|
|
2854
|
+
**File**: \`${workState.active_plan}\`
|
|
2855
|
+
**Progress**: ${progress.completed}/${progress.total} tasks complete
|
|
2856
|
+
|
|
2857
|
+
You were executing this plan. Read the plan file to find the next unchecked \`- [ ]\` task and continue execution.`);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
const workflowInstance = loadActiveInstance(directory);
|
|
2861
|
+
if (workflowInstance && workflowInstance.status === "running") {
|
|
2862
|
+
parts.push(`## Active Workflow: ${workflowInstance.definition_name}
|
|
2863
|
+
|
|
2864
|
+
**Instance**: ${workflowInstance.instance_id}
|
|
2865
|
+
**Goal**: ${workflowInstance.goal}
|
|
2866
|
+
**Current Step**: ${workflowInstance.current_step_id}
|
|
2867
|
+
|
|
2868
|
+
You were executing a workflow. Continue from the current step.`);
|
|
2869
|
+
}
|
|
2870
|
+
if (parts.length === 0) {
|
|
2871
|
+
return { recoveryPrompt: null };
|
|
2872
|
+
}
|
|
2873
|
+
const recoveryPrompt = `# Context Recovery After Compaction
|
|
2874
|
+
|
|
2875
|
+
Your context was compacted. Here is a summary of your active work:
|
|
2876
|
+
|
|
2877
|
+
${parts.join(`
|
|
2878
|
+
|
|
2879
|
+
---
|
|
2880
|
+
|
|
2881
|
+
`)}
|
|
2882
|
+
|
|
2883
|
+
Resume where you left off.`;
|
|
2884
|
+
return { recoveryPrompt };
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// src/hooks/verification-reminder.ts
|
|
2888
|
+
var IMPLEMENTATION_TOOLS = new Set([
|
|
2889
|
+
"write",
|
|
2890
|
+
"Write",
|
|
2891
|
+
"edit",
|
|
2892
|
+
"Edit",
|
|
2893
|
+
"bash",
|
|
2894
|
+
"Bash"
|
|
2895
|
+
]);
|
|
2896
|
+
var REMINDER_AGENTS = new Set(["lead-dev", "engineer", "tester"]);
|
|
2897
|
+
function buildVerificationReminder(options) {
|
|
2898
|
+
const { toolName, agentName } = options;
|
|
2899
|
+
if (!IMPLEMENTATION_TOOLS.has(toolName)) {
|
|
2900
|
+
return { reminderText: null };
|
|
2901
|
+
}
|
|
2902
|
+
if (agentName && !REMINDER_AGENTS.has(agentName)) {
|
|
2903
|
+
return { reminderText: null };
|
|
2904
|
+
}
|
|
2905
|
+
return {
|
|
2906
|
+
reminderText: `After making changes, verify your work:
|
|
2907
|
+
1. Run \`bun run typecheck\` to check for TypeScript errors
|
|
2908
|
+
2. Run \`bun test\` to ensure tests pass
|
|
2909
|
+
3. Review changed files for correctness before proceeding`
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// src/hooks/todo-description-override.ts
|
|
2914
|
+
var TODO_WRITE_TOOL = "todowrite";
|
|
2915
|
+
var ENHANCED_DESCRIPTION = `Manage the sidebar todo list.
|
|
2916
|
+
|
|
2917
|
+
CRITICAL RULES:
|
|
2918
|
+
- This tool performs a FULL ARRAY REPLACEMENT on every call
|
|
2919
|
+
- ALWAYS include ALL current todos in EVERY call — never drop items
|
|
2920
|
+
- Max 35 chars per todo content
|
|
2921
|
+
- Mark tasks in_progress BEFORE starting, completed IMMEDIATELY after finishing
|
|
2922
|
+
- NEVER batch completions — mark one at a time
|
|
2923
|
+
- Status values: "pending", "in_progress", "completed", "cancelled"
|
|
2924
|
+
- Priority values: "high", "medium", "low"
|
|
2925
|
+
- Use format: "N/M: Task title" for numbered tasks
|
|
2926
|
+
- ALWAYS issue a final todowrite before your last response marking all in_progress → completed`;
|
|
2927
|
+
function applyTodoDescriptionOverride(toolName, originalDescription) {
|
|
2928
|
+
if (toolName.toLowerCase() === TODO_WRITE_TOOL.toLowerCase()) {
|
|
2929
|
+
return ENHANCED_DESCRIPTION;
|
|
2930
|
+
}
|
|
2931
|
+
return originalDescription;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
// src/hooks/todo-continuation-enforcer.ts
|
|
2935
|
+
function checkStaleTodos(todos) {
|
|
2936
|
+
const staleTodos = todos.filter((t) => t.status === "in_progress");
|
|
2937
|
+
if (staleTodos.length === 0) {
|
|
2938
|
+
return { hasStale: false, prompt: null, staleTodos: [] };
|
|
2939
|
+
}
|
|
2940
|
+
const staleList = staleTodos.map((t) => `- "${t.content}"`).join(`
|
|
2941
|
+
`);
|
|
2942
|
+
return {
|
|
2943
|
+
hasStale: true,
|
|
2944
|
+
staleTodos,
|
|
2945
|
+
prompt: `You have ${staleTodos.length} in_progress todo(s) that appear stale:
|
|
2946
|
+
|
|
2947
|
+
${staleList}
|
|
2948
|
+
|
|
2949
|
+
Please either:
|
|
2950
|
+
1. Complete the tasks and mark them "completed"
|
|
2951
|
+
2. Update the todos with current status using the todowrite tool
|
|
2952
|
+
3. Mark them "cancelled" if they are no longer needed
|
|
2953
|
+
|
|
2954
|
+
Never leave in_progress items unattended — they are tracked as work in progress.`
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
// src/hooks/compaction-todo-preserver.ts
|
|
2959
|
+
var todoStateStore = new Map;
|
|
2960
|
+
function saveTodoStateForCompaction(sessionId, todos) {
|
|
2961
|
+
todoStateStore.set(sessionId, [...todos]);
|
|
2962
|
+
}
|
|
2963
|
+
function getPreservedTodoState(sessionId) {
|
|
2964
|
+
return todoStateStore.get(sessionId) ?? null;
|
|
2965
|
+
}
|
|
2966
|
+
function buildTodoPreservationPrompt(sessionId) {
|
|
2967
|
+
const todos = getPreservedTodoState(sessionId);
|
|
2968
|
+
if (!todos || todos.length === 0)
|
|
2969
|
+
return null;
|
|
2970
|
+
const inProgress = todos.filter((t) => t.status === "in_progress");
|
|
2971
|
+
const pending = todos.filter((t) => t.status === "pending");
|
|
2972
|
+
if (inProgress.length === 0 && pending.length === 0)
|
|
2973
|
+
return null;
|
|
2974
|
+
const parts = ["## Preserved Todo State (Before Compaction)"];
|
|
2975
|
+
if (inProgress.length > 0) {
|
|
2976
|
+
parts.push(`**In Progress** (${inProgress.length}):
|
|
2977
|
+
${inProgress.map((t) => `- ${t.content}`).join(`
|
|
2978
|
+
`)}`);
|
|
2979
|
+
}
|
|
2980
|
+
if (pending.length > 0) {
|
|
2981
|
+
parts.push(`**Pending** (${pending.length}):
|
|
2982
|
+
${pending.map((t) => `- ${t.content}`).join(`
|
|
2983
|
+
`)}`);
|
|
2984
|
+
}
|
|
2985
|
+
parts.push("Restore this todo state using the todowrite tool before continuing.");
|
|
2986
|
+
return parts.join(`
|
|
2987
|
+
|
|
2988
|
+
`);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/hooks/todo-writer.ts
|
|
2992
|
+
var TODO_WRITE_TOOL2 = "todowrite";
|
|
2993
|
+
function captureToDoWrite(input) {
|
|
2994
|
+
const { toolName, args, sessionId } = input;
|
|
2995
|
+
if (toolName.toLowerCase() !== TODO_WRITE_TOOL2.toLowerCase()) {
|
|
2996
|
+
return { captured: false };
|
|
2997
|
+
}
|
|
2998
|
+
const rawTodos = args["todos"];
|
|
2999
|
+
if (!Array.isArray(rawTodos)) {
|
|
3000
|
+
return { captured: false };
|
|
3001
|
+
}
|
|
3002
|
+
const todos = rawTodos.filter((t) => typeof t === "object" && t !== null).map((t) => ({
|
|
3003
|
+
content: String(t["content"] ?? ""),
|
|
3004
|
+
status: t["status"] ?? "pending",
|
|
3005
|
+
priority: t["priority"] ?? "medium"
|
|
3006
|
+
}));
|
|
3007
|
+
saveTodoStateForCompaction(sessionId, todos);
|
|
3008
|
+
return { captured: true, todos };
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// src/hooks/session-token-state.ts
|
|
3012
|
+
var tokenStateStore = new Map;
|
|
3013
|
+
function updateTokenState(sessionId, delta) {
|
|
3014
|
+
const existing = tokenStateStore.get(sessionId) ?? {
|
|
3015
|
+
sessionId,
|
|
3016
|
+
inputTokens: 0,
|
|
3017
|
+
outputTokens: 0,
|
|
3018
|
+
contextWindow: 0,
|
|
3019
|
+
lastUpdatedAt: new Date
|
|
3020
|
+
};
|
|
3021
|
+
const updated = {
|
|
3022
|
+
...existing,
|
|
3023
|
+
inputTokens: existing.inputTokens + (delta.inputTokens ?? 0),
|
|
3024
|
+
outputTokens: existing.outputTokens + (delta.outputTokens ?? 0),
|
|
3025
|
+
contextWindow: delta.contextWindow ?? existing.contextWindow,
|
|
3026
|
+
lastUpdatedAt: new Date
|
|
3027
|
+
};
|
|
3028
|
+
tokenStateStore.set(sessionId, updated);
|
|
3029
|
+
return updated;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
// src/hooks/create-hooks.ts
|
|
3033
|
+
function isHookEnabled(hookName, disabledHooks) {
|
|
3034
|
+
return !disabledHooks.includes(hookName);
|
|
3035
|
+
}
|
|
3036
|
+
function createHooks(args) {
|
|
3037
|
+
const disabled = args.pluginConfig.disabled_hooks ?? [];
|
|
3038
|
+
const _ = isHookEnabled;
|
|
3039
|
+
return {
|
|
3040
|
+
checkContextWindow: isHookEnabled("context-window-monitor", disabled) ? checkContextWindow : () => ({ severity: "ok", usagePercent: 0 }),
|
|
3041
|
+
createWriteGuard: isHookEnabled("write-guard", disabled) ? createWriteGuard : () => ({
|
|
3042
|
+
markRead: () => {},
|
|
3043
|
+
markWritten: () => {},
|
|
3044
|
+
checkWrite: async () => ({ allowed: true }),
|
|
3045
|
+
processToolCall: () => {},
|
|
3046
|
+
getState: () => ({ readFiles: new Set, writtenFiles: new Set })
|
|
3047
|
+
}),
|
|
3048
|
+
shouldApplyVariant: isHookEnabled("first-message-variant", disabled) ? shouldApplyVariant : () => false,
|
|
3049
|
+
markApplied,
|
|
3050
|
+
markSessionCreated,
|
|
3051
|
+
clearSession,
|
|
3052
|
+
processMessageForKeywords: isHookEnabled("keyword-detector", disabled) ? processMessageForKeywords : () => ({ keywords: [] }),
|
|
3053
|
+
checkArchitectWrite: isHookEnabled("architect-md-only", disabled) ? checkArchitectWrite : () => ({ verdict: "allow" }),
|
|
3054
|
+
handleStartImplementation: isHookEnabled("implement", disabled) ? handleStartImplementation : async () => ({ prompt: null }),
|
|
3055
|
+
checkContinuation: isHookEnabled("work-continuation", disabled) ? checkContinuation : () => ({ continuationPrompt: null }),
|
|
3056
|
+
checkCompactionRecovery: isHookEnabled("compaction-recovery", disabled) ? checkCompactionRecovery : () => ({ recoveryPrompt: null }),
|
|
3057
|
+
buildVerificationReminder: isHookEnabled("verification-reminder", disabled) ? buildVerificationReminder : () => ({ reminderText: null }),
|
|
3058
|
+
applyTodoDescriptionOverride: isHookEnabled("todo-description-override", disabled) ? applyTodoDescriptionOverride : (_tool, desc) => desc,
|
|
3059
|
+
checkStaleTodos: isHookEnabled("todo-continuation", disabled) ? checkStaleTodos : () => ({ hasStale: false, prompt: null, staleTodos: [] }),
|
|
3060
|
+
buildTodoPreservationPrompt: isHookEnabled("compaction-todo-preserver", disabled) ? buildTodoPreservationPrompt : () => null,
|
|
3061
|
+
captureToDoWrite: isHookEnabled("todo-writer", disabled) ? captureToDoWrite : () => ({ captured: false }),
|
|
3062
|
+
updateTokenState: isHookEnabled("session-token-state", disabled) ? updateTokenState : (_sessionId, _delta) => ({
|
|
3063
|
+
sessionId: _sessionId,
|
|
3064
|
+
inputTokens: 0,
|
|
3065
|
+
outputTokens: 0,
|
|
3066
|
+
contextWindow: 0,
|
|
3067
|
+
lastUpdatedAt: new Date
|
|
3068
|
+
})
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// src/features/workflow/discovery.ts
|
|
3073
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
3074
|
+
import { join as join6 } from "path";
|
|
3075
|
+
import { homedir as homedir3 } from "os";
|
|
3076
|
+
|
|
3077
|
+
// src/features/workflow/schema.ts
|
|
3078
|
+
import { z as z2 } from "zod";
|
|
3079
|
+
var StepTypeSchema = z2.enum(["interactive", "autonomous", "gate"]);
|
|
3080
|
+
var CompletionMethodSchema = z2.enum([
|
|
3081
|
+
"user_confirm",
|
|
3082
|
+
"plan_created",
|
|
3083
|
+
"plan_complete",
|
|
3084
|
+
"review_verdict",
|
|
3085
|
+
"agent_signal"
|
|
3086
|
+
]);
|
|
3087
|
+
var CompletionConfigSchema = z2.object({
|
|
3088
|
+
method: CompletionMethodSchema,
|
|
3089
|
+
plan_name: z2.string().optional(),
|
|
3090
|
+
keywords: z2.array(z2.string()).optional()
|
|
3091
|
+
});
|
|
3092
|
+
var StepArtifactRefSchema = z2.object({
|
|
3093
|
+
name: z2.string().min(1),
|
|
3094
|
+
description: z2.string().optional()
|
|
3095
|
+
});
|
|
3096
|
+
var StepArtifactsSchema = z2.object({
|
|
3097
|
+
inputs: z2.array(StepArtifactRefSchema).optional(),
|
|
3098
|
+
outputs: z2.array(StepArtifactRefSchema).optional()
|
|
3099
|
+
});
|
|
3100
|
+
var WorkflowStepDefinitionSchema = z2.object({
|
|
3101
|
+
id: z2.string().regex(/^[a-z0-9-]+$/, "Step ID must be lowercase alphanumeric with hyphens"),
|
|
3102
|
+
name: z2.string().min(1),
|
|
3103
|
+
type: StepTypeSchema,
|
|
3104
|
+
agent: z2.string().min(1),
|
|
3105
|
+
prompt: z2.string().min(1),
|
|
3106
|
+
completion: CompletionConfigSchema,
|
|
3107
|
+
artifacts: StepArtifactsSchema.optional(),
|
|
3108
|
+
on_reject: z2.enum(["pause", "fail"]).optional()
|
|
3109
|
+
});
|
|
3110
|
+
var WorkflowDefinitionSchema = z2.object({
|
|
3111
|
+
name: z2.string().regex(/^[a-z0-9-]+$/, "Workflow name must be lowercase alphanumeric with hyphens"),
|
|
3112
|
+
description: z2.string().optional(),
|
|
3113
|
+
version: z2.literal(1),
|
|
3114
|
+
steps: z2.array(WorkflowStepDefinitionSchema).min(1, "Workflow must have at least one step")
|
|
3115
|
+
}).superRefine((data, ctx) => {
|
|
3116
|
+
const ids = data.steps.map((s) => s.id);
|
|
3117
|
+
const unique = new Set(ids);
|
|
3118
|
+
if (ids.length !== unique.size) {
|
|
3119
|
+
ctx.addIssue({
|
|
3120
|
+
code: z2.ZodIssueCode.custom,
|
|
3121
|
+
message: "Workflow step IDs must be unique"
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
|
|
3126
|
+
// src/features/workflow/discovery.ts
|
|
3127
|
+
function parseWorkflowFile(filePath, scope) {
|
|
3128
|
+
try {
|
|
3129
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
3130
|
+
const raw = parse2(content);
|
|
3131
|
+
const result = WorkflowDefinitionSchema.safeParse(raw);
|
|
3132
|
+
if (!result.success) {
|
|
3133
|
+
return null;
|
|
3134
|
+
}
|
|
3135
|
+
return {
|
|
3136
|
+
definition: result.data,
|
|
3137
|
+
path: filePath,
|
|
3138
|
+
scope
|
|
3139
|
+
};
|
|
3140
|
+
} catch {
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
function scanWorkflowDir(dir, scope) {
|
|
3145
|
+
if (!existsSync5(dir))
|
|
3146
|
+
return [];
|
|
3147
|
+
const results = [];
|
|
3148
|
+
try {
|
|
3149
|
+
const entries = readdirSync2(dir);
|
|
3150
|
+
for (const entry of entries) {
|
|
3151
|
+
if (entry.endsWith(".jsonc") || entry.endsWith(".json")) {
|
|
3152
|
+
const fullPath = join6(dir, entry);
|
|
3153
|
+
const workflow = parseWorkflowFile(fullPath, scope);
|
|
3154
|
+
if (workflow) {
|
|
3155
|
+
results.push(workflow);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
} catch {}
|
|
3160
|
+
return results;
|
|
3161
|
+
}
|
|
3162
|
+
function discoverWorkflows(options) {
|
|
3163
|
+
const { projectDirectory, customDirs = [] } = options;
|
|
3164
|
+
const byName = new Map;
|
|
3165
|
+
const userDir = join6(homedir3(), ".config", "opencode", "workflows");
|
|
3166
|
+
for (const wf of scanWorkflowDir(userDir, "user")) {
|
|
3167
|
+
byName.set(wf.definition.name, wf);
|
|
3168
|
+
}
|
|
3169
|
+
const projectDir = join6(projectDirectory, ".opencode", "workflows");
|
|
3170
|
+
for (const wf of scanWorkflowDir(projectDir, "project")) {
|
|
3171
|
+
byName.set(wf.definition.name, wf);
|
|
3172
|
+
}
|
|
3173
|
+
for (const customDir of customDirs) {
|
|
3174
|
+
const safePath = resolveSafePath(projectDirectory, customDir);
|
|
3175
|
+
if (!safePath)
|
|
3176
|
+
continue;
|
|
3177
|
+
for (const wf of scanWorkflowDir(safePath, "project")) {
|
|
3178
|
+
byName.set(wf.definition.name, wf);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
return Array.from(byName.values());
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/features/workflow/engine.ts
|
|
3185
|
+
import { randomBytes } from "crypto";
|
|
3186
|
+
|
|
3187
|
+
// src/features/workflow/context.ts
|
|
3188
|
+
function substituteTemplateVars(template, instance, stepDef) {
|
|
3189
|
+
return template.replace(/\{\{instance\.goal\}\}/g, instance.goal).replace(/\{\{instance\.slug\}\}/g, instance.slug).replace(/\{\{instance\.instance_id\}\}/g, instance.instance_id).replace(/\{\{step\.id\}\}/g, stepDef.id).replace(/\{\{step\.name\}\}/g, stepDef.name).replace(/\{\{artifacts\.([^}]+)\}\}/g, (_match, name) => {
|
|
3190
|
+
return instance.artifacts[name] ?? `[artifact '${name}' not available]`;
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
function buildStepContext(instance, stepDef, definition) {
|
|
3194
|
+
const renderedPrompt = substituteTemplateVars(stepDef.prompt, instance, stepDef);
|
|
3195
|
+
const artifactSummary = Object.keys(instance.artifacts).length > 0 ? Object.entries(instance.artifacts).map(([k, v]) => `**${k}**: ${v}`).join(`
|
|
3196
|
+
`) : "(none)";
|
|
3197
|
+
const stepIndex = definition.steps.findIndex((s) => s.id === stepDef.id);
|
|
3198
|
+
const totalSteps = definition.steps.length;
|
|
3199
|
+
return [
|
|
3200
|
+
renderedPrompt,
|
|
3201
|
+
"",
|
|
3202
|
+
`**Workflow**: "${definition.name}"`,
|
|
3203
|
+
`**Goal**: "${instance.goal}"`,
|
|
3204
|
+
`**Step**: ${stepDef.name} (${stepIndex + 1}/${totalSteps})`,
|
|
3205
|
+
`**Previous Artifacts**:`,
|
|
3206
|
+
artifactSummary
|
|
3207
|
+
].join(`
|
|
3208
|
+
`);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// src/features/workflow/completion.ts
|
|
3212
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
3213
|
+
import { join as join7 } from "path";
|
|
3214
|
+
var DEFAULT_USER_CONFIRM_KEYWORDS = [
|
|
3215
|
+
"confirmed",
|
|
3216
|
+
"approved",
|
|
3217
|
+
"continue",
|
|
3218
|
+
"done",
|
|
3219
|
+
"let's proceed",
|
|
3220
|
+
"looks good",
|
|
3221
|
+
"lgtm"
|
|
3222
|
+
];
|
|
3223
|
+
function containsKeyword(text, keywords) {
|
|
3224
|
+
const lower = text.toLowerCase();
|
|
3225
|
+
return keywords.some((kw) => lower.includes(kw.toLowerCase()));
|
|
3226
|
+
}
|
|
3227
|
+
function checkStepCompletion(method, ctx) {
|
|
3228
|
+
switch (method) {
|
|
3229
|
+
case "user_confirm": {
|
|
3230
|
+
const keywords = ctx.config.keywords?.length ? ctx.config.keywords : DEFAULT_USER_CONFIRM_KEYWORDS;
|
|
3231
|
+
const msg = ctx.lastUserMessage ?? "";
|
|
3232
|
+
if (containsKeyword(msg, keywords)) {
|
|
3233
|
+
return {
|
|
3234
|
+
complete: true,
|
|
3235
|
+
summary: `User confirmed: "${msg.slice(0, 80)}"`
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
return { complete: false };
|
|
3239
|
+
}
|
|
3240
|
+
case "plan_created": {
|
|
3241
|
+
const planName = ctx.config.plan_name;
|
|
3242
|
+
if (!planName)
|
|
3243
|
+
return { complete: false };
|
|
3244
|
+
const leadDir = join7(ctx.directory, ".lead");
|
|
3245
|
+
if (!existsSync6(leadDir))
|
|
3246
|
+
return { complete: false };
|
|
3247
|
+
let found = null;
|
|
3248
|
+
try {
|
|
3249
|
+
const entries = readdirSync3(leadDir, { withFileTypes: true });
|
|
3250
|
+
for (const entry of entries) {
|
|
3251
|
+
if (entry.isDirectory() && entry.name !== "_adhoc" && entry.name !== "workflow") {
|
|
3252
|
+
const ticketPlan = join7(leadDir, entry.name, "plan.md");
|
|
3253
|
+
if (existsSync6(ticketPlan)) {
|
|
3254
|
+
found = ticketPlan;
|
|
3255
|
+
break;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (!found) {
|
|
3260
|
+
const adhocDir = join7(leadDir, "_adhoc");
|
|
3261
|
+
if (existsSync6(adhocDir)) {
|
|
3262
|
+
const adhocEntries = readdirSync3(adhocDir);
|
|
3263
|
+
for (const e of adhocEntries) {
|
|
3264
|
+
if (e === `${planName}.md` || e.includes(planName)) {
|
|
3265
|
+
found = join7(adhocDir, e);
|
|
3266
|
+
break;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
} catch {
|
|
3272
|
+
return { complete: false };
|
|
3273
|
+
}
|
|
3274
|
+
if (found) {
|
|
3275
|
+
return {
|
|
3276
|
+
complete: true,
|
|
3277
|
+
artifacts: { plan_path: found },
|
|
3278
|
+
summary: `Plan created at: ${found}`
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
return { complete: false };
|
|
3282
|
+
}
|
|
3283
|
+
case "plan_complete": {
|
|
3284
|
+
const planName = ctx.config.plan_name;
|
|
3285
|
+
if (!planName)
|
|
3286
|
+
return { complete: false };
|
|
3287
|
+
const plansDir = join7(ctx.directory, ".lead", "plans");
|
|
3288
|
+
if (!existsSync6(plansDir))
|
|
3289
|
+
return { complete: false };
|
|
3290
|
+
let planPath = null;
|
|
3291
|
+
try {
|
|
3292
|
+
const entries = readdirSync3(plansDir);
|
|
3293
|
+
for (const entry of entries) {
|
|
3294
|
+
if (entry === `${planName}.md` || entry.includes(planName)) {
|
|
3295
|
+
planPath = join7(plansDir, entry);
|
|
3296
|
+
break;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
} catch {
|
|
3300
|
+
return { complete: false };
|
|
3301
|
+
}
|
|
3302
|
+
if (!planPath)
|
|
3303
|
+
return { complete: false };
|
|
3304
|
+
const progress = getPlanProgress(planPath);
|
|
3305
|
+
if (progress.isComplete) {
|
|
3306
|
+
return {
|
|
3307
|
+
complete: true,
|
|
3308
|
+
summary: `Plan completed: ${progress.completed}/${progress.total} tasks done`
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
return { complete: false };
|
|
3312
|
+
}
|
|
3313
|
+
case "review_verdict": {
|
|
3314
|
+
const msg = ctx.lastAssistantMessage ?? "";
|
|
3315
|
+
if (/\[\s*APPROVE\s*\]/i.test(msg)) {
|
|
3316
|
+
return {
|
|
3317
|
+
complete: true,
|
|
3318
|
+
verdict: "approve",
|
|
3319
|
+
summary: "Review verdict: APPROVE"
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
if (/\[\s*REJECT\s*\]/i.test(msg)) {
|
|
3323
|
+
return {
|
|
3324
|
+
complete: true,
|
|
3325
|
+
verdict: "reject",
|
|
3326
|
+
summary: "Review verdict: REJECT"
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
return { complete: false };
|
|
3330
|
+
}
|
|
3331
|
+
case "agent_signal": {
|
|
3332
|
+
const msg = ctx.lastAssistantMessage ?? "";
|
|
3333
|
+
if (msg.includes(WORKFLOW_STEP_COMPLETE_SIGNAL)) {
|
|
3334
|
+
return {
|
|
3335
|
+
complete: true,
|
|
3336
|
+
summary: "Agent signaled step completion"
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
const keywords = ctx.config.keywords ?? [];
|
|
3340
|
+
if (keywords.length > 0 && containsKeyword(msg, keywords)) {
|
|
3341
|
+
return {
|
|
3342
|
+
complete: true,
|
|
3343
|
+
summary: `Agent signaled via keyword`
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
return { complete: false };
|
|
3347
|
+
}
|
|
3348
|
+
default:
|
|
3349
|
+
return { complete: false };
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// src/features/workflow/engine.ts
|
|
3354
|
+
function generateInstanceId() {
|
|
3355
|
+
return "wf_" + randomBytes(4).toString("hex");
|
|
3356
|
+
}
|
|
3357
|
+
function generateSlug(goal) {
|
|
3358
|
+
return goal.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50).replace(/-+$/, "");
|
|
3359
|
+
}
|
|
3360
|
+
function nowIso() {
|
|
3361
|
+
return new Date().toISOString();
|
|
3362
|
+
}
|
|
3363
|
+
function startWorkflow(input) {
|
|
3364
|
+
const { definition, definitionPath, goal, sessionId, directory } = input;
|
|
3365
|
+
const instanceId = generateInstanceId();
|
|
3366
|
+
const slug = generateSlug(goal);
|
|
3367
|
+
const firstStep = definition.steps[0];
|
|
3368
|
+
const steps = {};
|
|
3369
|
+
for (const [i, stepDef] of definition.steps.entries()) {
|
|
3370
|
+
steps[stepDef.id] = {
|
|
3371
|
+
id: stepDef.id,
|
|
3372
|
+
status: i === 0 ? "active" : "pending"
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
const instance = {
|
|
3376
|
+
instance_id: instanceId,
|
|
3377
|
+
definition_id: definition.name,
|
|
3378
|
+
definition_name: definition.name,
|
|
3379
|
+
definition_path: definitionPath,
|
|
3380
|
+
goal,
|
|
3381
|
+
slug,
|
|
3382
|
+
status: "running",
|
|
3383
|
+
started_at: nowIso(),
|
|
3384
|
+
session_ids: [sessionId],
|
|
3385
|
+
current_step_id: firstStep.id,
|
|
3386
|
+
steps,
|
|
3387
|
+
artifacts: {}
|
|
3388
|
+
};
|
|
3389
|
+
saveWorkflowInstance(directory, instance);
|
|
3390
|
+
setActiveInstance(directory, instanceId);
|
|
3391
|
+
const prompt = buildStepContext(instance, firstStep, definition);
|
|
3392
|
+
return { type: "inject_prompt", prompt, agent: firstStep.agent };
|
|
3393
|
+
}
|
|
3394
|
+
function checkAndAdvance(input) {
|
|
3395
|
+
const { directory, context, definition } = input;
|
|
3396
|
+
const instance = loadActiveInstance(directory);
|
|
3397
|
+
if (!instance || instance.status !== "running") {
|
|
3398
|
+
return { type: "none" };
|
|
3399
|
+
}
|
|
3400
|
+
const currentStepDef = definition.steps.find((s) => s.id === instance.current_step_id);
|
|
3401
|
+
if (!currentStepDef) {
|
|
3402
|
+
return { type: "none" };
|
|
3403
|
+
}
|
|
3404
|
+
const result = checkStepCompletion(currentStepDef.completion.method, {
|
|
3405
|
+
...context,
|
|
3406
|
+
config: currentStepDef.completion,
|
|
3407
|
+
artifacts: instance.artifacts
|
|
3408
|
+
});
|
|
3409
|
+
if (!result.complete) {
|
|
3410
|
+
return { type: "none" };
|
|
3411
|
+
}
|
|
3412
|
+
if (result.verdict === "reject") {
|
|
3413
|
+
const onReject = currentStepDef.on_reject ?? "pause";
|
|
3414
|
+
if (onReject === "pause") {
|
|
3415
|
+
instance.status = "paused";
|
|
3416
|
+
instance.pause_reason = result.summary;
|
|
3417
|
+
} else {
|
|
3418
|
+
instance.status = "failed";
|
|
3419
|
+
instance.ended_at = nowIso();
|
|
3420
|
+
clearActiveInstance(directory);
|
|
3421
|
+
}
|
|
3422
|
+
instance.steps[currentStepDef.id].status = "failed";
|
|
3423
|
+
instance.steps[currentStepDef.id].verdict = "reject";
|
|
3424
|
+
saveWorkflowInstance(directory, instance);
|
|
3425
|
+
return { type: "pause", reason: result.summary };
|
|
3426
|
+
}
|
|
3427
|
+
instance.steps[currentStepDef.id].status = "completed";
|
|
3428
|
+
instance.steps[currentStepDef.id].completed_at = nowIso();
|
|
3429
|
+
instance.steps[currentStepDef.id].verdict = result.verdict;
|
|
3430
|
+
instance.steps[currentStepDef.id].summary = result.summary;
|
|
3431
|
+
if (result.artifacts) {
|
|
3432
|
+
Object.assign(instance.artifacts, result.artifacts);
|
|
3433
|
+
}
|
|
3434
|
+
const currentIndex = definition.steps.findIndex((s) => s.id === instance.current_step_id);
|
|
3435
|
+
const nextStep = definition.steps[currentIndex + 1];
|
|
3436
|
+
if (!nextStep) {
|
|
3437
|
+
instance.status = "completed";
|
|
3438
|
+
instance.ended_at = nowIso();
|
|
3439
|
+
saveWorkflowInstance(directory, instance);
|
|
3440
|
+
clearActiveInstance(directory);
|
|
3441
|
+
return { type: "complete", reason: "All workflow steps completed" };
|
|
3442
|
+
}
|
|
3443
|
+
instance.current_step_id = nextStep.id;
|
|
3444
|
+
instance.steps[nextStep.id].status = "active";
|
|
3445
|
+
instance.steps[nextStep.id].started_at = nowIso();
|
|
3446
|
+
saveWorkflowInstance(directory, instance);
|
|
3447
|
+
const prompt = buildStepContext(instance, nextStep, definition);
|
|
3448
|
+
return { type: "inject_prompt", prompt, agent: nextStep.agent };
|
|
3449
|
+
}
|
|
3450
|
+
function pauseWorkflow(directory, reason) {
|
|
3451
|
+
const instance = loadActiveInstance(directory);
|
|
3452
|
+
if (!instance)
|
|
3453
|
+
return false;
|
|
3454
|
+
instance.status = "paused";
|
|
3455
|
+
if (reason)
|
|
3456
|
+
instance.pause_reason = reason;
|
|
3457
|
+
saveWorkflowInstance(directory, instance);
|
|
3458
|
+
return true;
|
|
3459
|
+
}
|
|
3460
|
+
function resumeWorkflow(directory, definition) {
|
|
3461
|
+
const instance = loadActiveInstance(directory);
|
|
3462
|
+
if (!instance || instance.status !== "paused")
|
|
3463
|
+
return { type: "none" };
|
|
3464
|
+
instance.status = "running";
|
|
3465
|
+
instance.pause_reason = undefined;
|
|
3466
|
+
saveWorkflowInstance(directory, instance);
|
|
3467
|
+
const stepDef = definition.steps.find((s) => s.id === instance.current_step_id);
|
|
3468
|
+
if (!stepDef)
|
|
3469
|
+
return { type: "none" };
|
|
3470
|
+
const prompt = buildStepContext(instance, stepDef, definition);
|
|
3471
|
+
return { type: "inject_prompt", prompt, agent: stepDef.agent };
|
|
3472
|
+
}
|
|
3473
|
+
function abortWorkflow(directory) {
|
|
3474
|
+
const instance = loadActiveInstance(directory);
|
|
3475
|
+
if (!instance)
|
|
3476
|
+
return false;
|
|
3477
|
+
instance.status = "cancelled";
|
|
3478
|
+
instance.ended_at = nowIso();
|
|
3479
|
+
saveWorkflowInstance(directory, instance);
|
|
3480
|
+
clearActiveInstance(directory);
|
|
3481
|
+
return true;
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
// src/features/workflow/hook.ts
|
|
3485
|
+
function parseWorkflowArgs(args) {
|
|
3486
|
+
const trimmed = args.trim();
|
|
3487
|
+
if (!trimmed)
|
|
3488
|
+
return { workflowName: null, goal: null };
|
|
3489
|
+
const quotedMatch = /^([^\s"]+)\s+"([^"]+)"/.exec(trimmed);
|
|
3490
|
+
if (quotedMatch) {
|
|
3491
|
+
return { workflowName: quotedMatch[1], goal: quotedMatch[2] };
|
|
3492
|
+
}
|
|
3493
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
3494
|
+
if (spaceIdx !== -1) {
|
|
3495
|
+
return {
|
|
3496
|
+
workflowName: trimmed.slice(0, spaceIdx),
|
|
3497
|
+
goal: trimmed.slice(spaceIdx + 1).trim()
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
return { workflowName: trimmed, goal: null };
|
|
3501
|
+
}
|
|
3502
|
+
async function handleRunWorkflow(input) {
|
|
3503
|
+
const { promptText, sessionId, directory, customDirs } = input;
|
|
3504
|
+
const argsText = promptText.replace(/^\/run-workflow\s*/, "").trim();
|
|
3505
|
+
const { workflowName, goal } = parseWorkflowArgs(argsText);
|
|
3506
|
+
const discovered = discoverWorkflows({ projectDirectory: directory, customDirs });
|
|
3507
|
+
const active = loadActiveInstance(directory);
|
|
3508
|
+
if (!workflowName) {
|
|
3509
|
+
if (active) {
|
|
3510
|
+
const def2 = discovered.find((d) => d.definition.name === active.definition_id);
|
|
3511
|
+
if (!def2) {
|
|
3512
|
+
return {
|
|
3513
|
+
handled: true,
|
|
3514
|
+
message: `Active workflow '${active.definition_id}' definition not found.`
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
const action2 = resumeWorkflow(directory, def2.definition);
|
|
3518
|
+
return { handled: true, action: action2 };
|
|
3519
|
+
}
|
|
3520
|
+
if (discovered.length === 0) {
|
|
3521
|
+
return {
|
|
3522
|
+
handled: true,
|
|
3523
|
+
message: "No workflows found. Create workflow definitions in `.opencode/workflows/` or `~/.config/opencode/workflows/`."
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
const list = discovered.map((d) => `- **${d.definition.name}**: ${d.definition.description ?? "no description"}`).join(`
|
|
3527
|
+
`);
|
|
3528
|
+
return {
|
|
3529
|
+
handled: true,
|
|
3530
|
+
message: `Available workflows:
|
|
3531
|
+
${list}
|
|
3532
|
+
|
|
3533
|
+
Use \`/run-workflow <name> "your goal"\` to start.`
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
const def = discovered.find((d) => d.definition.name === workflowName);
|
|
3537
|
+
if (!def) {
|
|
3538
|
+
return {
|
|
3539
|
+
handled: true,
|
|
3540
|
+
message: `Workflow '${workflowName}' not found. Available: ${discovered.map((d) => d.definition.name).join(", ") || "none"}`
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
if (!goal) {
|
|
3544
|
+
if (active?.definition_id === workflowName) {
|
|
3545
|
+
const action2 = resumeWorkflow(directory, def.definition);
|
|
3546
|
+
return { handled: true, action: action2 };
|
|
3547
|
+
}
|
|
3548
|
+
return {
|
|
3549
|
+
handled: true,
|
|
3550
|
+
message: `Please provide a goal for workflow '${workflowName}'. Usage: /run-workflow ${workflowName} "your goal"`
|
|
3551
|
+
};
|
|
3552
|
+
}
|
|
3553
|
+
if (active && active.status === "running") {
|
|
3554
|
+
return {
|
|
3555
|
+
handled: true,
|
|
3556
|
+
message: `A workflow is already active: '${active.definition_id}'. Abort it first with "workflow abort".`
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
const action = startWorkflow({
|
|
3560
|
+
definition: def.definition,
|
|
3561
|
+
definitionPath: def.path,
|
|
3562
|
+
goal,
|
|
3563
|
+
sessionId,
|
|
3564
|
+
directory
|
|
3565
|
+
});
|
|
3566
|
+
return { handled: true, action };
|
|
3567
|
+
}
|
|
3568
|
+
async function checkWorkflowContinuation(input) {
|
|
3569
|
+
const { directory, lastAssistantMessage, lastUserMessage, customDirs } = input;
|
|
3570
|
+
const active = loadActiveInstance(directory);
|
|
3571
|
+
if (!active || active.status !== "running") {
|
|
3572
|
+
return { continuationPrompt: null, switchAgent: null };
|
|
3573
|
+
}
|
|
3574
|
+
const discovered = discoverWorkflows({ projectDirectory: directory, customDirs });
|
|
3575
|
+
const def = discovered.find((d) => d.definition.name === active.definition_id);
|
|
3576
|
+
if (!def) {
|
|
3577
|
+
return { continuationPrompt: null, switchAgent: null };
|
|
3578
|
+
}
|
|
3579
|
+
const action = checkAndAdvance({
|
|
3580
|
+
directory,
|
|
3581
|
+
context: { lastAssistantMessage, lastUserMessage, directory },
|
|
3582
|
+
definition: def.definition
|
|
3583
|
+
});
|
|
3584
|
+
if (action.type === "inject_prompt" && action.prompt) {
|
|
3585
|
+
return {
|
|
3586
|
+
continuationPrompt: `${WORKFLOW_CONTINUATION_MARKER}
|
|
3587
|
+
|
|
3588
|
+
${action.prompt}`,
|
|
3589
|
+
switchAgent: action.agent ?? null
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
if (action.type === "complete") {
|
|
3593
|
+
return {
|
|
3594
|
+
continuationPrompt: `${WORKFLOW_CONTINUATION_MARKER}
|
|
3595
|
+
|
|
3596
|
+
Workflow "${active.definition_name}" completed! All steps are done.`,
|
|
3597
|
+
switchAgent: null
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
return { continuationPrompt: null, switchAgent: null };
|
|
3601
|
+
}
|
|
3602
|
+
function handleWorkflowCommand(message, directory) {
|
|
3603
|
+
const lower = message.toLowerCase().trim();
|
|
3604
|
+
if (lower === "workflow pause" || lower === "pause workflow") {
|
|
3605
|
+
const ok = pauseWorkflow(directory, "User requested pause");
|
|
3606
|
+
return ok ? "Workflow paused." : "No active workflow to pause.";
|
|
3607
|
+
}
|
|
3608
|
+
if (lower === "workflow abort" || lower === "abort workflow") {
|
|
3609
|
+
const ok = abortWorkflow(directory);
|
|
3610
|
+
return ok ? "Workflow aborted." : "No active workflow to abort.";
|
|
3611
|
+
}
|
|
3612
|
+
if (lower === "workflow status" || lower === "status workflow") {
|
|
3613
|
+
const active = loadActiveInstance(directory);
|
|
3614
|
+
if (!active)
|
|
3615
|
+
return "No active workflow.";
|
|
3616
|
+
return `Workflow: **${active.definition_name}** | Status: ${active.status} | Step: ${active.current_step_id}`;
|
|
3617
|
+
}
|
|
3618
|
+
return null;
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
// src/runtime/opencode/plugin-adapter.ts
|
|
3622
|
+
function textPart(text) {
|
|
3623
|
+
return {
|
|
3624
|
+
id: `synthetic-${Date.now()}`,
|
|
3625
|
+
sessionID: "",
|
|
3626
|
+
messageID: "",
|
|
3627
|
+
type: "text",
|
|
3628
|
+
text
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
var sessionAgentMap = new Map;
|
|
3632
|
+
var pendingContinuations = new Map;
|
|
3633
|
+
function createPluginAdapter(args) {
|
|
3634
|
+
const { pluginConfig, directory, configHandler, hooks } = args;
|
|
3635
|
+
return {
|
|
3636
|
+
config: async (config) => {
|
|
3637
|
+
debug("plugin", "config hook called");
|
|
3638
|
+
configHandler.handle(config);
|
|
3639
|
+
},
|
|
3640
|
+
tool: {},
|
|
3641
|
+
"chat.message": async (input, output) => {
|
|
3642
|
+
const { sessionID, agent } = input;
|
|
3643
|
+
debug("plugin", `chat.message: session=${sessionID} agent=${agent}`);
|
|
3644
|
+
if (agent) {
|
|
3645
|
+
sessionAgentMap.set(sessionID, agent);
|
|
3646
|
+
}
|
|
3647
|
+
markSessionCreated(sessionID);
|
|
3648
|
+
const pending = pendingContinuations.get(sessionID);
|
|
3649
|
+
if (pending && pending.length > 0) {
|
|
3650
|
+
for (const prompt of pending) {
|
|
3651
|
+
output.parts.push(textPart(prompt));
|
|
3652
|
+
}
|
|
3653
|
+
pendingContinuations.delete(sessionID);
|
|
3654
|
+
}
|
|
3655
|
+
},
|
|
3656
|
+
"chat.params": async (_input, _output) => {},
|
|
3657
|
+
event: async ({ event }) => {
|
|
3658
|
+
debug("plugin", `event: ${event.type}`);
|
|
3659
|
+
if (event.type === "session.idle" || event.type === "session.updated") {
|
|
3660
|
+
const sessionID = event.sessionID ?? "";
|
|
3661
|
+
const prompts = [];
|
|
3662
|
+
const continuationResult = checkContinuation({
|
|
3663
|
+
sessionId: sessionID,
|
|
3664
|
+
directory
|
|
3665
|
+
});
|
|
3666
|
+
if (continuationResult.continuationPrompt) {
|
|
3667
|
+
info("plugin", "Work continuation detected");
|
|
3668
|
+
prompts.push(continuationResult.continuationPrompt);
|
|
3669
|
+
}
|
|
3670
|
+
const workflowResult = await checkWorkflowContinuation({
|
|
3671
|
+
sessionId: sessionID,
|
|
3672
|
+
directory,
|
|
3673
|
+
customDirs: pluginConfig.workflows?.directories
|
|
3674
|
+
});
|
|
3675
|
+
if (workflowResult?.continuationPrompt) {
|
|
3676
|
+
info("plugin", "Workflow continuation detected");
|
|
3677
|
+
prompts.push(workflowResult.continuationPrompt);
|
|
3678
|
+
}
|
|
3679
|
+
if (prompts.length > 0) {
|
|
3680
|
+
pendingContinuations.set(sessionID, prompts);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
},
|
|
3684
|
+
"tool.execute.before": async (input, output) => {
|
|
3685
|
+
const { tool, sessionID } = input;
|
|
3686
|
+
const args2 = output.args;
|
|
3687
|
+
debug("plugin", `tool.execute.before: ${tool}`);
|
|
3688
|
+
const activeAgent = sessionAgentMap.get(sessionID);
|
|
3689
|
+
const architectResult = hooks.checkArchitectWrite({ toolName: tool, args: args2, agentName: activeAgent });
|
|
3690
|
+
if (architectResult.verdict === "deny") {
|
|
3691
|
+
info("plugin", `Architect md-only guard blocked: ${architectResult.reason}`);
|
|
3692
|
+
if ("filePath" in args2)
|
|
3693
|
+
args2["filePath"] = "";
|
|
3694
|
+
if ("path" in args2)
|
|
3695
|
+
args2["path"] = "";
|
|
3696
|
+
if ("content" in args2)
|
|
3697
|
+
args2["content"] = `<!-- BLOCKED: ${architectResult.reason} -->`;
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
captureToDoWrite({ toolName: tool, args: args2, sessionId: sessionID });
|
|
3701
|
+
},
|
|
3702
|
+
"tool.execute.after": async (input, _output) => {
|
|
3703
|
+
const { tool, sessionID } = input;
|
|
3704
|
+
debug("plugin", `tool.execute.after: ${tool}`);
|
|
3705
|
+
const reminder = hooks.buildVerificationReminder({
|
|
3706
|
+
toolName: tool,
|
|
3707
|
+
directory
|
|
3708
|
+
});
|
|
3709
|
+
if (reminder.reminderText) {
|
|
3710
|
+
debug("plugin", "Verification reminder triggered");
|
|
3711
|
+
}
|
|
3712
|
+
},
|
|
3713
|
+
"command.execute.before": async (input, output) => {
|
|
3714
|
+
const { command, sessionID, arguments: cmdArgs } = input;
|
|
3715
|
+
debug("plugin", `command: /${command} ${cmdArgs}`);
|
|
3716
|
+
if (command === "implement") {
|
|
3717
|
+
const result = await hooks.handleStartImplementation({
|
|
3718
|
+
args: cmdArgs,
|
|
3719
|
+
sessionId: sessionID,
|
|
3720
|
+
directory
|
|
3721
|
+
});
|
|
3722
|
+
if (result.prompt) {
|
|
3723
|
+
output.parts = [textPart(result.prompt)];
|
|
3724
|
+
} else if (result.error) {
|
|
3725
|
+
output.parts = [textPart(`❌ ${result.error}`)];
|
|
3726
|
+
}
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
if (command === "run-workflow") {
|
|
3730
|
+
const result = await handleRunWorkflow({
|
|
3731
|
+
promptText: cmdArgs,
|
|
3732
|
+
sessionId: sessionID,
|
|
3733
|
+
directory,
|
|
3734
|
+
customDirs: pluginConfig.workflows?.directories
|
|
3735
|
+
});
|
|
3736
|
+
if (result.message) {
|
|
3737
|
+
output.parts = [textPart(result.message)];
|
|
3738
|
+
}
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
const workflowResult = handleWorkflowCommand(cmdArgs, directory);
|
|
3742
|
+
if (workflowResult) {
|
|
3743
|
+
output.parts = [textPart(workflowResult)];
|
|
3744
|
+
}
|
|
3745
|
+
},
|
|
3746
|
+
"tool.definition": async (input, output) => {
|
|
3747
|
+
const { toolID } = input;
|
|
3748
|
+
const overriddenDesc = hooks.applyTodoDescriptionOverride(toolID, output.description);
|
|
3749
|
+
if (overriddenDesc !== output.description) {
|
|
3750
|
+
output.description = overriddenDesc;
|
|
3751
|
+
}
|
|
3752
|
+
},
|
|
3753
|
+
"experimental.session.compacting": async (input, output) => {
|
|
3754
|
+
const { sessionID } = input;
|
|
3755
|
+
const recovery = checkCompactionRecovery({ sessionId: sessionID, directory });
|
|
3756
|
+
if (recovery.recoveryPrompt) {
|
|
3757
|
+
output.context.push(recovery.recoveryPrompt);
|
|
3758
|
+
}
|
|
3759
|
+
const todoPreservation = buildTodoPreservationPrompt(sessionID);
|
|
3760
|
+
if (todoPreservation) {
|
|
3761
|
+
output.context.push(todoPreservation);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
// src/plugin/plugin-interface.ts
|
|
3768
|
+
function createPluginInterface(args) {
|
|
3769
|
+
return createPluginAdapter(args);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
// src/index.ts
|
|
3773
|
+
var LeadPlugin = async (ctx) => {
|
|
3774
|
+
setClient(ctx.client);
|
|
3775
|
+
const pluginConfig = loadLeadConfig(ctx.directory);
|
|
3776
|
+
ctx.client.tui.showToast({
|
|
3777
|
+
body: {
|
|
3778
|
+
title: "L.E.A.D.",
|
|
3779
|
+
message: "Lucas Engineering Automation & Delivery — ready",
|
|
3780
|
+
variant: "success",
|
|
3781
|
+
duration: 4000
|
|
3782
|
+
}
|
|
3783
|
+
}).catch(() => {});
|
|
3784
|
+
const continuation = resolveContinuationConfig(pluginConfig.continuation);
|
|
3785
|
+
const { resolveSkills } = await createTools({ ctx, pluginConfig });
|
|
3786
|
+
const { configHandler } = await createManagers({
|
|
3787
|
+
ctx,
|
|
3788
|
+
pluginConfig,
|
|
3789
|
+
continuation,
|
|
3790
|
+
resolveSkills
|
|
3791
|
+
});
|
|
3792
|
+
const hooks = createHooks({
|
|
3793
|
+
pluginConfig,
|
|
3794
|
+
continuation,
|
|
3795
|
+
directory: ctx.directory
|
|
3796
|
+
});
|
|
3797
|
+
return createPluginInterface({
|
|
3798
|
+
pluginConfig,
|
|
3799
|
+
directory: ctx.directory,
|
|
3800
|
+
configHandler,
|
|
3801
|
+
hooks
|
|
3802
|
+
});
|
|
3803
|
+
};
|
|
3804
|
+
var src_default = {
|
|
3805
|
+
id: "lead",
|
|
3806
|
+
server: LeadPlugin
|
|
3807
|
+
};
|
|
3808
|
+
export {
|
|
3809
|
+
src_default as default,
|
|
3810
|
+
LeadPlugin
|
|
3811
|
+
};
|