@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.
Files changed (98) hide show
  1. package/README.md +337 -0
  2. package/dist/agents/agent-builder.d.ts +30 -0
  3. package/dist/agents/architect/index.d.ts +4 -0
  4. package/dist/agents/builtin-agents.d.ts +32 -0
  5. package/dist/agents/code-analyst/index.d.ts +4 -0
  6. package/dist/agents/cto/index.d.ts +11 -0
  7. package/dist/agents/custom-agent-factory.d.ts +27 -0
  8. package/dist/agents/dynamic-prompt-builder.d.ts +22 -0
  9. package/dist/agents/engineer/index.d.ts +4 -0
  10. package/dist/agents/executor/index.d.ts +6 -0
  11. package/dist/agents/guardian/index.d.ts +4 -0
  12. package/dist/agents/index.d.ts +17 -0
  13. package/dist/agents/lead/index.d.ts +11 -0
  14. package/dist/agents/lead-dev/index.d.ts +6 -0
  15. package/dist/agents/model-resolution.d.ts +44 -0
  16. package/dist/agents/prompt-loader.d.ts +30 -0
  17. package/dist/agents/prompt-utils.d.ts +12 -0
  18. package/dist/agents/researcher/index.d.ts +4 -0
  19. package/dist/agents/reviewer/index.d.ts +4 -0
  20. package/dist/agents/scout/index.d.ts +4 -0
  21. package/dist/agents/tech-lead/index.d.ts +11 -0
  22. package/dist/agents/tester/index.d.ts +4 -0
  23. package/dist/agents/types.d.ts +37 -0
  24. package/dist/config/continuation.d.ts +13 -0
  25. package/dist/config/index.d.ts +6 -0
  26. package/dist/config/loader.d.ts +12 -0
  27. package/dist/config/merge.d.ts +8 -0
  28. package/dist/config/scaffold.d.ts +12 -0
  29. package/dist/config/schema.d.ts +140 -0
  30. package/dist/create-managers.d.ts +32 -0
  31. package/dist/create-tools.d.ts +24 -0
  32. package/dist/domain/plans/index.d.ts +2 -0
  33. package/dist/domain/policy/policy-result.d.ts +23 -0
  34. package/dist/domain/session/index.d.ts +41 -0
  35. package/dist/domain/workflows/index.d.ts +4 -0
  36. package/dist/domain/workflows/workflow-completion.d.ts +9 -0
  37. package/dist/domain/workflows/workflow-context.d.ts +9 -0
  38. package/dist/domain/workflows/workflow-repository.d.ts +19 -0
  39. package/dist/domain/workflows/workflow-service.d.ts +32 -0
  40. package/dist/features/skill-loader/discovery.d.ts +36 -0
  41. package/dist/features/skill-loader/index.d.ts +6 -0
  42. package/dist/features/skill-loader/loader.d.ts +15 -0
  43. package/dist/features/skill-loader/opencode-client.d.ts +6 -0
  44. package/dist/features/skill-loader/resolver.d.ts +8 -0
  45. package/dist/features/skill-loader/types.d.ts +14 -0
  46. package/dist/features/work-state/constants.d.ts +4 -0
  47. package/dist/features/work-state/index.d.ts +5 -0
  48. package/dist/features/work-state/storage.d.ts +20 -0
  49. package/dist/features/work-state/types.d.ts +16 -0
  50. package/dist/features/work-state/validation-types.d.ts +8 -0
  51. package/dist/features/work-state/validation.d.ts +6 -0
  52. package/dist/features/workflow/commands.d.ts +10 -0
  53. package/dist/features/workflow/completion.d.ts +5 -0
  54. package/dist/features/workflow/constants.d.ts +8 -0
  55. package/dist/features/workflow/context.d.ts +16 -0
  56. package/dist/features/workflow/discovery.d.ts +19 -0
  57. package/dist/features/workflow/engine.d.ts +19 -0
  58. package/dist/features/workflow/hook.d.ts +45 -0
  59. package/dist/features/workflow/index.d.ts +9 -0
  60. package/dist/features/workflow/schema.d.ts +43 -0
  61. package/dist/features/workflow/storage.d.ts +7 -0
  62. package/dist/features/workflow/types.d.ts +89 -0
  63. package/dist/hooks/architect-md-only.d.ts +11 -0
  64. package/dist/hooks/compaction-recovery.d.ts +20 -0
  65. package/dist/hooks/compaction-todo-preserver.d.ts +21 -0
  66. package/dist/hooks/context-window-monitor.d.ts +26 -0
  67. package/dist/hooks/create-hooks.d.ts +45 -0
  68. package/dist/hooks/first-message-variant.d.ts +23 -0
  69. package/dist/hooks/index.d.ts +28 -0
  70. package/dist/hooks/keyword-detector.d.ts +17 -0
  71. package/dist/hooks/rules-injector.d.ts +16 -0
  72. package/dist/hooks/session-token-state.d.ts +31 -0
  73. package/dist/hooks/start-implementation-hook.d.ts +26 -0
  74. package/dist/hooks/start-work-hook.d.ts +26 -0
  75. package/dist/hooks/todo-continuation-enforcer.d.ts +22 -0
  76. package/dist/hooks/todo-description-override.d.ts +10 -0
  77. package/dist/hooks/todo-writer.d.ts +20 -0
  78. package/dist/hooks/verification-reminder.d.ts +21 -0
  79. package/dist/hooks/work-continuation.d.ts +23 -0
  80. package/dist/hooks/write-existing-file-guard.d.ts +34 -0
  81. package/dist/index.d.ts +17 -0
  82. package/dist/index.js +3811 -0
  83. package/dist/infrastructure/fs/config-fs-loader.d.ts +6 -0
  84. package/dist/managers/background-manager.d.ts +48 -0
  85. package/dist/managers/config-handler.d.ts +31 -0
  86. package/dist/managers/index.d.ts +6 -0
  87. package/dist/managers/skill-mcp-manager.d.ts +31 -0
  88. package/dist/plugin/index.d.ts +2 -0
  89. package/dist/plugin/plugin-interface.d.ts +7 -0
  90. package/dist/plugin/types.d.ts +18 -0
  91. package/dist/runtime/opencode/plugin-adapter.d.ts +17 -0
  92. package/dist/shared/agent-display-names.d.ts +3 -0
  93. package/dist/shared/index.d.ts +6 -0
  94. package/dist/shared/log.d.ts +9 -0
  95. package/dist/shared/resolve-safe-path.d.ts +7 -0
  96. package/dist/shared/types.d.ts +3 -0
  97. package/dist/shared/version.d.ts +1 -0
  98. 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
+ };