@wrongstack/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DaOnnHNW.d.ts → agent-bridge-BkjOkSkD.d.ts} +1 -1
- package/dist/{compactor-CFky6JKM.d.ts → compactor-CTHjZAwf.d.ts} +1 -1
- package/dist/{config-RlhKLjme.d.ts → config-DO2eCKgq.d.ts} +1 -1
- package/dist/{context-B1kBj1lY.d.ts → context-P207fTpo.d.ts} +11 -0
- package/dist/coordination/index.d.ts +10 -891
- package/dist/coordination/index.js +407 -236
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +19 -18
- package/dist/defaults/index.js +2163 -301
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-BBaKeMfb.d.ts → events-DYAbU84e.d.ts} +41 -1
- package/dist/execution/index.d.ts +19 -13
- package/dist/execution/index.js +15 -6
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/index-BHL8QDCL.d.ts +899 -0
- package/dist/{index-meJRuQtc.d.ts → index-BNzUUDVR.d.ts} +8 -6
- package/dist/index.d.ts +30 -27
- package/dist/index.js +3792 -1466
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +71 -18
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +10 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-Cf4-bJnd.d.ts → mcp-servers-CLkhKdnF.d.ts} +18 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +106 -0
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-D5m66hzB.d.ts → multi-agent-B1Nn9eC0.d.ts} +71 -16
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-Bg4OP5fi.d.ts → path-resolver-mPccVA0l.d.ts} +9 -2
- package/dist/{provider-runner-CU9gnU2E.d.ts → provider-runner-D7lHu07L.d.ts} +3 -3
- package/dist/{skill-DayhFUBb.d.ts → retry-policy-CnYVKGPv.d.ts} +2 -28
- package/dist/sdd/index.d.ts +419 -5
- package/dist/sdd/index.js +1580 -1
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-scrubber-Dyh5LVYL.d.ts → secret-scrubber-BSgec8CU.d.ts} +1 -1
- package/dist/{secret-scrubber-DyUAWS09.d.ts → secret-scrubber-Buo9zLGs.d.ts} +1 -1
- package/dist/security/index.d.ts +8 -4
- package/dist/security/index.js +24 -5
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-DBEiwXBk.d.ts → selector-B-CGOshv.d.ts} +1 -1
- package/dist/{session-reader-D-z0AgHs.d.ts → session-reader-DmOEmSkq.d.ts} +1 -1
- package/dist/skill-CxuWrsKK.d.ts +29 -0
- package/dist/skills/index.d.ts +136 -0
- package/dist/skills/index.js +478 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/index.js +9 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-DNetCecu.d.ts → system-prompt-CzY1zrbC.d.ts} +1 -1
- package/dist/{tool-executor-B5bgmEgE.d.ts → tool-executor-CYe5cp5U.d.ts} +4 -4
- package/dist/types/index.d.ts +16 -15
- package/dist/types/index.js +132 -5
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +35 -2
- package/dist/utils/index.js +49 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/sdd/SKILL.md +93 -6
- package/skills/skill-creator/SKILL.md +98 -0
package/dist/defaults/index.js
CHANGED
|
@@ -1167,7 +1167,15 @@ var ENV_MAP = {
|
|
|
1167
1167
|
function isPrimitiveArray(a) {
|
|
1168
1168
|
return a.every((v) => v === null || typeof v !== "object");
|
|
1169
1169
|
}
|
|
1170
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
1170
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
1171
|
+
"__proto__",
|
|
1172
|
+
"constructor",
|
|
1173
|
+
"prototype",
|
|
1174
|
+
"__defineGetter__",
|
|
1175
|
+
"__defineSetter__",
|
|
1176
|
+
"__lookupGetter__",
|
|
1177
|
+
"__lookupSetter__"
|
|
1178
|
+
]);
|
|
1171
1179
|
function deepMerge(base, patch) {
|
|
1172
1180
|
if (typeof base !== "object" || base === null) return patch ?? base;
|
|
1173
1181
|
if (typeof patch !== "object" || patch === null) return base;
|
|
@@ -2197,12 +2205,15 @@ var PATTERNS = [
|
|
|
2197
2205
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
2198
2206
|
{
|
|
2199
2207
|
type: "bearer_token",
|
|
2200
|
-
|
|
2208
|
+
// Bounded at 512 chars to prevent catastrophic backtracking on adversarial input.
|
|
2209
|
+
// Negative lookahead is a simple single-char check — no backtracking risk.
|
|
2210
|
+
regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?![A-Za-z0-9_.~+/-])/g
|
|
2201
2211
|
},
|
|
2202
2212
|
{
|
|
2203
2213
|
type: "high_entropy_env",
|
|
2204
|
-
// Value
|
|
2205
|
-
|
|
2214
|
+
// Value bounded at 512 chars; negative lookahead simplified to avoid
|
|
2215
|
+
// nested quantifier backtracking on adversarial input.
|
|
2216
|
+
regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?/g
|
|
2206
2217
|
}
|
|
2207
2218
|
];
|
|
2208
2219
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
@@ -2415,7 +2426,7 @@ function walkCount(node, vault, counter) {
|
|
|
2415
2426
|
if (Array.isArray(node)) {
|
|
2416
2427
|
return node.map((item) => walkCount(item, vault, counter));
|
|
2417
2428
|
}
|
|
2418
|
-
const out =
|
|
2429
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2419
2430
|
for (const [k, v] of Object.entries(node)) {
|
|
2420
2431
|
if (typeof v === "string" && isSecretField2(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
2421
2432
|
out[k] = vault.encrypt(v);
|
|
@@ -2428,7 +2439,15 @@ function walkCount(node, vault, counter) {
|
|
|
2428
2439
|
}
|
|
2429
2440
|
return out;
|
|
2430
2441
|
}
|
|
2431
|
-
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
2442
|
+
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
2443
|
+
"__proto__",
|
|
2444
|
+
"constructor",
|
|
2445
|
+
"prototype",
|
|
2446
|
+
"__defineGetter__",
|
|
2447
|
+
"__defineSetter__",
|
|
2448
|
+
"__lookupGetter__",
|
|
2449
|
+
"__lookupSetter__"
|
|
2450
|
+
]);
|
|
2432
2451
|
function deepMerge2(a, b) {
|
|
2433
2452
|
const out = { ...a };
|
|
2434
2453
|
for (const [k, v] of Object.entries(b)) {
|
|
@@ -2568,6 +2587,14 @@ var DefaultPermissionPolicy = class {
|
|
|
2568
2587
|
setPromptDelegate(delegate) {
|
|
2569
2588
|
this.promptDelegate = delegate;
|
|
2570
2589
|
}
|
|
2590
|
+
/** Toggle YOLO (auto-approve) mode at runtime. */
|
|
2591
|
+
setYolo(enabled) {
|
|
2592
|
+
this.yolo = enabled;
|
|
2593
|
+
}
|
|
2594
|
+
/** Check whether YOLO mode is currently active. */
|
|
2595
|
+
getYolo() {
|
|
2596
|
+
return this.yolo;
|
|
2597
|
+
}
|
|
2571
2598
|
async reload() {
|
|
2572
2599
|
try {
|
|
2573
2600
|
const raw = await fsp.readFile(this.trustFile, "utf8");
|
|
@@ -3032,6 +3059,9 @@ var DefaultSkillLoader = class {
|
|
|
3032
3059
|
}
|
|
3033
3060
|
return entries;
|
|
3034
3061
|
}
|
|
3062
|
+
invalidateCache() {
|
|
3063
|
+
this.cache = void 0;
|
|
3064
|
+
}
|
|
3035
3065
|
async readBody(name) {
|
|
3036
3066
|
const m = await this.find(name);
|
|
3037
3067
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
@@ -3417,6 +3447,54 @@ function estimateToolResultTokens(content) {
|
|
|
3417
3447
|
function estimateTextTokens(text) {
|
|
3418
3448
|
return RoughTokenEstimate(text);
|
|
3419
3449
|
}
|
|
3450
|
+
function estimateToolDefTokens(tool) {
|
|
3451
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
3452
|
+
}
|
|
3453
|
+
function estimateRequestTokens(messages, systemPrompt, tools) {
|
|
3454
|
+
let messagesTokens = 0;
|
|
3455
|
+
if (typeof messages === "string") {
|
|
3456
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
3457
|
+
} else if (Array.isArray(messages)) {
|
|
3458
|
+
for (const m of messages) {
|
|
3459
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
3460
|
+
const content = m.content;
|
|
3461
|
+
if (typeof content === "string") {
|
|
3462
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
3463
|
+
} else if (Array.isArray(content)) {
|
|
3464
|
+
for (const b of content) {
|
|
3465
|
+
if (typeof b === "object" && b !== null) {
|
|
3466
|
+
if (b.type === "text") {
|
|
3467
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
3468
|
+
} else {
|
|
3469
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
let systemTokens = 0;
|
|
3478
|
+
if (typeof systemPrompt === "string") {
|
|
3479
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
3480
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
3481
|
+
for (const b of systemPrompt) {
|
|
3482
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
3483
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
let toolsTokens = 0;
|
|
3488
|
+
for (const t of tools) {
|
|
3489
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
messages: messagesTokens,
|
|
3493
|
+
systemPrompt: systemTokens,
|
|
3494
|
+
tools: toolsTokens,
|
|
3495
|
+
total: messagesTokens + systemTokens + toolsTokens
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3420
3498
|
|
|
3421
3499
|
// src/execution/compactor.ts
|
|
3422
3500
|
var HybridCompactor = class {
|
|
@@ -4164,11 +4242,12 @@ Summarize the following message range:`;
|
|
|
4164
4242
|
var AutoCompactionMiddleware = class {
|
|
4165
4243
|
name = "AutoCompaction";
|
|
4166
4244
|
compactor;
|
|
4245
|
+
estimator;
|
|
4167
4246
|
warnThreshold;
|
|
4168
4247
|
softThreshold;
|
|
4169
4248
|
hardThreshold;
|
|
4170
|
-
|
|
4171
|
-
|
|
4249
|
+
/** Writable so model-switch can update the denominator without re-registering the middleware. */
|
|
4250
|
+
_maxContext;
|
|
4172
4251
|
aggressiveOn;
|
|
4173
4252
|
events;
|
|
4174
4253
|
failureMode;
|
|
@@ -4187,7 +4266,7 @@ var AutoCompactionMiddleware = class {
|
|
|
4187
4266
|
constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
4188
4267
|
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
4189
4268
|
this.compactor = compactor;
|
|
4190
|
-
this.
|
|
4269
|
+
this._maxContext = maxContext;
|
|
4191
4270
|
this.estimator = estimator;
|
|
4192
4271
|
this.warnThreshold = thresholds.warn;
|
|
4193
4272
|
this.softThreshold = thresholds.soft;
|
|
@@ -4197,10 +4276,15 @@ var AutoCompactionMiddleware = class {
|
|
|
4197
4276
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
4198
4277
|
this.policyProvider = opts.policyProvider;
|
|
4199
4278
|
}
|
|
4279
|
+
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
4280
|
+
* denominator when the active model changes. */
|
|
4281
|
+
setMaxContext(maxContext) {
|
|
4282
|
+
this._maxContext = maxContext;
|
|
4283
|
+
}
|
|
4200
4284
|
handler() {
|
|
4201
4285
|
return async (ctx, next) => {
|
|
4202
4286
|
const tokens = this.estimator(ctx);
|
|
4203
|
-
const load = tokens / this.
|
|
4287
|
+
const load = tokens / this._maxContext;
|
|
4204
4288
|
const policy = this.policyProvider?.(ctx);
|
|
4205
4289
|
const thresholds = policy?.thresholds ?? {
|
|
4206
4290
|
warn: this.warnThreshold,
|
|
@@ -4229,7 +4313,7 @@ var AutoCompactionMiddleware = class {
|
|
|
4229
4313
|
aggressive,
|
|
4230
4314
|
level: pressure.level,
|
|
4231
4315
|
tokens: pressure.tokens,
|
|
4232
|
-
maxContext: this.
|
|
4316
|
+
maxContext: this._maxContext,
|
|
4233
4317
|
load: pressure.load,
|
|
4234
4318
|
fatal
|
|
4235
4319
|
});
|
|
@@ -4241,7 +4325,7 @@ var AutoCompactionMiddleware = class {
|
|
|
4241
4325
|
context: {
|
|
4242
4326
|
level: pressure.level,
|
|
4243
4327
|
tokens: pressure.tokens,
|
|
4244
|
-
maxContext: this.
|
|
4328
|
+
maxContext: this._maxContext
|
|
4245
4329
|
},
|
|
4246
4330
|
cause: err
|
|
4247
4331
|
});
|
|
@@ -4989,7 +5073,9 @@ var FleetBus = class {
|
|
|
4989
5073
|
"session.damaged",
|
|
4990
5074
|
"compaction.fired",
|
|
4991
5075
|
"compaction.failed",
|
|
4992
|
-
"token.threshold"
|
|
5076
|
+
"token.threshold",
|
|
5077
|
+
// Subagent hit a soft budget limit — coordinator can extend or stop.
|
|
5078
|
+
"budget.threshold_reached"
|
|
4993
5079
|
];
|
|
4994
5080
|
const offs = [];
|
|
4995
5081
|
for (const t of FORWARDED_TYPES) {
|
|
@@ -5150,6 +5236,21 @@ var BudgetExceededError = class extends Error {
|
|
|
5150
5236
|
this.observed = observed;
|
|
5151
5237
|
}
|
|
5152
5238
|
};
|
|
5239
|
+
var BudgetThresholdSignal = class extends Error {
|
|
5240
|
+
kind;
|
|
5241
|
+
limit;
|
|
5242
|
+
used;
|
|
5243
|
+
/** Resolves to 'extend' (with optional new limits) or 'stop' */
|
|
5244
|
+
decision;
|
|
5245
|
+
constructor(kind, limit, used, decision) {
|
|
5246
|
+
super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
|
|
5247
|
+
this.name = "BudgetThresholdSignal";
|
|
5248
|
+
this.kind = kind;
|
|
5249
|
+
this.limit = limit;
|
|
5250
|
+
this.used = used;
|
|
5251
|
+
this.decision = decision;
|
|
5252
|
+
}
|
|
5253
|
+
};
|
|
5153
5254
|
var SubagentBudget = class {
|
|
5154
5255
|
limits;
|
|
5155
5256
|
iterations = 0;
|
|
@@ -5158,22 +5259,111 @@ var SubagentBudget = class {
|
|
|
5158
5259
|
tokenOutput = 0;
|
|
5159
5260
|
costUsd = 0;
|
|
5160
5261
|
startTime = null;
|
|
5262
|
+
_onThreshold;
|
|
5263
|
+
/**
|
|
5264
|
+
* Injected by the runner when wiring the budget to its EventBus.
|
|
5265
|
+
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
5266
|
+
*/
|
|
5267
|
+
_events;
|
|
5268
|
+
/**
|
|
5269
|
+
* Optional callback for soft-limit handling. When set, the budget will
|
|
5270
|
+
* call this instead of throwing when a limit is first reached. The
|
|
5271
|
+
* handler decides whether to throw, continue, or ask the coordinator.
|
|
5272
|
+
*/
|
|
5273
|
+
get onThreshold() {
|
|
5274
|
+
return this._onThreshold;
|
|
5275
|
+
}
|
|
5276
|
+
set onThreshold(fn) {
|
|
5277
|
+
this._onThreshold = fn;
|
|
5278
|
+
}
|
|
5161
5279
|
constructor(limits = {}) {
|
|
5162
5280
|
this.limits = Object.freeze({ ...limits });
|
|
5163
5281
|
}
|
|
5164
5282
|
start() {
|
|
5165
5283
|
this.startTime = Date.now();
|
|
5166
5284
|
}
|
|
5285
|
+
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
5286
|
+
isNearLimit() {
|
|
5287
|
+
const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
|
|
5288
|
+
if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
|
|
5289
|
+
if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
|
|
5290
|
+
if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
|
|
5291
|
+
if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
|
|
5292
|
+
return false;
|
|
5293
|
+
}
|
|
5294
|
+
/**
|
|
5295
|
+
* Synchronous budget check — always throws synchronously so callers
|
|
5296
|
+
* (especially test event handlers using `expect().toThrow()`) get an
|
|
5297
|
+
* unhandled rejection when the budget is exceeded without a handler.
|
|
5298
|
+
* When `onThreshold` IS configured, the actual async threshold-handling
|
|
5299
|
+
* is dispatched as a fire-and-forget promise.
|
|
5300
|
+
*/
|
|
5301
|
+
checkLimit(kind, used, limit) {
|
|
5302
|
+
if (kind === "timeout" || !this._onThreshold) {
|
|
5303
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
5304
|
+
}
|
|
5305
|
+
void this.checkLimitAsync(kind, used, limit);
|
|
5306
|
+
}
|
|
5307
|
+
/**
|
|
5308
|
+
* Async threshold negotiation with the coordinator. Fire-and-forget —
|
|
5309
|
+
* any error thrown here becomes an unhandled rejection in the test environment
|
|
5310
|
+
* because the runner's catch only handles the synchronous throw from `checkLimit`.
|
|
5311
|
+
*/
|
|
5312
|
+
async checkLimitAsync(kind, used, limit) {
|
|
5313
|
+
const result = this._onThreshold({
|
|
5314
|
+
kind,
|
|
5315
|
+
used,
|
|
5316
|
+
limit,
|
|
5317
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
5318
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
5319
|
+
// The runner wires this by injecting its EventBus into ctx.budget._events.
|
|
5320
|
+
requestDecision: () => {
|
|
5321
|
+
return new Promise((resolve2) => {
|
|
5322
|
+
this._events?.emit("budget.threshold_reached", {
|
|
5323
|
+
kind,
|
|
5324
|
+
used,
|
|
5325
|
+
limit,
|
|
5326
|
+
timeoutMs: 3e4,
|
|
5327
|
+
extend: (extra) => resolve2({ extend: extra }),
|
|
5328
|
+
deny: () => resolve2("stop")
|
|
5329
|
+
});
|
|
5330
|
+
});
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
if (result === "throw") {
|
|
5334
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
5335
|
+
}
|
|
5336
|
+
if (result === "continue") {
|
|
5337
|
+
return;
|
|
5338
|
+
}
|
|
5339
|
+
const decision = await result;
|
|
5340
|
+
if (decision === "stop") {
|
|
5341
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
5342
|
+
}
|
|
5343
|
+
const ext = decision.extend;
|
|
5344
|
+
if (ext.maxIterations !== void 0) {
|
|
5345
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
5346
|
+
}
|
|
5347
|
+
if (ext.maxToolCalls !== void 0) {
|
|
5348
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
5349
|
+
}
|
|
5350
|
+
if (ext.maxTokens !== void 0) {
|
|
5351
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
5352
|
+
}
|
|
5353
|
+
if (ext.maxCostUsd !== void 0) {
|
|
5354
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5167
5357
|
recordIteration() {
|
|
5168
5358
|
this.iterations++;
|
|
5169
5359
|
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
5170
|
-
|
|
5360
|
+
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
5171
5361
|
}
|
|
5172
5362
|
}
|
|
5173
5363
|
recordToolCall() {
|
|
5174
5364
|
this.toolCalls++;
|
|
5175
5365
|
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
5176
|
-
|
|
5366
|
+
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
5177
5367
|
}
|
|
5178
5368
|
}
|
|
5179
5369
|
recordUsage(usage, costUsd = 0) {
|
|
@@ -5182,10 +5372,10 @@ var SubagentBudget = class {
|
|
|
5182
5372
|
this.costUsd += costUsd;
|
|
5183
5373
|
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
5184
5374
|
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
5185
|
-
|
|
5375
|
+
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
5186
5376
|
}
|
|
5187
5377
|
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
5188
|
-
|
|
5378
|
+
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
5189
5379
|
}
|
|
5190
5380
|
}
|
|
5191
5381
|
/**
|
|
@@ -5219,6 +5409,198 @@ var SubagentBudget = class {
|
|
|
5219
5409
|
}
|
|
5220
5410
|
};
|
|
5221
5411
|
|
|
5412
|
+
// src/coordination/fleet.ts
|
|
5413
|
+
var AUDIT_LOG_AGENT = {
|
|
5414
|
+
id: "audit-log",
|
|
5415
|
+
name: "Audit Log",
|
|
5416
|
+
role: "audit-log",
|
|
5417
|
+
prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
|
|
5418
|
+
session logs and produce actionable markdown reports.
|
|
5419
|
+
|
|
5420
|
+
Scope:
|
|
5421
|
+
- Parse session logs (iteration counts, tool calls, errors, usage)
|
|
5422
|
+
- Detect repeated failure patterns across multiple runs
|
|
5423
|
+
- Identify tool usage anomalies (over-use, failures, unexpected chains)
|
|
5424
|
+
- Track token consumption trends
|
|
5425
|
+
- Generate structured audit reports with severity ratings
|
|
5426
|
+
|
|
5427
|
+
Input format you accept:
|
|
5428
|
+
{ "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
|
|
5429
|
+
|
|
5430
|
+
Output: Markdown audit report with sections:
|
|
5431
|
+
- ## Summary (totals, error rate)
|
|
5432
|
+
- ## Top Errors (count + context)
|
|
5433
|
+
- ## Tool Usage (table with calls, failures, avg duration)
|
|
5434
|
+
- ## Anomalies (pattern \u2192 severity)
|
|
5435
|
+
|
|
5436
|
+
Working rules:
|
|
5437
|
+
- Never fabricate numbers \u2014 read the actual logs first
|
|
5438
|
+
- Always include file:line references for errors
|
|
5439
|
+
- If sessionPath is missing, ask the director to provide it
|
|
5440
|
+
- Report confidence level: high (>90% accuracy), medium, low`
|
|
5441
|
+
// No hardcoded budgets — the orchestrator (delegate tool or
|
|
5442
|
+
// spawn_subagent) decides per-task how much room a subagent gets.
|
|
5443
|
+
// A monorepo audit needs hours; a single-file lint check needs
|
|
5444
|
+
// seconds. Pinning a number here forces the orchestrator to fight
|
|
5445
|
+
// the role's default instead of just asking for what it needs.
|
|
5446
|
+
};
|
|
5447
|
+
var BUG_HUNTER_AGENT = {
|
|
5448
|
+
id: "bug-hunter",
|
|
5449
|
+
name: "Bug Hunter",
|
|
5450
|
+
role: "bug-hunter",
|
|
5451
|
+
prompt: `You are the Bug Hunter agent. Your job is to systematically scan
|
|
5452
|
+
source code for bugs, anti-patterns, and code smells using pattern matching
|
|
5453
|
+
and heuristics. Output a prioritized hit list with file:line references.
|
|
5454
|
+
|
|
5455
|
+
Scope:
|
|
5456
|
+
- Detect common bug patterns (uncaught errors, resource leaks, race conditions)
|
|
5457
|
+
- Identify anti-patterns (callback hell, God objects, circular deps)
|
|
5458
|
+
- Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
|
|
5459
|
+
- Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
|
|
5460
|
+
- Rank findings: critical > high > medium > low
|
|
5461
|
+
|
|
5462
|
+
Input format you accept:
|
|
5463
|
+
{ "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
|
|
5464
|
+
|
|
5465
|
+
Output: Markdown bug hunt report:
|
|
5466
|
+
- ## Critical (must fix first)
|
|
5467
|
+
- ## High (should fix)
|
|
5468
|
+
- ## Medium
|
|
5469
|
+
- ## Low (consider)
|
|
5470
|
+
Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
|
|
5471
|
+
|
|
5472
|
+
Bug pattern reference you know:
|
|
5473
|
+
| Pattern | Regex hint | Severity |
|
|
5474
|
+
|---------|------------|----------|
|
|
5475
|
+
| Uncaught promise | /.then\\(.*\\)/ without catch | high |
|
|
5476
|
+
| Event leak | on\\( without off/removeListener | high |
|
|
5477
|
+
| Hardcoded secret | [a-zA-Z0-9/_-]{20,} in config files | critical |
|
|
5478
|
+
| unsafe any | : any\\b or <any> | medium |
|
|
5479
|
+
| innerHTML | innerHTML\\s*= | high |
|
|
5480
|
+
|
|
5481
|
+
Working rules:
|
|
5482
|
+
- Never scan node_modules \u2014 it's noise
|
|
5483
|
+
- Always include file:line for every finding
|
|
5484
|
+
- If >30% of findings are false positives, note the confidence level
|
|
5485
|
+
- Ask director for clarification if paths are ambiguous`
|
|
5486
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5487
|
+
};
|
|
5488
|
+
var REFACTOR_PLANNER_AGENT = {
|
|
5489
|
+
id: "refactor-planner",
|
|
5490
|
+
name: "Refactor Planner",
|
|
5491
|
+
role: "refactor-planner",
|
|
5492
|
+
prompt: `You are the Refactor Planner agent. Your job is to analyze code
|
|
5493
|
+
structure and produce a concrete, phased refactoring plan with risk
|
|
5494
|
+
assessment, dependency ordering, and rollback strategy.
|
|
5495
|
+
|
|
5496
|
+
Scope:
|
|
5497
|
+
- Map module-level dependencies (import graph)
|
|
5498
|
+
- Identify coupling hotspots (high fan-in/out modules)
|
|
5499
|
+
- Assess refactoring risk by complexity and test coverage
|
|
5500
|
+
- Generate phased plans with checkpoint milestones
|
|
5501
|
+
- Produce diff-friendly task lists (one task = one concern)
|
|
5502
|
+
|
|
5503
|
+
Input format you accept:
|
|
5504
|
+
{ "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
|
|
5505
|
+
|
|
5506
|
+
Output: Markdown refactor plan:
|
|
5507
|
+
- ## Phase 1: Low Risk / High Payoff (do first)
|
|
5508
|
+
Table: | # | Task | Module | Risk | Est. Time |
|
|
5509
|
+
- ## Phase 2: Medium Risk
|
|
5510
|
+
- ## Phase 3: High Risk (requires full regression)
|
|
5511
|
+
- ## Dependency Graph (abbreviated ASCII)
|
|
5512
|
+
- ## Rollback Strategy
|
|
5513
|
+
- ## Exit Criteria (checkbox list)
|
|
5514
|
+
|
|
5515
|
+
Risk scoring criteria:
|
|
5516
|
+
| Factor | Low | Medium | High |
|
|
5517
|
+
|--------|-----|--------|------|
|
|
5518
|
+
| Cyclomatic complexity | <10 | 10-20 | >20 |
|
|
5519
|
+
| Test coverage | >80% | 50-80% | <50% |
|
|
5520
|
+
| Fan-out (imports) | <5 | 5-15 | >15 |
|
|
5521
|
+
|
|
5522
|
+
Working rules:
|
|
5523
|
+
- Always include rollback strategy \u2014 every refactor can fail
|
|
5524
|
+
- Merge tasks that take <1h into a single phase
|
|
5525
|
+
- Respect team constraints (reviewer availability, parallelization)
|
|
5526
|
+
- Never plan without analyzing the actual code first`
|
|
5527
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5528
|
+
};
|
|
5529
|
+
var SECURITY_SCANNER_AGENT = {
|
|
5530
|
+
id: "security-scanner",
|
|
5531
|
+
name: "Security Scanner",
|
|
5532
|
+
role: "security-scanner",
|
|
5533
|
+
prompt: `You are the Security Scanner agent. Your job is to scan code,
|
|
5534
|
+
configs, and dependencies for security issues from hardcoded secrets to
|
|
5535
|
+
supply chain risks.
|
|
5536
|
+
|
|
5537
|
+
Scope:
|
|
5538
|
+
- Detect hardcoded secrets: API keys, tokens, passwords, private keys
|
|
5539
|
+
- Find injection vectors: eval, innerHTML, SQL concat, shell injection
|
|
5540
|
+
- Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
|
|
5541
|
+
- Scan dependencies for known CVEs (via npm/pnpm audit)
|
|
5542
|
+
- Flag supply chain risks: postinstall hooks, unverified scripts, .npmrc
|
|
5543
|
+
|
|
5544
|
+
Input format you accept:
|
|
5545
|
+
{ "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
|
|
5546
|
+
|
|
5547
|
+
Output: Markdown security report:
|
|
5548
|
+
- ## CRITICAL: Secrets Found (with code snippets)
|
|
5549
|
+
- ## HIGH: Injection Vectors
|
|
5550
|
+
- ## MEDIUM: Insecure Patterns
|
|
5551
|
+
- ## Dependency Issues (CVE list)
|
|
5552
|
+
- ## Summary table (severity \u2192 count)
|
|
5553
|
+
- ## Remediation Checklist (with checkboxes)
|
|
5554
|
+
|
|
5555
|
+
Secret patterns you detect:
|
|
5556
|
+
| Pattern | Example | Severity |
|
|
5557
|
+
|---------|---------|----------|
|
|
5558
|
+
| AWS Access Key | AKIAIOSFODNN7EXAMPLE | critical |
|
|
5559
|
+
| AWS Secret Key | [a-zA-Z0-9/+=]{40} base64 | critical |
|
|
5560
|
+
| GitHub Token | ghp_[a-zA-Z0-9]{36} | critical |
|
|
5561
|
+
| Private Key PEM | -----BEGIN.*PRIVATE KEY----- | critical |
|
|
5562
|
+
| JWT | eyJ[a-zA-Z0-9_-]+ | high |
|
|
5563
|
+
|
|
5564
|
+
Injection patterns:
|
|
5565
|
+
| Construct | Safe alternative |
|
|
5566
|
+
|-----------|-----------------|
|
|
5567
|
+
| eval(str) | new Function() or parse |
|
|
5568
|
+
| innerHTML = x | textContent or sanitize |
|
|
5569
|
+
| exec(\`cmd \${x}\`) | execFile with args array |
|
|
5570
|
+
|
|
5571
|
+
Working rules:
|
|
5572
|
+
- Never scan node_modules \u2014 use npm audit instead
|
|
5573
|
+
- Always provide remediation steps, not just findings
|
|
5574
|
+
- Verify regex-based secrets before flagging (false positive risk)
|
|
5575
|
+
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
5576
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5577
|
+
};
|
|
5578
|
+
var FLEET_ROSTER = {
|
|
5579
|
+
"audit-log": AUDIT_LOG_AGENT,
|
|
5580
|
+
"bug-hunter": BUG_HUNTER_AGENT,
|
|
5581
|
+
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
5582
|
+
"security-scanner": SECURITY_SCANNER_AGENT
|
|
5583
|
+
};
|
|
5584
|
+
var FLEET_ROSTER_BUDGETS = {
|
|
5585
|
+
"audit-log": { timeoutMs: 5 * 60 * 1e3, maxIterations: 80, maxToolCalls: 300 },
|
|
5586
|
+
"bug-hunter": { timeoutMs: 10 * 60 * 1e3, maxIterations: 120, maxToolCalls: 400 },
|
|
5587
|
+
"refactor-planner": { timeoutMs: 8 * 60 * 1e3, maxIterations: 100, maxToolCalls: 350 },
|
|
5588
|
+
"security-scanner": { timeoutMs: 10 * 60 * 1e3, maxIterations: 120, maxToolCalls: 400 }
|
|
5589
|
+
};
|
|
5590
|
+
function applyRosterBudget(cfg) {
|
|
5591
|
+
const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
|
|
5592
|
+
if (!defaultBudget) return cfg;
|
|
5593
|
+
return {
|
|
5594
|
+
...cfg,
|
|
5595
|
+
timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
|
|
5596
|
+
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
5597
|
+
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
5598
|
+
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
5599
|
+
maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
|
|
5603
|
+
|
|
5222
5604
|
// src/coordination/multi-agent-coordinator.ts
|
|
5223
5605
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
5224
5606
|
coordinatorId;
|
|
@@ -5459,12 +5841,13 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5459
5841
|
task.subagentId = subagentId;
|
|
5460
5842
|
subagent.context.tasks.push(task);
|
|
5461
5843
|
this.emit("task.assigned", { task, subagentId });
|
|
5844
|
+
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
5462
5845
|
const budget = new SubagentBudget({
|
|
5463
|
-
maxIterations:
|
|
5464
|
-
maxToolCalls: task.maxToolCalls ??
|
|
5465
|
-
maxTokens:
|
|
5466
|
-
maxCostUsd:
|
|
5467
|
-
timeoutMs: task.timeoutMs ??
|
|
5846
|
+
maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
|
|
5847
|
+
maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
|
|
5848
|
+
maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
|
|
5849
|
+
maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
|
|
5850
|
+
timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
|
|
5468
5851
|
});
|
|
5469
5852
|
subagent.activeBudget = budget;
|
|
5470
5853
|
if (!this.runner) {
|
|
@@ -6002,6 +6385,36 @@ var Director = class {
|
|
|
6002
6385
|
this.scheduleManifest();
|
|
6003
6386
|
};
|
|
6004
6387
|
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
6388
|
+
const extendCounts = /* @__PURE__ */ new Map();
|
|
6389
|
+
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
6390
|
+
const payload = e.payload;
|
|
6391
|
+
const guardKey = `${e.subagentId}:${payload.kind}`;
|
|
6392
|
+
const prior = extendCounts.get(guardKey) ?? 0;
|
|
6393
|
+
if (prior >= 2) {
|
|
6394
|
+
payload.deny();
|
|
6395
|
+
extendCounts.delete(guardKey);
|
|
6396
|
+
return;
|
|
6397
|
+
}
|
|
6398
|
+
extendCounts.set(guardKey, prior + 1);
|
|
6399
|
+
setTimeout(() => {
|
|
6400
|
+
const extra = {};
|
|
6401
|
+
switch (payload.kind) {
|
|
6402
|
+
case "iterations":
|
|
6403
|
+
extra.maxIterations = Math.min(payload.used + 50, 500);
|
|
6404
|
+
break;
|
|
6405
|
+
case "tool_calls":
|
|
6406
|
+
extra.maxToolCalls = Math.min(Math.ceil(payload.limit * 1.5), 1e3);
|
|
6407
|
+
break;
|
|
6408
|
+
case "tokens":
|
|
6409
|
+
extra.maxTokens = Math.min(Math.ceil(payload.limit * 1.5), 5e5);
|
|
6410
|
+
break;
|
|
6411
|
+
case "cost":
|
|
6412
|
+
extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
|
|
6413
|
+
break;
|
|
6414
|
+
}
|
|
6415
|
+
payload.extend(extra);
|
|
6416
|
+
}, Math.min(payload.timeoutMs, 3e4));
|
|
6417
|
+
});
|
|
6005
6418
|
}
|
|
6006
6419
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
6007
6420
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
@@ -6461,65 +6874,68 @@ function createDelegateTool(opts) {
|
|
|
6461
6874
|
if (typeof i.task !== "string" || !i.task.trim()) {
|
|
6462
6875
|
return { ok: false, error: "`task` is required." };
|
|
6463
6876
|
}
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
if (!director) {
|
|
6469
|
-
const reason = opts.host.getPromotionBlockReason?.();
|
|
6470
|
-
return {
|
|
6471
|
-
ok: false,
|
|
6472
|
-
error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
|
|
6473
|
-
};
|
|
6474
|
-
}
|
|
6475
|
-
const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
|
|
6476
|
-
let cfg;
|
|
6477
|
-
if (i.role) {
|
|
6478
|
-
const base = opts.roster?.[i.role];
|
|
6479
|
-
if (!base) {
|
|
6480
|
-
return {
|
|
6481
|
-
ok: false,
|
|
6482
|
-
error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
|
|
6483
|
-
};
|
|
6877
|
+
try {
|
|
6878
|
+
let director = await opts.host.ensureDirector();
|
|
6879
|
+
if (!director) {
|
|
6880
|
+
director = await opts.host.promoteToDirector();
|
|
6484
6881
|
}
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
if (i.provider) cfg.provider = i.provider;
|
|
6488
|
-
if (i.model) cfg.model = i.model;
|
|
6489
|
-
} else {
|
|
6490
|
-
if (!i.name) {
|
|
6882
|
+
if (!director) {
|
|
6883
|
+
const reason = opts.host.getPromotionBlockReason?.();
|
|
6491
6884
|
return {
|
|
6492
6885
|
ok: false,
|
|
6493
|
-
error: "
|
|
6886
|
+
error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
|
|
6494
6887
|
};
|
|
6495
6888
|
}
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6889
|
+
const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
|
|
6890
|
+
let cfg;
|
|
6891
|
+
if (i.role) {
|
|
6892
|
+
const base = opts.roster?.[i.role];
|
|
6893
|
+
if (!base) {
|
|
6894
|
+
return {
|
|
6895
|
+
ok: false,
|
|
6896
|
+
error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
cfg = instantiateRosterConfig2(i.role, base);
|
|
6900
|
+
if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
|
|
6901
|
+
if (i.provider) cfg.provider = i.provider;
|
|
6902
|
+
if (i.model) cfg.model = i.model;
|
|
6903
|
+
} else {
|
|
6904
|
+
if (!i.name) {
|
|
6905
|
+
return {
|
|
6906
|
+
ok: false,
|
|
6907
|
+
error: "Either `role` (from the roster) or `name` is required."
|
|
6908
|
+
};
|
|
6909
|
+
}
|
|
6910
|
+
cfg = {
|
|
6911
|
+
name: i.name,
|
|
6912
|
+
provider: i.provider,
|
|
6913
|
+
model: i.model,
|
|
6914
|
+
systemPromptOverride: i.systemPromptOverride
|
|
6915
|
+
};
|
|
6916
|
+
}
|
|
6917
|
+
if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
|
|
6918
|
+
cfg.maxIterations = i.maxIterations;
|
|
6919
|
+
}
|
|
6920
|
+
if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
|
|
6921
|
+
cfg.maxToolCalls = i.maxToolCalls;
|
|
6922
|
+
}
|
|
6923
|
+
const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
|
|
6924
|
+
const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
|
|
6925
|
+
if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
|
|
6926
|
+
cfg.timeoutMs = desiredSubTimeout;
|
|
6927
|
+
}
|
|
6515
6928
|
const subagentId = await director.spawn(cfg);
|
|
6516
6929
|
const taskId = await director.assign({
|
|
6517
|
-
id:
|
|
6930
|
+
id: `${randomUUID()}`,
|
|
6518
6931
|
description: i.task,
|
|
6519
6932
|
subagentId
|
|
6520
6933
|
});
|
|
6521
6934
|
const result = await Promise.race([
|
|
6522
|
-
director.awaitTasks([taskId]).then((r) =>
|
|
6935
|
+
director.awaitTasks([taskId]).then((r) => {
|
|
6936
|
+
if (!r[0]) throw new Error(`Task "${taskId}" not found in completed results`);
|
|
6937
|
+
return r[0];
|
|
6938
|
+
}),
|
|
6523
6939
|
new Promise(
|
|
6524
6940
|
(resolve2) => setTimeout(() => resolve2({ __timeout: true }), timeoutMs)
|
|
6525
6941
|
)
|
|
@@ -6593,7 +7009,7 @@ function hintForKind(kind, retryable, backoffMs) {
|
|
|
6593
7009
|
case "budget_tool_calls":
|
|
6594
7010
|
case "budget_tokens":
|
|
6595
7011
|
case "budget_cost":
|
|
6596
|
-
return "Subagent exhausted its budget.
|
|
7012
|
+
return "Subagent exhausted its budget. The coordinator may auto-extend; otherwise raise the matching `max*` field (e.g. maxToolCalls: 600) on the next delegate, or split the task.";
|
|
6597
7013
|
case "budget_timeout":
|
|
6598
7014
|
return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
|
|
6599
7015
|
case "aborted_by_parent":
|
|
@@ -6666,8 +7082,21 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6666
7082
|
const { agent, events } = factoryResult;
|
|
6667
7083
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
6668
7084
|
const aborter = new AbortController();
|
|
7085
|
+
ctx.budget._events = events;
|
|
6669
7086
|
let budgetError = null;
|
|
6670
7087
|
const onBudgetError = (err) => {
|
|
7088
|
+
if (err instanceof BudgetThresholdSignal) {
|
|
7089
|
+
err.decision.then((decision) => {
|
|
7090
|
+
if (decision === "stop") {
|
|
7091
|
+
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
7092
|
+
aborter.abort();
|
|
7093
|
+
}
|
|
7094
|
+
}).catch(() => {
|
|
7095
|
+
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
7096
|
+
aborter.abort();
|
|
7097
|
+
});
|
|
7098
|
+
return;
|
|
7099
|
+
}
|
|
6671
7100
|
aborter.abort();
|
|
6672
7101
|
budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
|
|
6673
7102
|
"tool_calls",
|
|
@@ -6697,7 +7126,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6697
7126
|
try {
|
|
6698
7127
|
ctx.budget.recordUsage(e.usage);
|
|
6699
7128
|
} catch (e2) {
|
|
6700
|
-
onBudgetError(e2);
|
|
7129
|
+
void onBudgetError(e2);
|
|
6701
7130
|
}
|
|
6702
7131
|
}),
|
|
6703
7132
|
events.on("iteration.started", () => {
|
|
@@ -6705,7 +7134,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6705
7134
|
ctx.budget.recordIteration();
|
|
6706
7135
|
ctx.budget.checkTimeout();
|
|
6707
7136
|
} catch (e) {
|
|
6708
|
-
onBudgetError(e);
|
|
7137
|
+
void onBudgetError(e);
|
|
6709
7138
|
}
|
|
6710
7139
|
}),
|
|
6711
7140
|
// D3: cooperative timeout enforcement DURING a long tool call.
|
|
@@ -6725,7 +7154,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6725
7154
|
try {
|
|
6726
7155
|
ctx.budget.checkTimeout();
|
|
6727
7156
|
} catch (e) {
|
|
6728
|
-
onBudgetError(e);
|
|
7157
|
+
void onBudgetError(e);
|
|
6729
7158
|
}
|
|
6730
7159
|
})
|
|
6731
7160
|
);
|
|
@@ -6799,180 +7228,6 @@ function makeDirectorSessionFactory(opts) {
|
|
|
6799
7228
|
}
|
|
6800
7229
|
};
|
|
6801
7230
|
}
|
|
6802
|
-
|
|
6803
|
-
// src/coordination/fleet.ts
|
|
6804
|
-
var AUDIT_LOG_AGENT = {
|
|
6805
|
-
id: "audit-log",
|
|
6806
|
-
name: "Audit Log",
|
|
6807
|
-
role: "audit-log",
|
|
6808
|
-
prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
|
|
6809
|
-
session logs and produce actionable markdown reports.
|
|
6810
|
-
|
|
6811
|
-
Scope:
|
|
6812
|
-
- Parse session logs (iteration counts, tool calls, errors, usage)
|
|
6813
|
-
- Detect repeated failure patterns across multiple runs
|
|
6814
|
-
- Identify tool usage anomalies (over-use, failures, unexpected chains)
|
|
6815
|
-
- Track token consumption trends
|
|
6816
|
-
- Generate structured audit reports with severity ratings
|
|
6817
|
-
|
|
6818
|
-
Input format you accept:
|
|
6819
|
-
{ "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
|
|
6820
|
-
|
|
6821
|
-
Output: Markdown audit report with sections:
|
|
6822
|
-
- ## Summary (totals, error rate)
|
|
6823
|
-
- ## Top Errors (count + context)
|
|
6824
|
-
- ## Tool Usage (table with calls, failures, avg duration)
|
|
6825
|
-
- ## Anomalies (pattern \u2192 severity)
|
|
6826
|
-
|
|
6827
|
-
Working rules:
|
|
6828
|
-
- Never fabricate numbers \u2014 read the actual logs first
|
|
6829
|
-
- Always include file:line references for errors
|
|
6830
|
-
- If sessionPath is missing, ask the director to provide it
|
|
6831
|
-
- Report confidence level: high (>90% accuracy), medium, low`
|
|
6832
|
-
// No hardcoded budgets — the orchestrator (delegate tool or
|
|
6833
|
-
// spawn_subagent) decides per-task how much room a subagent gets.
|
|
6834
|
-
// A monorepo audit needs hours; a single-file lint check needs
|
|
6835
|
-
// seconds. Pinning a number here forces the orchestrator to fight
|
|
6836
|
-
// the role's default instead of just asking for what it needs.
|
|
6837
|
-
};
|
|
6838
|
-
var BUG_HUNTER_AGENT = {
|
|
6839
|
-
id: "bug-hunter",
|
|
6840
|
-
name: "Bug Hunter",
|
|
6841
|
-
role: "bug-hunter",
|
|
6842
|
-
prompt: `You are the Bug Hunter agent. Your job is to systematically scan
|
|
6843
|
-
source code for bugs, anti-patterns, and code smells using pattern matching
|
|
6844
|
-
and heuristics. Output a prioritized hit list with file:line references.
|
|
6845
|
-
|
|
6846
|
-
Scope:
|
|
6847
|
-
- Detect common bug patterns (uncaught errors, resource leaks, race conditions)
|
|
6848
|
-
- Identify anti-patterns (callback hell, God objects, circular deps)
|
|
6849
|
-
- Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
|
|
6850
|
-
- Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
|
|
6851
|
-
- Rank findings: critical > high > medium > low
|
|
6852
|
-
|
|
6853
|
-
Input format you accept:
|
|
6854
|
-
{ "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
|
|
6855
|
-
|
|
6856
|
-
Output: Markdown bug hunt report:
|
|
6857
|
-
- ## Critical (must fix first)
|
|
6858
|
-
- ## High (should fix)
|
|
6859
|
-
- ## Medium
|
|
6860
|
-
- ## Low (consider)
|
|
6861
|
-
Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
|
|
6862
|
-
|
|
6863
|
-
Bug pattern reference you know:
|
|
6864
|
-
| Pattern | Regex hint | Severity |
|
|
6865
|
-
|---------|------------|----------|
|
|
6866
|
-
| Uncaught promise | /.then\\(.*\\)/ without catch | high |
|
|
6867
|
-
| Event leak | on\\( without off/removeListener | high |
|
|
6868
|
-
| Hardcoded secret | [a-zA-Z0-9/_-]{20,} in config files | critical |
|
|
6869
|
-
| unsafe any | : any\\b or <any> | medium |
|
|
6870
|
-
| innerHTML | innerHTML\\s*= | high |
|
|
6871
|
-
|
|
6872
|
-
Working rules:
|
|
6873
|
-
- Never scan node_modules \u2014 it's noise
|
|
6874
|
-
- Always include file:line for every finding
|
|
6875
|
-
- If >30% of findings are false positives, note the confidence level
|
|
6876
|
-
- Ask director for clarification if paths are ambiguous`
|
|
6877
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
6878
|
-
};
|
|
6879
|
-
var REFACTOR_PLANNER_AGENT = {
|
|
6880
|
-
id: "refactor-planner",
|
|
6881
|
-
name: "Refactor Planner",
|
|
6882
|
-
role: "refactor-planner",
|
|
6883
|
-
prompt: `You are the Refactor Planner agent. Your job is to analyze code
|
|
6884
|
-
structure and produce a concrete, phased refactoring plan with risk
|
|
6885
|
-
assessment, dependency ordering, and rollback strategy.
|
|
6886
|
-
|
|
6887
|
-
Scope:
|
|
6888
|
-
- Map module-level dependencies (import graph)
|
|
6889
|
-
- Identify coupling hotspots (high fan-in/out modules)
|
|
6890
|
-
- Assess refactoring risk by complexity and test coverage
|
|
6891
|
-
- Generate phased plans with checkpoint milestones
|
|
6892
|
-
- Produce diff-friendly task lists (one task = one concern)
|
|
6893
|
-
|
|
6894
|
-
Input format you accept:
|
|
6895
|
-
{ "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
|
|
6896
|
-
|
|
6897
|
-
Output: Markdown refactor plan:
|
|
6898
|
-
- ## Phase 1: Low Risk / High Payoff (do first)
|
|
6899
|
-
Table: | # | Task | Module | Risk | Est. Time |
|
|
6900
|
-
- ## Phase 2: Medium Risk
|
|
6901
|
-
- ## Phase 3: High Risk (requires full regression)
|
|
6902
|
-
- ## Dependency Graph (abbreviated ASCII)
|
|
6903
|
-
- ## Rollback Strategy
|
|
6904
|
-
- ## Exit Criteria (checkbox list)
|
|
6905
|
-
|
|
6906
|
-
Risk scoring criteria:
|
|
6907
|
-
| Factor | Low | Medium | High |
|
|
6908
|
-
|--------|-----|--------|------|
|
|
6909
|
-
| Cyclomatic complexity | <10 | 10-20 | >20 |
|
|
6910
|
-
| Test coverage | >80% | 50-80% | <50% |
|
|
6911
|
-
| Fan-out (imports) | <5 | 5-15 | >15 |
|
|
6912
|
-
|
|
6913
|
-
Working rules:
|
|
6914
|
-
- Always include rollback strategy \u2014 every refactor can fail
|
|
6915
|
-
- Merge tasks that take <1h into a single phase
|
|
6916
|
-
- Respect team constraints (reviewer availability, parallelization)
|
|
6917
|
-
- Never plan without analyzing the actual code first`
|
|
6918
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
6919
|
-
};
|
|
6920
|
-
var SECURITY_SCANNER_AGENT = {
|
|
6921
|
-
id: "security-scanner",
|
|
6922
|
-
name: "Security Scanner",
|
|
6923
|
-
role: "security-scanner",
|
|
6924
|
-
prompt: `You are the Security Scanner agent. Your job is to scan code,
|
|
6925
|
-
configs, and dependencies for security issues from hardcoded secrets to
|
|
6926
|
-
supply chain risks.
|
|
6927
|
-
|
|
6928
|
-
Scope:
|
|
6929
|
-
- Detect hardcoded secrets: API keys, tokens, passwords, private keys
|
|
6930
|
-
- Find injection vectors: eval, innerHTML, SQL concat, shell injection
|
|
6931
|
-
- Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
|
|
6932
|
-
- Scan dependencies for known CVEs (via npm/pnpm audit)
|
|
6933
|
-
- Flag supply chain risks: postinstall hooks, unverified scripts, .npmrc
|
|
6934
|
-
|
|
6935
|
-
Input format you accept:
|
|
6936
|
-
{ "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
|
|
6937
|
-
|
|
6938
|
-
Output: Markdown security report:
|
|
6939
|
-
- ## CRITICAL: Secrets Found (with code snippets)
|
|
6940
|
-
- ## HIGH: Injection Vectors
|
|
6941
|
-
- ## MEDIUM: Insecure Patterns
|
|
6942
|
-
- ## Dependency Issues (CVE list)
|
|
6943
|
-
- ## Summary table (severity \u2192 count)
|
|
6944
|
-
- ## Remediation Checklist (with checkboxes)
|
|
6945
|
-
|
|
6946
|
-
Secret patterns you detect:
|
|
6947
|
-
| Pattern | Example | Severity |
|
|
6948
|
-
|---------|---------|----------|
|
|
6949
|
-
| AWS Access Key | AKIAIOSFODNN7EXAMPLE | critical |
|
|
6950
|
-
| AWS Secret Key | [a-zA-Z0-9/+=]{40} base64 | critical |
|
|
6951
|
-
| GitHub Token | ghp_[a-zA-Z0-9]{36} | critical |
|
|
6952
|
-
| Private Key PEM | -----BEGIN.*PRIVATE KEY----- | critical |
|
|
6953
|
-
| JWT | eyJ[a-zA-Z0-9_-]+ | high |
|
|
6954
|
-
|
|
6955
|
-
Injection patterns:
|
|
6956
|
-
| Construct | Safe alternative |
|
|
6957
|
-
|-----------|-----------------|
|
|
6958
|
-
| eval(str) | new Function() or parse |
|
|
6959
|
-
| innerHTML = x | textContent or sanitize |
|
|
6960
|
-
| exec(\`cmd \${x}\`) | execFile with args array |
|
|
6961
|
-
|
|
6962
|
-
Working rules:
|
|
6963
|
-
- Never scan node_modules \u2014 use npm audit instead
|
|
6964
|
-
- Always provide remediation steps, not just findings
|
|
6965
|
-
- Verify regex-based secrets before flagging (false positive risk)
|
|
6966
|
-
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
6967
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
6968
|
-
};
|
|
6969
|
-
var FLEET_ROSTER = {
|
|
6970
|
-
"audit-log": AUDIT_LOG_AGENT,
|
|
6971
|
-
"bug-hunter": BUG_HUNTER_AGENT,
|
|
6972
|
-
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
6973
|
-
"security-scanner": SECURITY_SCANNER_AGENT
|
|
6974
|
-
};
|
|
6975
|
-
var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
|
|
6976
7231
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
6977
7232
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
6978
7233
|
var FAMILY_BY_NPM = {
|
|
@@ -7273,6 +7528,112 @@ When refactoring code:
|
|
|
7273
7528
|
- Keep performance in mind \u2014 don't regress`,
|
|
7274
7529
|
tags: ["refactor", "modernization", "improvement"],
|
|
7275
7530
|
toolPreferences: ["read", "edit", "test", "git", "grep"]
|
|
7531
|
+
},
|
|
7532
|
+
{
|
|
7533
|
+
id: "brief",
|
|
7534
|
+
name: "Brief",
|
|
7535
|
+
description: "Fast, no-nonsense \u2014 get to the point",
|
|
7536
|
+
prompt: `## Brief Mode
|
|
7537
|
+
|
|
7538
|
+
You are WrongStack, a fast, no-nonsense AI coding agent.
|
|
7539
|
+
|
|
7540
|
+
You operate inside the user's terminal. Read files, run commands, make changes \u2014 get to the point.
|
|
7541
|
+
|
|
7542
|
+
### Operating rules
|
|
7543
|
+
|
|
7544
|
+
1. **Read first.** Inspect relevant files before touching anything.
|
|
7545
|
+
2. **Edit surgically.** Use edit tool for existing files, write only for new ones.
|
|
7546
|
+
3. **One sentence before action.** State what you're doing, then do it. No preambles.
|
|
7547
|
+
4. **Say what happened.** After tool calls, one line: success, failure, or what's next.
|
|
7548
|
+
5. **Be honest.** Admit when you don't know or something failed. No fake progress.
|
|
7549
|
+
6. **Keep moving.** Task done? Stop. More work needed? State it and continue.
|
|
7550
|
+
|
|
7551
|
+
### Decision rules
|
|
7552
|
+
|
|
7553
|
+
- **Ambiguous task?** Ask. One question, get clarity, proceed.
|
|
7554
|
+
- **Clear task, unknown approach?** Pick one reasonable path, execute, report.
|
|
7555
|
+
- **Tool fails?** Retry once with adjusted params, then report.
|
|
7556
|
+
- **Permission denied?** Stop. Acknowledge. Ask what they want instead.
|
|
7557
|
+
- **Context filling up?** Compact proactively, don't wait.
|
|
7558
|
+
|
|
7559
|
+
### Output style
|
|
7560
|
+
|
|
7561
|
+
- Prose paragraphs (no bullet points unless unavoidable)
|
|
7562
|
+
- Code blocks for code, backticks for paths/commands
|
|
7563
|
+
- One-liner sufficient? One liner.
|
|
7564
|
+
- No "Great question!", "Here's what I did:", or similar filler.
|
|
7565
|
+
- Max 3 sentences per paragraph.
|
|
7566
|
+
|
|
7567
|
+
### Focus
|
|
7568
|
+
|
|
7569
|
+
Stay on task. Fix only what's asked. Don't refactor surrounding code unless explicitly requested. Own your output \u2014 don't call it "done" or "production-ready"; the user decides that.`,
|
|
7570
|
+
tags: ["fast", "concise", "direct"],
|
|
7571
|
+
toolPreferences: ["read", "edit", "bash"]
|
|
7572
|
+
},
|
|
7573
|
+
{
|
|
7574
|
+
id: "teach",
|
|
7575
|
+
name: "Teach",
|
|
7576
|
+
description: "Mentor mode \u2014 explains why, not just what",
|
|
7577
|
+
prompt: `## Teach Mode
|
|
7578
|
+
|
|
7579
|
+
You are WrongStack, an expert AI coding mentor.
|
|
7580
|
+
|
|
7581
|
+
You operate inside the user's terminal with full access to their codebase. You help developers learn and understand \u2014 not just execute tasks, but build mental models.
|
|
7582
|
+
|
|
7583
|
+
### Teaching philosophy
|
|
7584
|
+
|
|
7585
|
+
1. **Explain the why.** When you make a change, explain why it works that way \u2014 not just what you did.
|
|
7586
|
+
2. **Build mental models.** Use analogies, highlight patterns, connect new concepts to things the user already knows.
|
|
7587
|
+
3. **Read before teaching.** Always inspect relevant files so your explanations are accurate and specific to the actual code.
|
|
7588
|
+
4. **Surgical edits with context.** When editing code, explain the approach before doing it, and what trade-offs were considered.
|
|
7589
|
+
5. **Be thorough but not verbose.** A 2-paragraph explanation beats a 5-paragraph one. Depth without padding.
|
|
7590
|
+
6. **Admit knowledge gaps.** If you're unsure, say so. Speculating teaches bad patterns.
|
|
7591
|
+
|
|
7592
|
+
### Teaching style
|
|
7593
|
+
|
|
7594
|
+
- **Before action:** Briefly explain what you're going to do and why.
|
|
7595
|
+
- **After action:** Summarize what happened and what the user should take away from this.
|
|
7596
|
+
- **With code:** Show concrete examples, explain syntax choices, point out gotchas.
|
|
7597
|
+
- **With errors:** Explain why the error occurred, what it's actually complaining about, and how to avoid it in the future.
|
|
7598
|
+
- **General principles:** Offer them when the user's question suggests a deeper concept they'd benefit from understanding.
|
|
7599
|
+
|
|
7600
|
+
### Decision heuristics
|
|
7601
|
+
|
|
7602
|
+
- **Task is ambiguous?** Ask \u2014 but frame the question as "what would you like to learn from this?"
|
|
7603
|
+
- **Task is clear, approach is unknown?** Execute, then teach the approach as you go.
|
|
7604
|
+
- **Tool fails?** Explain what failed, why it failed, and how to avoid the failure.
|
|
7605
|
+
- **User asks "how do I...?"** Don't just give the answer \u2014 explain the underlying mechanism.
|
|
7606
|
+
- **Context window filling up?** Compact, but summarize what was lost so the teaching continuity isn't broken.
|
|
7607
|
+
|
|
7608
|
+
### Output format
|
|
7609
|
+
|
|
7610
|
+
- Use headings to structure multi-concept explanations.
|
|
7611
|
+
- Code blocks with brief annotations for code examples.
|
|
7612
|
+
- **Bold** key terms and concepts worth remembering.
|
|
7613
|
+
- Callouts like "Key takeaway:" or "Pattern:" to anchor learning.
|
|
7614
|
+
- Max 3 sentences per paragraph \u2014 readability over completeness.
|
|
7615
|
+
|
|
7616
|
+
### Don'ts
|
|
7617
|
+
|
|
7618
|
+
- Don't lecture condescendingly \u2014 the user is a developer, not a beginner.
|
|
7619
|
+
- Don't pad explanations with obvious things.
|
|
7620
|
+
- Don't skip the "why" \u2014 even quick tasks deserve one sentence of context.
|
|
7621
|
+
- Don't just say "do X" \u2014 say "do X because Y."
|
|
7622
|
+
- Don't leave the user hanging after a complex operation \u2014 explain what just happened.
|
|
7623
|
+
|
|
7624
|
+
### Core principles
|
|
7625
|
+
|
|
7626
|
+
You follow these principles, but always with explanation:
|
|
7627
|
+
- Read before write
|
|
7628
|
+
- Surgical edits over rewrites
|
|
7629
|
+
- Show your work (explain your reasoning, not just mechanical steps)
|
|
7630
|
+
- Be honest about limits
|
|
7631
|
+
- Format for scanability
|
|
7632
|
+
- Recover explicitly from failures
|
|
7633
|
+
|
|
7634
|
+
Remember: your job is to make the user a better developer, not just to complete tasks faster.`,
|
|
7635
|
+
tags: ["teaching", "mentor", "learning"],
|
|
7636
|
+
toolPreferences: ["read", "edit", "explain"]
|
|
7276
7637
|
}
|
|
7277
7638
|
];
|
|
7278
7639
|
|
|
@@ -7843,6 +8204,27 @@ function computeTaskProgress(graph) {
|
|
|
7843
8204
|
actualHours
|
|
7844
8205
|
};
|
|
7845
8206
|
}
|
|
8207
|
+
function topologicalSort(graph) {
|
|
8208
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8209
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
8210
|
+
const result = [];
|
|
8211
|
+
function visit(id) {
|
|
8212
|
+
if (inStack.has(id)) return;
|
|
8213
|
+
if (visited.has(id)) return;
|
|
8214
|
+
if (!graph.nodes.has(id)) return;
|
|
8215
|
+
visited.add(id);
|
|
8216
|
+
inStack.add(id);
|
|
8217
|
+
for (const edge of graph.edges) {
|
|
8218
|
+
if (edge.from === id) visit(edge.to);
|
|
8219
|
+
}
|
|
8220
|
+
inStack.delete(id);
|
|
8221
|
+
result.push(id);
|
|
8222
|
+
}
|
|
8223
|
+
for (const rootId of graph.rootNodes) {
|
|
8224
|
+
visit(rootId);
|
|
8225
|
+
}
|
|
8226
|
+
return result;
|
|
8227
|
+
}
|
|
7846
8228
|
|
|
7847
8229
|
// src/sdd/task-tracker.ts
|
|
7848
8230
|
var TaskTracker = class {
|
|
@@ -8241,39 +8623,1527 @@ var SpecDrivenDev = class {
|
|
|
8241
8623
|
}));
|
|
8242
8624
|
}
|
|
8243
8625
|
};
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
8251
|
-
}
|
|
8252
|
-
function quantile(sorted, q) {
|
|
8253
|
-
if (sorted.length === 0) return 0;
|
|
8254
|
-
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
8255
|
-
return sorted[idx] ?? 0;
|
|
8256
|
-
}
|
|
8257
|
-
var InMemoryMetricsSink = class {
|
|
8258
|
-
counters = /* @__PURE__ */ new Map();
|
|
8259
|
-
gauges = /* @__PURE__ */ new Map();
|
|
8260
|
-
histograms = /* @__PURE__ */ new Map();
|
|
8261
|
-
counter(name, value = 1, labels) {
|
|
8262
|
-
const series = this.getOrCreate(this.counters, name);
|
|
8263
|
-
const key = labelKey(labels);
|
|
8264
|
-
const state = series.get(key) ?? { value: 0 };
|
|
8265
|
-
state.value += value;
|
|
8266
|
-
series.set(key, state);
|
|
8626
|
+
var SpecStore = class {
|
|
8627
|
+
baseDir;
|
|
8628
|
+
indexPath;
|
|
8629
|
+
constructor(opts) {
|
|
8630
|
+
this.baseDir = opts.baseDir;
|
|
8631
|
+
this.indexPath = path3.join(this.baseDir, "_index.json");
|
|
8267
8632
|
}
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8633
|
+
async save(spec) {
|
|
8634
|
+
await ensureDir(this.baseDir);
|
|
8635
|
+
const filePath = this.filePath(spec.id);
|
|
8636
|
+
await atomicWrite(filePath, JSON.stringify(spec, null, 2), { mode: 384 });
|
|
8637
|
+
await this.updateIndex(spec);
|
|
8271
8638
|
}
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8639
|
+
async load(id) {
|
|
8640
|
+
try {
|
|
8641
|
+
const raw = await fsp.readFile(this.filePath(id), "utf8");
|
|
8642
|
+
return JSON.parse(raw);
|
|
8643
|
+
} catch {
|
|
8644
|
+
return null;
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
async list() {
|
|
8648
|
+
const index = await this.readIndex();
|
|
8649
|
+
return index.entries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
8650
|
+
}
|
|
8651
|
+
async delete(id) {
|
|
8652
|
+
try {
|
|
8653
|
+
await fsp.unlink(this.filePath(id));
|
|
8654
|
+
await this.removeFromIndex(id);
|
|
8655
|
+
return true;
|
|
8656
|
+
} catch {
|
|
8657
|
+
return false;
|
|
8658
|
+
}
|
|
8659
|
+
}
|
|
8660
|
+
async exists(id) {
|
|
8661
|
+
try {
|
|
8662
|
+
await fsp.access(this.filePath(id));
|
|
8663
|
+
return true;
|
|
8664
|
+
} catch {
|
|
8665
|
+
return false;
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8668
|
+
/** Create a new spec with defaults, assign ID, and persist. */
|
|
8669
|
+
async createDraft(title, overview) {
|
|
8670
|
+
const now = Date.now();
|
|
8671
|
+
const spec = {
|
|
8672
|
+
id: randomUUID(),
|
|
8673
|
+
title,
|
|
8674
|
+
version: "0.1.0",
|
|
8675
|
+
status: "draft",
|
|
8676
|
+
overview: overview ?? "",
|
|
8677
|
+
sections: [],
|
|
8678
|
+
requirements: [],
|
|
8679
|
+
createdAt: now,
|
|
8680
|
+
updatedAt: now
|
|
8681
|
+
};
|
|
8682
|
+
await this.save(spec);
|
|
8683
|
+
return spec;
|
|
8684
|
+
}
|
|
8685
|
+
/** Update spec fields and persist. */
|
|
8686
|
+
async update(id, patch) {
|
|
8687
|
+
const spec = await this.load(id);
|
|
8688
|
+
if (!spec) return null;
|
|
8689
|
+
const updated = {
|
|
8690
|
+
...spec,
|
|
8691
|
+
...patch,
|
|
8692
|
+
id: spec.id,
|
|
8693
|
+
createdAt: spec.createdAt,
|
|
8694
|
+
updatedAt: Date.now()
|
|
8695
|
+
};
|
|
8696
|
+
await this.save(updated);
|
|
8697
|
+
return updated;
|
|
8698
|
+
}
|
|
8699
|
+
filePath(id) {
|
|
8700
|
+
return path3.join(this.baseDir, `${id}.json`);
|
|
8701
|
+
}
|
|
8702
|
+
async readIndex() {
|
|
8703
|
+
try {
|
|
8704
|
+
const raw = await fsp.readFile(this.indexPath, "utf8");
|
|
8705
|
+
const parsed = JSON.parse(raw);
|
|
8706
|
+
if (parsed?.version === 1) return parsed;
|
|
8707
|
+
} catch {
|
|
8708
|
+
}
|
|
8709
|
+
return { version: 1, entries: [] };
|
|
8710
|
+
}
|
|
8711
|
+
async updateIndex(spec) {
|
|
8712
|
+
const index = await this.readIndex();
|
|
8713
|
+
const entry = {
|
|
8714
|
+
id: spec.id,
|
|
8715
|
+
title: spec.title,
|
|
8716
|
+
version: spec.version,
|
|
8717
|
+
status: spec.status,
|
|
8718
|
+
updatedAt: spec.updatedAt,
|
|
8719
|
+
filePath: this.filePath(spec.id)
|
|
8720
|
+
};
|
|
8721
|
+
const idx = index.entries.findIndex((e) => e.id === spec.id);
|
|
8722
|
+
if (idx >= 0) {
|
|
8723
|
+
index.entries[idx] = entry;
|
|
8724
|
+
} else {
|
|
8725
|
+
index.entries.push(entry);
|
|
8726
|
+
}
|
|
8727
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
8728
|
+
}
|
|
8729
|
+
async removeFromIndex(id) {
|
|
8730
|
+
const index = await this.readIndex();
|
|
8731
|
+
index.entries = index.entries.filter((e) => e.id !== id);
|
|
8732
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
8733
|
+
}
|
|
8734
|
+
};
|
|
8735
|
+
function graphToJSON(graph) {
|
|
8736
|
+
const serialisable = {
|
|
8737
|
+
...graph,
|
|
8738
|
+
nodes: Array.from(graph.nodes.entries())
|
|
8739
|
+
};
|
|
8740
|
+
return JSON.stringify(serialisable, null, 2);
|
|
8741
|
+
}
|
|
8742
|
+
function graphFromJSON(raw) {
|
|
8743
|
+
const parsed = JSON.parse(raw);
|
|
8744
|
+
return {
|
|
8745
|
+
...parsed,
|
|
8746
|
+
nodes: new Map(parsed.nodes)
|
|
8747
|
+
};
|
|
8748
|
+
}
|
|
8749
|
+
var TaskGraphStore = class {
|
|
8750
|
+
baseDir;
|
|
8751
|
+
indexPath;
|
|
8752
|
+
constructor(opts) {
|
|
8753
|
+
this.baseDir = opts.baseDir;
|
|
8754
|
+
this.indexPath = path3.join(this.baseDir, "_index.json");
|
|
8755
|
+
}
|
|
8756
|
+
async save(graph) {
|
|
8757
|
+
await ensureDir(this.baseDir);
|
|
8758
|
+
const filePath = this.filePath(graph.id);
|
|
8759
|
+
await atomicWrite(filePath, graphToJSON(graph), { mode: 384 });
|
|
8760
|
+
await this.updateIndex(graph);
|
|
8761
|
+
}
|
|
8762
|
+
async load(id) {
|
|
8763
|
+
try {
|
|
8764
|
+
const raw = await fsp.readFile(this.filePath(id), "utf8");
|
|
8765
|
+
return graphFromJSON(raw);
|
|
8766
|
+
} catch {
|
|
8767
|
+
return null;
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8770
|
+
async list() {
|
|
8771
|
+
const index = await this.readIndex();
|
|
8772
|
+
return index.entries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
8773
|
+
}
|
|
8774
|
+
async delete(id) {
|
|
8775
|
+
try {
|
|
8776
|
+
await fsp.unlink(this.filePath(id));
|
|
8777
|
+
await this.removeFromIndex(id);
|
|
8778
|
+
return true;
|
|
8779
|
+
} catch {
|
|
8780
|
+
return false;
|
|
8781
|
+
}
|
|
8782
|
+
}
|
|
8783
|
+
async exists(id) {
|
|
8784
|
+
try {
|
|
8785
|
+
await fsp.access(this.filePath(id));
|
|
8786
|
+
return true;
|
|
8787
|
+
} catch {
|
|
8788
|
+
return false;
|
|
8789
|
+
}
|
|
8790
|
+
}
|
|
8791
|
+
filePath(id) {
|
|
8792
|
+
return path3.join(this.baseDir, `${id}.json`);
|
|
8793
|
+
}
|
|
8794
|
+
async readIndex() {
|
|
8795
|
+
try {
|
|
8796
|
+
const raw = await fsp.readFile(this.indexPath, "utf8");
|
|
8797
|
+
const parsed = JSON.parse(raw);
|
|
8798
|
+
if (parsed?.version === 1) return parsed;
|
|
8799
|
+
} catch {
|
|
8800
|
+
}
|
|
8801
|
+
return { version: 1, entries: [] };
|
|
8802
|
+
}
|
|
8803
|
+
async updateIndex(graph) {
|
|
8804
|
+
const index = await this.readIndex();
|
|
8805
|
+
const completedCount = Array.from(graph.nodes.values()).filter(
|
|
8806
|
+
(n) => n.status === "completed"
|
|
8807
|
+
).length;
|
|
8808
|
+
const entry = {
|
|
8809
|
+
id: graph.id,
|
|
8810
|
+
specId: graph.specId,
|
|
8811
|
+
title: graph.title,
|
|
8812
|
+
nodeCount: graph.nodes.size,
|
|
8813
|
+
completedCount,
|
|
8814
|
+
updatedAt: graph.updatedAt,
|
|
8815
|
+
filePath: this.filePath(graph.id)
|
|
8816
|
+
};
|
|
8817
|
+
const idx = index.entries.findIndex((e) => e.id === graph.id);
|
|
8818
|
+
if (idx >= 0) {
|
|
8819
|
+
index.entries[idx] = entry;
|
|
8820
|
+
} else {
|
|
8821
|
+
index.entries.push(entry);
|
|
8822
|
+
}
|
|
8823
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
8824
|
+
}
|
|
8825
|
+
async removeFromIndex(id) {
|
|
8826
|
+
const index = await this.readIndex();
|
|
8827
|
+
index.entries = index.entries.filter((e) => e.id !== id);
|
|
8828
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
8829
|
+
}
|
|
8830
|
+
};
|
|
8831
|
+
|
|
8832
|
+
// src/sdd/spec-builder.ts
|
|
8833
|
+
function buildQuestioningPrompt(session, min, max) {
|
|
8834
|
+
const answered = session.answers.length;
|
|
8835
|
+
const remaining = Math.max(0, min - answered);
|
|
8836
|
+
const budget = max - answered;
|
|
8837
|
+
const lines = [
|
|
8838
|
+
`\u2550\u2550\u2550 SDD Spec Builder \u2550\u2550\u2550`,
|
|
8839
|
+
`Feature: "${session.title}"`,
|
|
8840
|
+
session.userIntent ? `Intent: ${session.userIntent}` : "",
|
|
8841
|
+
`Phase: Questioning (${answered} answered, ${budget} remaining budget)`,
|
|
8842
|
+
"",
|
|
8843
|
+
"**Instructions for AI:**",
|
|
8844
|
+
"",
|
|
8845
|
+
"You are conducting a specification interview. Your job is to ask the user",
|
|
8846
|
+
"intelligent, contextual questions to understand what they want to build.",
|
|
8847
|
+
"",
|
|
8848
|
+
`You have asked ${answered} questions so far.`
|
|
8849
|
+
];
|
|
8850
|
+
if (remaining > 0) {
|
|
8851
|
+
lines.push(`You MUST ask at least ${remaining} more question(s) before generating the spec.`);
|
|
8852
|
+
} else if (budget <= 0) {
|
|
8853
|
+
lines.push("You have reached the maximum question budget. Generate the spec NOW.");
|
|
8854
|
+
} else {
|
|
8855
|
+
lines.push(
|
|
8856
|
+
"You may ask more questions if needed, or generate the spec if you have enough information.",
|
|
8857
|
+
"Ask a question ONLY if it reveals something you genuinely need to know."
|
|
8858
|
+
);
|
|
8859
|
+
}
|
|
8860
|
+
lines.push(
|
|
8861
|
+
"",
|
|
8862
|
+
"**Rules:**",
|
|
8863
|
+
"- Ask ONE question at a time",
|
|
8864
|
+
"- Questions must be specific and contextual \u2014 never generic",
|
|
8865
|
+
"- Adapt based on previous answers",
|
|
8866
|
+
"- Cover: scope, constraints, edge cases, integrations, security, performance as relevant",
|
|
8867
|
+
"- When you have enough info, respond with the full specification in JSON format",
|
|
8868
|
+
"",
|
|
8869
|
+
`**Question budget:** ${budget}/${max} remaining`,
|
|
8870
|
+
`**Minimum required:** ${remaining > 0 ? remaining : "met"}`
|
|
8871
|
+
);
|
|
8872
|
+
if (session.projectContext) {
|
|
8873
|
+
lines.push("", "**Project Context:**", "```", session.projectContext, "```");
|
|
8874
|
+
}
|
|
8875
|
+
if (answered > 0) {
|
|
8876
|
+
lines.push("", "**Conversation so far:**");
|
|
8877
|
+
for (let i = 0; i < answered; i++) {
|
|
8878
|
+
const a = session.answers[i];
|
|
8879
|
+
lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
lines.push(
|
|
8883
|
+
"",
|
|
8884
|
+
"---",
|
|
8885
|
+
"Now either:",
|
|
8886
|
+
`1. Ask your next question (if you need more info)`,
|
|
8887
|
+
`2. Generate the complete specification as JSON (if ready)`,
|
|
8888
|
+
"",
|
|
8889
|
+
"If generating spec, output JSON inside ```json code block with this structure:",
|
|
8890
|
+
"```json",
|
|
8891
|
+
"{",
|
|
8892
|
+
' "title": "...",',
|
|
8893
|
+
' "overview": "...",',
|
|
8894
|
+
' "sections": [{ "type": "overview|requirements|architecture|api|data|security|acceptance", "title": "...", "content": "...", "level": 1 }],',
|
|
8895
|
+
' "requirements": [{ "id": "REQ-1", "type": "functional|non-functional|security|performance|ux", "priority": "critical|high|medium|low", "description": "...", "acceptanceCriteria": ["..."] }]',
|
|
8896
|
+
"}",
|
|
8897
|
+
"```"
|
|
8898
|
+
);
|
|
8899
|
+
return lines.filter(Boolean).join("\n");
|
|
8900
|
+
}
|
|
8901
|
+
function buildSpecReviewPrompt(session) {
|
|
8902
|
+
const spec = session.spec;
|
|
8903
|
+
if (!spec) return "No spec generated yet.";
|
|
8904
|
+
const reqSummary = spec.requirements.map((r) => ` [${r.priority}] ${r.description}`).join("\n");
|
|
8905
|
+
return [
|
|
8906
|
+
`\u2550\u2550\u2550 Spec Review \u2550\u2550\u2550`,
|
|
8907
|
+
`Feature: "${spec.title}"`,
|
|
8908
|
+
`Requirements: ${spec.requirements.length}`,
|
|
8909
|
+
"",
|
|
8910
|
+
"**Specification:**",
|
|
8911
|
+
spec.overview,
|
|
8912
|
+
"",
|
|
8913
|
+
"**Requirements:**",
|
|
8914
|
+
reqSummary,
|
|
8915
|
+
"",
|
|
8916
|
+
"---",
|
|
8917
|
+
"Approve this spec? The AI will then generate an implementation plan and tasks.",
|
|
8918
|
+
'Say "approve" to proceed, or describe what needs to change.'
|
|
8919
|
+
].join("\n");
|
|
8920
|
+
}
|
|
8921
|
+
function buildImplementationPrompt(session) {
|
|
8922
|
+
const spec = session.spec;
|
|
8923
|
+
if (!spec) return "No spec to implement.";
|
|
8924
|
+
const reqList = spec.requirements.map((r) => ` - [${r.priority}] ${r.description}`).join("\n");
|
|
8925
|
+
return [
|
|
8926
|
+
`\u2550\u2550\u2550 Implementation Planning \u2550\u2550\u2550`,
|
|
8927
|
+
`Feature: "${spec.title}"`,
|
|
8928
|
+
`Requirements: ${spec.requirements.length}`,
|
|
8929
|
+
"",
|
|
8930
|
+
"**Requirements to implement:**",
|
|
8931
|
+
reqList,
|
|
8932
|
+
"",
|
|
8933
|
+
"**Instructions for AI:**",
|
|
8934
|
+
"Generate a detailed implementation plan for this specification.",
|
|
8935
|
+
"Include:",
|
|
8936
|
+
"1. Architecture decisions",
|
|
8937
|
+
"2. File structure changes",
|
|
8938
|
+
"3. Key implementation details",
|
|
8939
|
+
"4. Dependency requirements",
|
|
8940
|
+
"5. Testing strategy",
|
|
8941
|
+
"",
|
|
8942
|
+
"**IMPORTANT:** After the plan, you MUST generate executable tasks as a JSON array.",
|
|
8943
|
+
"Each task should be a concrete, actionable step. Output the JSON inside a ```json code block:",
|
|
8944
|
+
"```json",
|
|
8945
|
+
"[",
|
|
8946
|
+
" {",
|
|
8947
|
+
' "title": "Create auth middleware",',
|
|
8948
|
+
' "description": "Implement JWT verification middleware for protected routes",',
|
|
8949
|
+
' "type": "feature",',
|
|
8950
|
+
' "priority": "critical",',
|
|
8951
|
+
' "estimateHours": 3,',
|
|
8952
|
+
' "tags": ["auth", "middleware"]',
|
|
8953
|
+
" },",
|
|
8954
|
+
" {",
|
|
8955
|
+
' "title": "Write auth tests",',
|
|
8956
|
+
' "description": "Unit and integration tests for authentication flow",',
|
|
8957
|
+
' "type": "test",',
|
|
8958
|
+
' "priority": "high",',
|
|
8959
|
+
' "estimateHours": 2,',
|
|
8960
|
+
' "tags": ["test", "auth"]',
|
|
8961
|
+
" }",
|
|
8962
|
+
"]",
|
|
8963
|
+
"```",
|
|
8964
|
+
"",
|
|
8965
|
+
"Rules:",
|
|
8966
|
+
"- Each task must be independently executable",
|
|
8967
|
+
"- Order tasks by dependency (things that block others come first)",
|
|
8968
|
+
'- Use type: "feature" for code, "test" for tests, "docs" for documentation, "chore" for config',
|
|
8969
|
+
'- Use priority: "critical" for blockers, "high" for core features, "medium" for nice-to-haves, "low" for polish'
|
|
8970
|
+
].join("\n");
|
|
8971
|
+
}
|
|
8972
|
+
function buildTaskReviewPrompt(session) {
|
|
8973
|
+
return [
|
|
8974
|
+
`\u2550\u2550\u2550 Task Review \u2550\u2550\u2550`,
|
|
8975
|
+
`Feature: "${session.spec?.title ?? session.title}"`,
|
|
8976
|
+
"",
|
|
8977
|
+
session.implementation ?? "No implementation plan yet.",
|
|
8978
|
+
"",
|
|
8979
|
+
"---",
|
|
8980
|
+
'Ready to execute these tasks? Say "execute" to begin, or describe changes needed.'
|
|
8981
|
+
].join("\n");
|
|
8982
|
+
}
|
|
8983
|
+
function buildExecutingPrompt(session) {
|
|
8984
|
+
return [
|
|
8985
|
+
`\u2550\u2550\u2550 Task Execution \u2550\u2550\u2550`,
|
|
8986
|
+
`Feature: "${session.spec?.title ?? session.title}"`,
|
|
8987
|
+
"",
|
|
8988
|
+
"**Instructions for AI:**",
|
|
8989
|
+
"Execute the tasks one by one in the order shown in the task list above.",
|
|
8990
|
+
"",
|
|
8991
|
+
"For each task:",
|
|
8992
|
+
"1. Implement the code (create/modify files)",
|
|
8993
|
+
"2. Write tests if applicable",
|
|
8994
|
+
"3. After completing a task, tell the user to run: /sdd done <task number or title>",
|
|
8995
|
+
"4. Then move to the next task",
|
|
8996
|
+
"",
|
|
8997
|
+
"**Important:**",
|
|
8998
|
+
"- Focus on ONE task at a time",
|
|
8999
|
+
"- After completing each task, explicitly state what you did",
|
|
9000
|
+
'- Tell the user: "Run /sdd done <N> to mark this task complete"',
|
|
9001
|
+
"- Then proceed to the next task automatically",
|
|
9002
|
+
"- When ALL tasks are done, provide a summary of everything implemented",
|
|
9003
|
+
"",
|
|
9004
|
+
"Start executing the first pending task now."
|
|
9005
|
+
].join("\n");
|
|
9006
|
+
}
|
|
9007
|
+
var AISpecBuilder = class {
|
|
9008
|
+
session;
|
|
9009
|
+
store;
|
|
9010
|
+
minQuestions;
|
|
9011
|
+
maxQuestions;
|
|
9012
|
+
sessionPath;
|
|
9013
|
+
constructor(opts) {
|
|
9014
|
+
this.store = opts.store;
|
|
9015
|
+
this.minQuestions = opts.minQuestions ?? 2;
|
|
9016
|
+
this.maxQuestions = opts.maxQuestions ?? 10;
|
|
9017
|
+
this.sessionPath = opts.sessionPath;
|
|
9018
|
+
this.session = {
|
|
9019
|
+
id: crypto.randomUUID(),
|
|
9020
|
+
phase: "questioning",
|
|
9021
|
+
title: "",
|
|
9022
|
+
userIntent: "",
|
|
9023
|
+
projectContext: opts.projectContext ?? "",
|
|
9024
|
+
answers: [],
|
|
9025
|
+
questionCount: 0,
|
|
9026
|
+
approved: false,
|
|
9027
|
+
createdAt: Date.now(),
|
|
9028
|
+
updatedAt: Date.now()
|
|
9029
|
+
};
|
|
9030
|
+
}
|
|
9031
|
+
// ── Session Persistence ──────────────────────────────────────────────────
|
|
9032
|
+
/** Save session state to disk. */
|
|
9033
|
+
async saveSession() {
|
|
9034
|
+
if (!this.sessionPath) return;
|
|
9035
|
+
try {
|
|
9036
|
+
const fsp13 = await import('fs/promises');
|
|
9037
|
+
const path17 = await import('path');
|
|
9038
|
+
await fsp13.mkdir(path17.dirname(this.sessionPath), { recursive: true });
|
|
9039
|
+
await fsp13.writeFile(this.sessionPath, JSON.stringify(this.session, null, 2), "utf8");
|
|
9040
|
+
} catch {
|
|
9041
|
+
}
|
|
9042
|
+
}
|
|
9043
|
+
/** Load session state from disk. Returns true if a session was loaded. */
|
|
9044
|
+
async loadSession() {
|
|
9045
|
+
if (!this.sessionPath) return false;
|
|
9046
|
+
try {
|
|
9047
|
+
const fsp13 = await import('fs/promises');
|
|
9048
|
+
const raw = await fsp13.readFile(this.sessionPath, "utf8");
|
|
9049
|
+
const loaded = JSON.parse(raw);
|
|
9050
|
+
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
9051
|
+
this.session = loaded;
|
|
9052
|
+
return true;
|
|
9053
|
+
}
|
|
9054
|
+
} catch {
|
|
9055
|
+
}
|
|
9056
|
+
return false;
|
|
9057
|
+
}
|
|
9058
|
+
/** Delete saved session from disk. */
|
|
9059
|
+
async deleteSession() {
|
|
9060
|
+
if (!this.sessionPath) return;
|
|
9061
|
+
try {
|
|
9062
|
+
const fsp13 = await import('fs/promises');
|
|
9063
|
+
await fsp13.unlink(this.sessionPath);
|
|
9064
|
+
} catch {
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
/** Auto-save helper — calls saveSession() but never throws. */
|
|
9068
|
+
autoSave() {
|
|
9069
|
+
this.saveSession().catch(() => {
|
|
9070
|
+
});
|
|
9071
|
+
}
|
|
9072
|
+
// ── Session Lifecycle ─────────────────────────────────────────────────────
|
|
9073
|
+
/** Start a new session with a title and optional intent. */
|
|
9074
|
+
startSession(title, intent) {
|
|
9075
|
+
this.session.title = title;
|
|
9076
|
+
this.session.userIntent = intent ?? "";
|
|
9077
|
+
this.session.phase = "questioning";
|
|
9078
|
+
this.session.updatedAt = Date.now();
|
|
9079
|
+
this.autoSave();
|
|
9080
|
+
}
|
|
9081
|
+
/** Get current session state (readonly). */
|
|
9082
|
+
getSession() {
|
|
9083
|
+
return { ...this.session };
|
|
9084
|
+
}
|
|
9085
|
+
/** Get the current phase. */
|
|
9086
|
+
getPhase() {
|
|
9087
|
+
return this.session.phase;
|
|
9088
|
+
}
|
|
9089
|
+
// ── AI Prompt Generation ──────────────────────────────────────────────────
|
|
9090
|
+
/**
|
|
9091
|
+
* Get the AI prompt for the current phase.
|
|
9092
|
+
* This prompt is injected into the conversation so the AI agent knows
|
|
9093
|
+
* what to do next (ask a question, generate a spec, etc.).
|
|
9094
|
+
*/
|
|
9095
|
+
getAIPrompt() {
|
|
9096
|
+
switch (this.session.phase) {
|
|
9097
|
+
case "questioning":
|
|
9098
|
+
return buildQuestioningPrompt(this.session, this.minQuestions, this.maxQuestions);
|
|
9099
|
+
case "spec_review":
|
|
9100
|
+
return buildSpecReviewPrompt(this.session);
|
|
9101
|
+
case "implementation":
|
|
9102
|
+
return buildImplementationPrompt(this.session);
|
|
9103
|
+
case "task_review":
|
|
9104
|
+
return buildTaskReviewPrompt(this.session);
|
|
9105
|
+
case "executing":
|
|
9106
|
+
return buildExecutingPrompt(this.session);
|
|
9107
|
+
case "done":
|
|
9108
|
+
return "All tasks completed. Specification is fully implemented.";
|
|
9109
|
+
}
|
|
9110
|
+
}
|
|
9111
|
+
// ── Answer Processing ─────────────────────────────────────────────────────
|
|
9112
|
+
/**
|
|
9113
|
+
* Record a question/answer pair from the AI conversation.
|
|
9114
|
+
* Call this when the AI asks a question and the user responds.
|
|
9115
|
+
*/
|
|
9116
|
+
addAnswer(question, answer) {
|
|
9117
|
+
this.session.answers.push({ question, answer, timestamp: Date.now() });
|
|
9118
|
+
this.session.questionCount++;
|
|
9119
|
+
this.session.updatedAt = Date.now();
|
|
9120
|
+
this.autoSave();
|
|
9121
|
+
}
|
|
9122
|
+
/**
|
|
9123
|
+
* Check if more questions should be asked.
|
|
9124
|
+
* Returns false if max reached or if the AI has signaled it has enough info.
|
|
9125
|
+
*/
|
|
9126
|
+
shouldContinueQuestioning() {
|
|
9127
|
+
return this.session.questionCount < this.maxQuestions;
|
|
9128
|
+
}
|
|
9129
|
+
/**
|
|
9130
|
+
* Check if minimum questions have been asked.
|
|
9131
|
+
*/
|
|
9132
|
+
hasMetMinimumQuestions() {
|
|
9133
|
+
return this.session.questionCount >= this.minQuestions;
|
|
9134
|
+
}
|
|
9135
|
+
// ── Phase Transitions ─────────────────────────────────────────────────────
|
|
9136
|
+
/**
|
|
9137
|
+
* Set the generated specification and move to spec_review phase.
|
|
9138
|
+
*/
|
|
9139
|
+
setSpec(spec) {
|
|
9140
|
+
this.session.spec = spec;
|
|
9141
|
+
this.session.phase = "spec_review";
|
|
9142
|
+
this.session.updatedAt = Date.now();
|
|
9143
|
+
this.autoSave();
|
|
9144
|
+
}
|
|
9145
|
+
/**
|
|
9146
|
+
* Approve the current phase and advance to the next.
|
|
9147
|
+
* questioning → spec_review (requires spec to be set)
|
|
9148
|
+
* spec_review → implementation
|
|
9149
|
+
* implementation → task_review (requires implementation to be set)
|
|
9150
|
+
* task_review → executing
|
|
9151
|
+
* executing → done
|
|
9152
|
+
*/
|
|
9153
|
+
approve() {
|
|
9154
|
+
switch (this.session.phase) {
|
|
9155
|
+
case "questioning":
|
|
9156
|
+
if (!this.session.spec) {
|
|
9157
|
+
throw new Error("Cannot approve: no spec generated yet.");
|
|
9158
|
+
}
|
|
9159
|
+
this.session.phase = "spec_review";
|
|
9160
|
+
break;
|
|
9161
|
+
case "spec_review":
|
|
9162
|
+
this.session.phase = "implementation";
|
|
9163
|
+
break;
|
|
9164
|
+
case "implementation":
|
|
9165
|
+
this.session.phase = "task_review";
|
|
9166
|
+
break;
|
|
9167
|
+
case "task_review":
|
|
9168
|
+
this.session.phase = "executing";
|
|
9169
|
+
break;
|
|
9170
|
+
case "executing":
|
|
9171
|
+
this.session.phase = "done";
|
|
9172
|
+
break;
|
|
9173
|
+
}
|
|
9174
|
+
this.session.approved = true;
|
|
9175
|
+
this.session.updatedAt = Date.now();
|
|
9176
|
+
this.autoSave();
|
|
9177
|
+
return this.session.phase;
|
|
9178
|
+
}
|
|
9179
|
+
/**
|
|
9180
|
+
* Set the implementation plan text.
|
|
9181
|
+
*/
|
|
9182
|
+
setImplementation(plan) {
|
|
9183
|
+
this.session.implementation = plan;
|
|
9184
|
+
this.session.phase = "task_review";
|
|
9185
|
+
this.session.updatedAt = Date.now();
|
|
9186
|
+
this.autoSave();
|
|
9187
|
+
}
|
|
9188
|
+
/**
|
|
9189
|
+
* Mark session as done.
|
|
9190
|
+
*/
|
|
9191
|
+
markDone() {
|
|
9192
|
+
this.session.phase = "done";
|
|
9193
|
+
this.session.updatedAt = Date.now();
|
|
9194
|
+
this.autoSave();
|
|
9195
|
+
}
|
|
9196
|
+
/**
|
|
9197
|
+
* Set the task graph ID for this session.
|
|
9198
|
+
*/
|
|
9199
|
+
setTaskGraphId(graphId) {
|
|
9200
|
+
this.session.taskGraphId = graphId;
|
|
9201
|
+
this.autoSave();
|
|
9202
|
+
}
|
|
9203
|
+
/**
|
|
9204
|
+
* Get the task graph ID for this session.
|
|
9205
|
+
*/
|
|
9206
|
+
getTaskGraphId() {
|
|
9207
|
+
return this.session.taskGraphId;
|
|
9208
|
+
}
|
|
9209
|
+
// ── Spec Persistence ──────────────────────────────────────────────────────
|
|
9210
|
+
/**
|
|
9211
|
+
* Save the current spec to the store.
|
|
9212
|
+
*/
|
|
9213
|
+
async saveSpec() {
|
|
9214
|
+
if (!this.session.spec) {
|
|
9215
|
+
throw new Error("No spec to save.");
|
|
9216
|
+
}
|
|
9217
|
+
await this.store.save(this.session.spec);
|
|
9218
|
+
return this.session.spec;
|
|
9219
|
+
}
|
|
9220
|
+
// ── Spec Generation Helpers ───────────────────────────────────────────────
|
|
9221
|
+
/**
|
|
9222
|
+
* Parse a spec from a JSON string (from AI output).
|
|
9223
|
+
* Validates and normalizes the structure.
|
|
9224
|
+
*/
|
|
9225
|
+
parseSpecFromJSON(jsonStr) {
|
|
9226
|
+
let parsed;
|
|
9227
|
+
try {
|
|
9228
|
+
parsed = JSON.parse(jsonStr);
|
|
9229
|
+
} catch (e) {
|
|
9230
|
+
throw new Error(`Invalid JSON for spec: ${e instanceof Error ? e.message : "parse error"}`);
|
|
9231
|
+
}
|
|
9232
|
+
if (!parsed || typeof parsed !== "object") {
|
|
9233
|
+
throw new Error("Spec JSON must be an object.");
|
|
9234
|
+
}
|
|
9235
|
+
const raw = parsed;
|
|
9236
|
+
const now = Date.now();
|
|
9237
|
+
const title = String(raw.title ?? this.session.title ?? "Untitled");
|
|
9238
|
+
const overview = String(raw.overview ?? "");
|
|
9239
|
+
if (!overview || overview === "undefined") {
|
|
9240
|
+
throw new Error("Spec must have an overview.");
|
|
9241
|
+
}
|
|
9242
|
+
const rawSections = Array.isArray(raw.sections) ? raw.sections : [];
|
|
9243
|
+
const sections = rawSections.filter((s) => s && typeof s === "object").map((s) => ({
|
|
9244
|
+
type: ["overview", "requirements", "architecture", "api", "data", "security", "acceptance"].includes(String(s.type)) ? String(s.type) : "overview",
|
|
9245
|
+
title: String(s.title ?? ""),
|
|
9246
|
+
content: String(s.content ?? ""),
|
|
9247
|
+
level: Number(s.level) || 1
|
|
9248
|
+
}));
|
|
9249
|
+
const rawReqs = Array.isArray(raw.requirements) ? raw.requirements : [];
|
|
9250
|
+
const requirements = rawReqs.filter((r) => r && typeof r === "object").map((r, i) => ({
|
|
9251
|
+
id: String(r.id ?? `REQ-${i + 1}`),
|
|
9252
|
+
type: ["functional", "non-functional", "security", "performance", "ux"].includes(String(r.type)) ? String(r.type) : "functional",
|
|
9253
|
+
priority: ["critical", "high", "medium", "low"].includes(String(r.priority)) ? String(r.priority) : "medium",
|
|
9254
|
+
description: String(r.description ?? ""),
|
|
9255
|
+
acceptanceCriteria: Array.isArray(r.acceptanceCriteria) ? r.acceptanceCriteria.map(String) : []
|
|
9256
|
+
}));
|
|
9257
|
+
const spec = {
|
|
9258
|
+
id: crypto.randomUUID(),
|
|
9259
|
+
title,
|
|
9260
|
+
version: "0.1.0",
|
|
9261
|
+
status: "draft",
|
|
9262
|
+
overview,
|
|
9263
|
+
sections,
|
|
9264
|
+
requirements,
|
|
9265
|
+
createdAt: now,
|
|
9266
|
+
updatedAt: now,
|
|
9267
|
+
metadata: {
|
|
9268
|
+
generatedBy: "AISpecBuilder",
|
|
9269
|
+
sessionId: this.session.id
|
|
9270
|
+
}
|
|
9271
|
+
};
|
|
9272
|
+
return spec;
|
|
9273
|
+
}
|
|
9274
|
+
/**
|
|
9275
|
+
* Extract JSON from AI output (handles ```json blocks and raw JSON).
|
|
9276
|
+
*/
|
|
9277
|
+
extractJSON(text) {
|
|
9278
|
+
const codeBlockMatch = text.match(/```json\s*([\s\S]*?)```/);
|
|
9279
|
+
if (codeBlockMatch?.[1]) {
|
|
9280
|
+
return codeBlockMatch[1].trim();
|
|
9281
|
+
}
|
|
9282
|
+
const genericBlockMatch = text.match(/```\s*([\s\S]*?)```/);
|
|
9283
|
+
if (genericBlockMatch?.[1]) {
|
|
9284
|
+
const trimmed = genericBlockMatch[1].trim();
|
|
9285
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
9286
|
+
return trimmed;
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
const jsonMatch = text.match(/(\{[\s\S]*\})/);
|
|
9290
|
+
if (jsonMatch?.[1]) {
|
|
9291
|
+
try {
|
|
9292
|
+
JSON.parse(jsonMatch[1]);
|
|
9293
|
+
return jsonMatch[1];
|
|
9294
|
+
} catch {
|
|
9295
|
+
}
|
|
9296
|
+
}
|
|
9297
|
+
return null;
|
|
9298
|
+
}
|
|
9299
|
+
/**
|
|
9300
|
+
* Detect if AI output contains a spec (JSON block).
|
|
9301
|
+
*/
|
|
9302
|
+
hasSpecInOutput(text) {
|
|
9303
|
+
return this.extractJSON(text) !== null;
|
|
9304
|
+
}
|
|
9305
|
+
/**
|
|
9306
|
+
* Try to parse a spec from AI output text.
|
|
9307
|
+
* Returns null if no valid spec found.
|
|
9308
|
+
*/
|
|
9309
|
+
tryParseSpecFromOutput(text) {
|
|
9310
|
+
const json = this.extractJSON(text);
|
|
9311
|
+
if (!json) return null;
|
|
9312
|
+
try {
|
|
9313
|
+
return this.parseSpecFromJSON(json);
|
|
9314
|
+
} catch {
|
|
9315
|
+
return null;
|
|
9316
|
+
}
|
|
9317
|
+
}
|
|
9318
|
+
// ── JSON Array Extraction (for tasks) ─────────────────────────────────────
|
|
9319
|
+
/**
|
|
9320
|
+
* Extract a JSON array from AI output (for task lists).
|
|
9321
|
+
*/
|
|
9322
|
+
extractJSONArray(text) {
|
|
9323
|
+
const codeBlockMatch = text.match(/```json\s*([\s\S]*?)```/);
|
|
9324
|
+
if (codeBlockMatch?.[1]) {
|
|
9325
|
+
const trimmed = codeBlockMatch[1].trim();
|
|
9326
|
+
if (trimmed.startsWith("[")) return trimmed;
|
|
9327
|
+
}
|
|
9328
|
+
const arrayMatch = text.match(/(\[[\s\S]*\])/);
|
|
9329
|
+
if (arrayMatch?.[1]) {
|
|
9330
|
+
try {
|
|
9331
|
+
const parsed = JSON.parse(arrayMatch[1]);
|
|
9332
|
+
if (Array.isArray(parsed)) return arrayMatch[1];
|
|
9333
|
+
} catch {
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
return null;
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
|
|
9340
|
+
// src/sdd/spec-templates.ts
|
|
9341
|
+
var SPEC_TEMPLATES = [
|
|
9342
|
+
{
|
|
9343
|
+
id: "feature",
|
|
9344
|
+
name: "New Feature",
|
|
9345
|
+
description: "Template for new feature development",
|
|
9346
|
+
sections: [
|
|
9347
|
+
{ type: "overview", title: "Overview", level: 2 },
|
|
9348
|
+
{ type: "requirements", title: "Requirements", level: 2 },
|
|
9349
|
+
{ type: "architecture", title: "Architecture", level: 2 },
|
|
9350
|
+
{ type: "api", title: "API Design", level: 2 },
|
|
9351
|
+
{ type: "data", title: "Data Model", level: 2 },
|
|
9352
|
+
{ type: "security", title: "Security", level: 2 },
|
|
9353
|
+
{ type: "acceptance", title: "Acceptance Criteria", level: 2 }
|
|
9354
|
+
],
|
|
9355
|
+
defaultRequirements: [
|
|
9356
|
+
{ type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
9357
|
+
{ type: "non-functional", priority: "medium", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9358
|
+
]
|
|
9359
|
+
},
|
|
9360
|
+
{
|
|
9361
|
+
id: "bugfix",
|
|
9362
|
+
name: "Bug Fix",
|
|
9363
|
+
description: "Template for bug fix specifications",
|
|
9364
|
+
sections: [
|
|
9365
|
+
{ type: "overview", title: "Bug Description", level: 2 },
|
|
9366
|
+
{ type: "requirements", title: "Root Cause Analysis", level: 2 },
|
|
9367
|
+
{ type: "acceptance", title: "Fix Verification", level: 2 }
|
|
9368
|
+
],
|
|
9369
|
+
defaultRequirements: [
|
|
9370
|
+
{ type: "functional", priority: "critical", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9371
|
+
]
|
|
9372
|
+
},
|
|
9373
|
+
{
|
|
9374
|
+
id: "refactor",
|
|
9375
|
+
name: "Refactor",
|
|
9376
|
+
description: "Template for code refactoring",
|
|
9377
|
+
sections: [
|
|
9378
|
+
{ type: "overview", title: "Current State", level: 2 },
|
|
9379
|
+
{ type: "requirements", title: "Refactoring Goals", level: 2 },
|
|
9380
|
+
{ type: "architecture", title: "Target Architecture", level: 2 },
|
|
9381
|
+
{ type: "acceptance", title: "Verification", level: 2 }
|
|
9382
|
+
],
|
|
9383
|
+
defaultRequirements: [
|
|
9384
|
+
{ type: "non-functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9385
|
+
]
|
|
9386
|
+
},
|
|
9387
|
+
{
|
|
9388
|
+
id: "infra",
|
|
9389
|
+
name: "Infrastructure",
|
|
9390
|
+
description: "Template for infrastructure/tooling changes",
|
|
9391
|
+
sections: [
|
|
9392
|
+
{ type: "overview", title: "What and Why", level: 2 },
|
|
9393
|
+
{ type: "requirements", title: "Requirements", level: 2 },
|
|
9394
|
+
{ type: "architecture", title: "Design", level: 2 },
|
|
9395
|
+
{ type: "security", title: "Security Impact", level: 2 },
|
|
9396
|
+
{ type: "acceptance", title: "Rollout Plan", level: 2 }
|
|
9397
|
+
],
|
|
9398
|
+
defaultRequirements: [
|
|
9399
|
+
{ type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
9400
|
+
{ type: "security", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9401
|
+
]
|
|
9402
|
+
},
|
|
9403
|
+
{
|
|
9404
|
+
id: "integration",
|
|
9405
|
+
name: "Integration",
|
|
9406
|
+
description: "Template for integrating external services or APIs",
|
|
9407
|
+
sections: [
|
|
9408
|
+
{ type: "overview", title: "Integration Overview", level: 2 },
|
|
9409
|
+
{ type: "requirements", title: "Integration Requirements", level: 2 },
|
|
9410
|
+
{ type: "api", title: "API Contract", level: 2 },
|
|
9411
|
+
{ type: "architecture", title: "Architecture", level: 2 },
|
|
9412
|
+
{ type: "security", title: "Auth & Security", level: 2 },
|
|
9413
|
+
{ type: "acceptance", title: "Testing Strategy", level: 2 }
|
|
9414
|
+
],
|
|
9415
|
+
defaultRequirements: [
|
|
9416
|
+
{ type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
9417
|
+
{ type: "security", priority: "critical", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
9418
|
+
{ type: "performance", priority: "medium", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9419
|
+
]
|
|
9420
|
+
},
|
|
9421
|
+
{
|
|
9422
|
+
id: "cli-command",
|
|
9423
|
+
name: "CLI Command",
|
|
9424
|
+
description: "Template for new CLI commands/slash commands",
|
|
9425
|
+
sections: [
|
|
9426
|
+
{ type: "overview", title: "Command Overview", level: 2 },
|
|
9427
|
+
{ type: "requirements", title: "Command Requirements", level: 2 },
|
|
9428
|
+
{ type: "api", title: "Command Interface", level: 2 },
|
|
9429
|
+
{ type: "acceptance", title: "Usage Examples", level: 2 }
|
|
9430
|
+
],
|
|
9431
|
+
defaultRequirements: [
|
|
9432
|
+
{ type: "ux", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
9433
|
+
{ type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
|
|
9434
|
+
]
|
|
9435
|
+
}
|
|
9436
|
+
];
|
|
9437
|
+
function getTemplate(id) {
|
|
9438
|
+
return SPEC_TEMPLATES.find((t) => t.id === id);
|
|
9439
|
+
}
|
|
9440
|
+
function listTemplates() {
|
|
9441
|
+
return SPEC_TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description }));
|
|
9442
|
+
}
|
|
9443
|
+
function templateToMarkdown(template, title) {
|
|
9444
|
+
const lines = [];
|
|
9445
|
+
lines.push(`# ${title ?? "Untitled Specification"}`);
|
|
9446
|
+
lines.push("Version: 0.1.0");
|
|
9447
|
+
lines.push("");
|
|
9448
|
+
for (const section of template.sections) {
|
|
9449
|
+
lines.push(`${"#".repeat(section.level + 1)} ${section.title}`);
|
|
9450
|
+
lines.push(`_<!-- ${section.type} section content -->_`);
|
|
9451
|
+
lines.push("");
|
|
9452
|
+
}
|
|
9453
|
+
return lines.join("\n");
|
|
9454
|
+
}
|
|
9455
|
+
|
|
9456
|
+
// src/sdd/task-visualizer.ts
|
|
9457
|
+
var STATUS_ICON = {
|
|
9458
|
+
pending: "\u25CB",
|
|
9459
|
+
in_progress: "\u25D0",
|
|
9460
|
+
blocked: "\u2298",
|
|
9461
|
+
failed: "\u2717",
|
|
9462
|
+
review: "\u25D1",
|
|
9463
|
+
completed: "\u25CF"
|
|
9464
|
+
};
|
|
9465
|
+
var PRIORITY_ICON = {
|
|
9466
|
+
critical: "\u{1F534}",
|
|
9467
|
+
high: "\u{1F7E0}",
|
|
9468
|
+
medium: "\u{1F7E1}",
|
|
9469
|
+
low: "\u{1F7E2}"
|
|
9470
|
+
};
|
|
9471
|
+
var TYPE_ICON = {
|
|
9472
|
+
feature: "\u26A1",
|
|
9473
|
+
bugfix: "\u{1F41B}",
|
|
9474
|
+
refactor: "\u267B\uFE0F",
|
|
9475
|
+
docs: "\u{1F4DD}",
|
|
9476
|
+
test: "\u{1F9EA}",
|
|
9477
|
+
chore: "\u{1F527}"
|
|
9478
|
+
};
|
|
9479
|
+
function renderTaskGraph(graph, opts) {
|
|
9480
|
+
const lines = [];
|
|
9481
|
+
const compact = opts?.compact ?? false;
|
|
9482
|
+
lines.push(`\u256D\u2500 Task Graph: ${graph.title} \u2500\u256E`);
|
|
9483
|
+
lines.push(`\u2502 Spec: ${graph.specId.slice(0, 8)}... \u2502 Nodes: ${graph.nodes.size} \u2502 Edges: ${graph.edges.length} \u2502`);
|
|
9484
|
+
lines.push("\u2570" + "\u2500".repeat(Math.max(50, graph.title.length + 30)) + "\u256F");
|
|
9485
|
+
lines.push("");
|
|
9486
|
+
const progress = computeTaskProgress(graph);
|
|
9487
|
+
lines.push(renderProgress(progress));
|
|
9488
|
+
lines.push("");
|
|
9489
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
9490
|
+
for (const edge of graph.edges) {
|
|
9491
|
+
if (edge.type === "depends_on") {
|
|
9492
|
+
const deps = childrenMap.get(edge.from) ?? [];
|
|
9493
|
+
deps.push(edge.to);
|
|
9494
|
+
childrenMap.set(edge.from, deps);
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
const rendered = /* @__PURE__ */ new Set();
|
|
9498
|
+
const rootNodes = graph.rootNodes.filter((id) => graph.nodes.has(id));
|
|
9499
|
+
const startNodes = rootNodes.length > 0 ? rootNodes : Array.from(graph.nodes.keys()).filter((id) => {
|
|
9500
|
+
const deps = childrenMap.get(id);
|
|
9501
|
+
return !deps || deps.length === 0;
|
|
9502
|
+
});
|
|
9503
|
+
for (const rootId of startNodes) {
|
|
9504
|
+
renderNode(graph, rootId, lines, rendered, childrenMap, compact, "");
|
|
9505
|
+
}
|
|
9506
|
+
for (const [id] of graph.nodes) {
|
|
9507
|
+
if (!rendered.has(id)) {
|
|
9508
|
+
renderNode(graph, id, lines, rendered, childrenMap, compact, "");
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
lines.push("");
|
|
9512
|
+
lines.push("Legend: \u25CF done \u25D0 in-progress \u25CB pending \u2297 blocked \u2717 failed \u25D2 review");
|
|
9513
|
+
return lines.join("\n");
|
|
9514
|
+
}
|
|
9515
|
+
function renderNode(graph, nodeId, lines, rendered, childrenMap, compact, prefix) {
|
|
9516
|
+
if (rendered.has(nodeId)) return;
|
|
9517
|
+
rendered.add(nodeId);
|
|
9518
|
+
const node = graph.nodes.get(nodeId);
|
|
9519
|
+
if (!node) return;
|
|
9520
|
+
const icon = STATUS_ICON[node.status];
|
|
9521
|
+
const prioIcon = PRIORITY_ICON[node.priority];
|
|
9522
|
+
const typeIcon = TYPE_ICON[node.type];
|
|
9523
|
+
const title = compact ? truncate2(node.title, 40) : node.title;
|
|
9524
|
+
const blockedBy = childrenMap.get(nodeId) ?? [];
|
|
9525
|
+
const depsStr = blockedBy.length > 0 ? ` \u2190 [${blockedBy.map((d) => graph.nodes.get(d)?.title?.slice(0, 12) ?? "?").join(", ")}]` : "";
|
|
9526
|
+
lines.push(`${prefix}${icon} ${typeIcon} ${prioIcon} ${title}${depsStr}`);
|
|
9527
|
+
if (!compact && node.description) {
|
|
9528
|
+
const descLines = node.description.split("\n").slice(0, 3);
|
|
9529
|
+
for (const dl of descLines) {
|
|
9530
|
+
lines.push(`${prefix} \u2514 ${truncate2(dl, 60)}`);
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
const dependents = graph.edges.filter((e) => e.type === "depends_on" && e.to === nodeId).map((e) => e.from).filter((id) => graph.nodes.has(id));
|
|
9534
|
+
for (const depId of dependents) {
|
|
9535
|
+
renderNode(graph, depId, lines, rendered, childrenMap, compact, prefix + " ");
|
|
9536
|
+
}
|
|
9537
|
+
}
|
|
9538
|
+
function renderProgress(progress) {
|
|
9539
|
+
const barWidth = 30;
|
|
9540
|
+
const filled = Math.round(progress.percentComplete / 100 * barWidth);
|
|
9541
|
+
const empty = barWidth - filled;
|
|
9542
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
9543
|
+
return [
|
|
9544
|
+
`Progress: [${bar}] ${progress.percentComplete}%`,
|
|
9545
|
+
` ${progress.completed} done \u2502 ${progress.inProgress} active \u2502 ${progress.pending} pending \u2502 ${progress.blocked} blocked \u2502 ${progress.failed} failed`
|
|
9546
|
+
].join("\n");
|
|
9547
|
+
}
|
|
9548
|
+
function renderTaskList(graph) {
|
|
9549
|
+
const lines = [];
|
|
9550
|
+
const nodes = Array.from(graph.nodes.values());
|
|
9551
|
+
const groups = {
|
|
9552
|
+
in_progress: [],
|
|
9553
|
+
pending: [],
|
|
9554
|
+
blocked: [],
|
|
9555
|
+
review: [],
|
|
9556
|
+
failed: [],
|
|
9557
|
+
completed: []
|
|
9558
|
+
};
|
|
9559
|
+
for (const node of nodes) {
|
|
9560
|
+
groups[node.status]?.push(node);
|
|
9561
|
+
}
|
|
9562
|
+
for (const [status, group] of Object.entries(groups)) {
|
|
9563
|
+
if (group.length === 0) continue;
|
|
9564
|
+
const icon = STATUS_ICON[status];
|
|
9565
|
+
lines.push(`${icon} ${status.toUpperCase()} (${group.length})`);
|
|
9566
|
+
for (const node of group) {
|
|
9567
|
+
const prio = PRIORITY_ICON[node.priority];
|
|
9568
|
+
const type = TYPE_ICON[node.type];
|
|
9569
|
+
lines.push(` ${type} ${prio} ${node.title}`);
|
|
9570
|
+
}
|
|
9571
|
+
lines.push("");
|
|
9572
|
+
}
|
|
9573
|
+
return lines.join("\n");
|
|
9574
|
+
}
|
|
9575
|
+
function renderSpecAnalysis(spec, analysis) {
|
|
9576
|
+
const lines = [];
|
|
9577
|
+
lines.push(`\u256D\u2500 Spec Analysis: ${spec.title} \u2500\u256E`);
|
|
9578
|
+
lines.push("");
|
|
9579
|
+
const barWidth = 20;
|
|
9580
|
+
const filled = Math.round(analysis.completeness / 100 * barWidth);
|
|
9581
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
9582
|
+
lines.push(`Completeness: [${bar}] ${analysis.completeness}%`);
|
|
9583
|
+
lines.push("");
|
|
9584
|
+
if (analysis.gaps.length > 0) {
|
|
9585
|
+
lines.push("\u26A0 Gaps:");
|
|
9586
|
+
for (const gap of analysis.gaps) {
|
|
9587
|
+
lines.push(` \u2022 ${gap}`);
|
|
9588
|
+
}
|
|
9589
|
+
lines.push("");
|
|
9590
|
+
}
|
|
9591
|
+
if (analysis.risks.length > 0) {
|
|
9592
|
+
lines.push("\u{1F534} Risks:");
|
|
9593
|
+
for (const risk of analysis.risks) {
|
|
9594
|
+
lines.push(` \u2022 ${risk}`);
|
|
9595
|
+
}
|
|
9596
|
+
lines.push("");
|
|
9597
|
+
}
|
|
9598
|
+
if (analysis.suggestions.length > 0) {
|
|
9599
|
+
lines.push("\u{1F4A1} Suggestions:");
|
|
9600
|
+
for (const sug of analysis.suggestions) {
|
|
9601
|
+
lines.push(` \u2022 ${sug}`);
|
|
9602
|
+
}
|
|
9603
|
+
}
|
|
9604
|
+
return lines.join("\n");
|
|
9605
|
+
}
|
|
9606
|
+
function truncate2(str, maxLen) {
|
|
9607
|
+
if (str.length <= maxLen) return str;
|
|
9608
|
+
return str.slice(0, maxLen - 1) + "\u2026";
|
|
9609
|
+
}
|
|
9610
|
+
|
|
9611
|
+
// src/sdd/critical-path.ts
|
|
9612
|
+
function analyzeCriticalPath(graph) {
|
|
9613
|
+
const nodes = Array.from(graph.nodes.values());
|
|
9614
|
+
const topoOrder = topologicalSort(graph);
|
|
9615
|
+
const blockedByMap = /* @__PURE__ */ new Map();
|
|
9616
|
+
const blocksMap = /* @__PURE__ */ new Map();
|
|
9617
|
+
for (const edge of graph.edges) {
|
|
9618
|
+
if (edge.type === "depends_on") {
|
|
9619
|
+
if (!blockedByMap.has(edge.from)) blockedByMap.set(edge.from, /* @__PURE__ */ new Set());
|
|
9620
|
+
blockedByMap.get(edge.from).add(edge.to);
|
|
9621
|
+
if (!blocksMap.has(edge.to)) blocksMap.set(edge.to, /* @__PURE__ */ new Set());
|
|
9622
|
+
blocksMap.get(edge.to).add(edge.from);
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
const readyTasks = [];
|
|
9626
|
+
const blockedTasks = [];
|
|
9627
|
+
for (const node of nodes) {
|
|
9628
|
+
if (node.status === "completed") continue;
|
|
9629
|
+
const blockers = blockedByMap.get(node.id);
|
|
9630
|
+
if (!blockers || blockers.size === 0) {
|
|
9631
|
+
readyTasks.push(node.id);
|
|
9632
|
+
} else {
|
|
9633
|
+
const allCompleted = Array.from(blockers).every((id) => {
|
|
9634
|
+
const n = graph.nodes.get(id);
|
|
9635
|
+
return n?.status === "completed";
|
|
9636
|
+
});
|
|
9637
|
+
if (allCompleted) {
|
|
9638
|
+
readyTasks.push(node.id);
|
|
9639
|
+
} else {
|
|
9640
|
+
blockedTasks.push(node.id);
|
|
9641
|
+
}
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
const bottlenecks = [];
|
|
9645
|
+
for (const node of nodes) {
|
|
9646
|
+
if (node.status === "completed") continue;
|
|
9647
|
+
const downstream = getTransitiveBlocked(graph, node.id, blocksMap);
|
|
9648
|
+
if (downstream.size > 0) {
|
|
9649
|
+
const blockedHours = Array.from(downstream).reduce((sum, id) => {
|
|
9650
|
+
const n = graph.nodes.get(id);
|
|
9651
|
+
return sum + (n?.estimateHours ?? 0);
|
|
9652
|
+
}, 0);
|
|
9653
|
+
bottlenecks.push({
|
|
9654
|
+
taskId: node.id,
|
|
9655
|
+
title: node.title,
|
|
9656
|
+
blockedCount: downstream.size,
|
|
9657
|
+
blockedHours,
|
|
9658
|
+
severity: Math.min(100, Math.round(downstream.size / nodes.length * 100))
|
|
9659
|
+
});
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9662
|
+
bottlenecks.sort((a, b) => b.severity - a.severity);
|
|
9663
|
+
const criticalPath = computeCriticalPath(graph, topoOrder, blockedByMap);
|
|
9664
|
+
const totalHours = criticalPath.reduce((sum, id) => {
|
|
9665
|
+
const n = graph.nodes.get(id);
|
|
9666
|
+
return sum + (n?.estimateHours ?? 0);
|
|
9667
|
+
}, 0);
|
|
9668
|
+
const parallelGroups = computeParallelGroups(graph, blockedByMap);
|
|
9669
|
+
const executionOrder = topoOrder.filter((id) => {
|
|
9670
|
+
const n = graph.nodes.get(id);
|
|
9671
|
+
return n && n.status !== "completed";
|
|
9672
|
+
});
|
|
9673
|
+
return {
|
|
9674
|
+
criticalPath,
|
|
9675
|
+
totalHours,
|
|
9676
|
+
bottlenecks,
|
|
9677
|
+
parallelGroups,
|
|
9678
|
+
executionOrder,
|
|
9679
|
+
readyTasks,
|
|
9680
|
+
blockedTasks
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
function getTransitiveBlocked(graph, taskId, blocksMap) {
|
|
9684
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9685
|
+
const queue = [taskId];
|
|
9686
|
+
while (queue.length > 0) {
|
|
9687
|
+
const current = queue.shift();
|
|
9688
|
+
const blocked = blocksMap.get(current);
|
|
9689
|
+
if (!blocked) continue;
|
|
9690
|
+
for (const id of blocked) {
|
|
9691
|
+
if (!visited.has(id) && id !== taskId) {
|
|
9692
|
+
visited.add(id);
|
|
9693
|
+
queue.push(id);
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
return visited;
|
|
9698
|
+
}
|
|
9699
|
+
function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
9700
|
+
const allIds = Array.from(graph.nodes.keys());
|
|
9701
|
+
if (allIds.length === 0) return [];
|
|
9702
|
+
const dist = /* @__PURE__ */ new Map();
|
|
9703
|
+
const prev = /* @__PURE__ */ new Map();
|
|
9704
|
+
for (const id of allIds) {
|
|
9705
|
+
dist.set(id, graph.nodes.get(id)?.estimateHours ?? 1);
|
|
9706
|
+
prev.set(id, null);
|
|
9707
|
+
}
|
|
9708
|
+
const blocksMap = /* @__PURE__ */ new Map();
|
|
9709
|
+
for (const [taskId, blockers] of blockedByMap) {
|
|
9710
|
+
for (const blockerId of blockers) {
|
|
9711
|
+
if (!blocksMap.has(blockerId)) blocksMap.set(blockerId, /* @__PURE__ */ new Set());
|
|
9712
|
+
blocksMap.get(blockerId).add(taskId);
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
const n = allIds.length;
|
|
9716
|
+
for (let i = 0; i < n - 1; i++) {
|
|
9717
|
+
let changed = false;
|
|
9718
|
+
for (const id of allIds) {
|
|
9719
|
+
const blocked = blocksMap.get(id);
|
|
9720
|
+
if (!blocked) continue;
|
|
9721
|
+
for (const blockedId of blocked) {
|
|
9722
|
+
const candidateDist = (dist.get(id) ?? 0) + (graph.nodes.get(blockedId)?.estimateHours ?? 1);
|
|
9723
|
+
if (candidateDist > (dist.get(blockedId) ?? 0)) {
|
|
9724
|
+
dist.set(blockedId, candidateDist);
|
|
9725
|
+
prev.set(blockedId, id);
|
|
9726
|
+
changed = true;
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
}
|
|
9730
|
+
if (!changed) break;
|
|
9731
|
+
}
|
|
9732
|
+
let maxDist = 0;
|
|
9733
|
+
let maxId = allIds[0];
|
|
9734
|
+
for (const id of allIds) {
|
|
9735
|
+
const d = dist.get(id) ?? 0;
|
|
9736
|
+
if (d > maxDist) {
|
|
9737
|
+
maxDist = d;
|
|
9738
|
+
maxId = id;
|
|
9739
|
+
}
|
|
9740
|
+
}
|
|
9741
|
+
const path17 = [];
|
|
9742
|
+
let current = maxId;
|
|
9743
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9744
|
+
while (current && !visited.has(current)) {
|
|
9745
|
+
visited.add(current);
|
|
9746
|
+
path17.unshift(current);
|
|
9747
|
+
current = prev.get(current) ?? null;
|
|
9748
|
+
}
|
|
9749
|
+
return path17;
|
|
9750
|
+
}
|
|
9751
|
+
function computeParallelGroups(graph, blockedByMap) {
|
|
9752
|
+
const groups = [];
|
|
9753
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
9754
|
+
const nodes = Array.from(graph.nodes.values()).filter((n) => n.status !== "completed");
|
|
9755
|
+
const remaining = new Set(nodes.map((n) => n.id));
|
|
9756
|
+
while (remaining.size > 0) {
|
|
9757
|
+
const group = [];
|
|
9758
|
+
for (const id of remaining) {
|
|
9759
|
+
const blockers = blockedByMap.get(id);
|
|
9760
|
+
if (!blockers || blockers.size === 0) {
|
|
9761
|
+
group.push(id);
|
|
9762
|
+
} else {
|
|
9763
|
+
const allAssigned = Array.from(blockers).every((b) => assigned.has(b));
|
|
9764
|
+
if (allAssigned) {
|
|
9765
|
+
group.push(id);
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
}
|
|
9769
|
+
if (group.length === 0) {
|
|
9770
|
+
const first = Array.from(remaining)[0];
|
|
9771
|
+
if (first) group.push(first);
|
|
9772
|
+
}
|
|
9773
|
+
for (const id of group) {
|
|
9774
|
+
assigned.add(id);
|
|
9775
|
+
remaining.delete(id);
|
|
9776
|
+
}
|
|
9777
|
+
groups.push(group);
|
|
9778
|
+
}
|
|
9779
|
+
return groups;
|
|
9780
|
+
}
|
|
9781
|
+
|
|
9782
|
+
// src/sdd/spec-versioning.ts
|
|
9783
|
+
var SpecVersioning = class {
|
|
9784
|
+
versions = /* @__PURE__ */ new Map();
|
|
9785
|
+
/** Record a new version of a spec. */
|
|
9786
|
+
recordVersion(spec, changeDescription) {
|
|
9787
|
+
const version = {
|
|
9788
|
+
version: spec.version,
|
|
9789
|
+
spec: { ...spec },
|
|
9790
|
+
timestamp: Date.now(),
|
|
9791
|
+
changeDescription
|
|
9792
|
+
};
|
|
9793
|
+
const history = this.versions.get(spec.id) ?? [];
|
|
9794
|
+
history.push(version);
|
|
9795
|
+
this.versions.set(spec.id, history);
|
|
9796
|
+
return version;
|
|
9797
|
+
}
|
|
9798
|
+
/** Get version history for a spec. */
|
|
9799
|
+
getHistory(specId) {
|
|
9800
|
+
return this.versions.get(specId) ?? [];
|
|
9801
|
+
}
|
|
9802
|
+
/** Get a specific version of a spec. */
|
|
9803
|
+
getVersion(specId, version) {
|
|
9804
|
+
const history = this.versions.get(specId) ?? [];
|
|
9805
|
+
return history.find((v) => v.version === version);
|
|
9806
|
+
}
|
|
9807
|
+
/** Get the latest version of a spec. */
|
|
9808
|
+
getLatest(specId) {
|
|
9809
|
+
const history = this.versions.get(specId) ?? [];
|
|
9810
|
+
return history[history.length - 1];
|
|
9811
|
+
}
|
|
9812
|
+
/** Compute diff between two versions of a spec. */
|
|
9813
|
+
diff(oldSpec, newSpec) {
|
|
9814
|
+
const oldReqs = new Map(oldSpec.requirements.map((r) => [r.id, r]));
|
|
9815
|
+
const newReqs = new Map(newSpec.requirements.map((r) => [r.id, r]));
|
|
9816
|
+
const added = [];
|
|
9817
|
+
const removed = [];
|
|
9818
|
+
const modified = [];
|
|
9819
|
+
for (const [id, newReq] of newReqs) {
|
|
9820
|
+
const oldReq = oldReqs.get(id);
|
|
9821
|
+
if (!oldReq) {
|
|
9822
|
+
added.push(newReq);
|
|
9823
|
+
} else {
|
|
9824
|
+
const changes = this.compareRequirements(oldReq, newReq);
|
|
9825
|
+
if (changes.length > 0) {
|
|
9826
|
+
modified.push({
|
|
9827
|
+
requirement: newReq,
|
|
9828
|
+
previousVersion: oldReq,
|
|
9829
|
+
changes
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9834
|
+
for (const [id, oldReq] of oldReqs) {
|
|
9835
|
+
if (!newReqs.has(id)) {
|
|
9836
|
+
removed.push(oldReq);
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
const parts = [];
|
|
9840
|
+
if (added.length > 0) parts.push(`${added.length} added`);
|
|
9841
|
+
if (removed.length > 0) parts.push(`${removed.length} removed`);
|
|
9842
|
+
if (modified.length > 0) parts.push(`${modified.length} modified`);
|
|
9843
|
+
return {
|
|
9844
|
+
added,
|
|
9845
|
+
removed,
|
|
9846
|
+
modified,
|
|
9847
|
+
summary: parts.length > 0 ? parts.join(", ") : "No changes"
|
|
9848
|
+
};
|
|
9849
|
+
}
|
|
9850
|
+
/**
|
|
9851
|
+
* Update a task graph incrementally based on spec changes.
|
|
9852
|
+
* - Added requirements → new tasks
|
|
9853
|
+
* - Removed requirements → remove tasks
|
|
9854
|
+
* - Modified requirements → update task descriptions
|
|
9855
|
+
* Returns the updated graph and list of changes made.
|
|
9856
|
+
*/
|
|
9857
|
+
updateTaskGraph(graph, oldSpec, newSpec) {
|
|
9858
|
+
const specDiff = this.diff(oldSpec, newSpec);
|
|
9859
|
+
const changes = [];
|
|
9860
|
+
const reqToTask = /* @__PURE__ */ new Map();
|
|
9861
|
+
for (const node of graph.nodes.values()) {
|
|
9862
|
+
if (node.specRequirementId) {
|
|
9863
|
+
reqToTask.set(node.specRequirementId, node);
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
for (const req of specDiff.removed) {
|
|
9867
|
+
const task = reqToTask.get(req.id);
|
|
9868
|
+
if (task) {
|
|
9869
|
+
graph.nodes.delete(task.id);
|
|
9870
|
+
graph.edges = graph.edges.filter((e) => e.from !== task.id && e.to !== task.id);
|
|
9871
|
+
changes.push(`Removed task: ${task.title}`);
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
for (const mod of specDiff.modified) {
|
|
9875
|
+
const task = reqToTask.get(mod.requirement.id);
|
|
9876
|
+
if (task) {
|
|
9877
|
+
task.title = mod.requirement.description;
|
|
9878
|
+
task.description = this.buildTaskDescription(mod.requirement);
|
|
9879
|
+
task.priority = mod.requirement.priority;
|
|
9880
|
+
task.updatedAt = Date.now();
|
|
9881
|
+
changes.push(`Updated task: ${task.title} (${mod.changes.join(", ")})`);
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
for (const req of specDiff.added) {
|
|
9885
|
+
const now = Date.now();
|
|
9886
|
+
const newTask = {
|
|
9887
|
+
id: crypto.randomUUID(),
|
|
9888
|
+
title: req.description,
|
|
9889
|
+
description: this.buildTaskDescription(req),
|
|
9890
|
+
type: this.mapReqType(req.type),
|
|
9891
|
+
priority: req.priority,
|
|
9892
|
+
status: "pending",
|
|
9893
|
+
specRequirementId: req.id,
|
|
9894
|
+
tags: [req.type, req.priority],
|
|
9895
|
+
createdAt: now,
|
|
9896
|
+
updatedAt: now
|
|
9897
|
+
};
|
|
9898
|
+
graph.nodes.set(newTask.id, newTask);
|
|
9899
|
+
graph.rootNodes.push(newTask.id);
|
|
9900
|
+
changes.push(`Added task: ${newTask.title}`);
|
|
9901
|
+
}
|
|
9902
|
+
graph.updatedAt = Date.now();
|
|
9903
|
+
return { graph, changes };
|
|
9904
|
+
}
|
|
9905
|
+
compareRequirements(old, current) {
|
|
9906
|
+
const changes = [];
|
|
9907
|
+
if (old.description !== current.description) changes.push("description");
|
|
9908
|
+
if (old.priority !== current.priority) changes.push("priority");
|
|
9909
|
+
if (old.type !== current.type) changes.push("type");
|
|
9910
|
+
if (JSON.stringify(old.acceptanceCriteria) !== JSON.stringify(current.acceptanceCriteria)) {
|
|
9911
|
+
changes.push("acceptance criteria");
|
|
9912
|
+
}
|
|
9913
|
+
if (JSON.stringify(old.blockedBy) !== JSON.stringify(current.blockedBy)) {
|
|
9914
|
+
changes.push("dependencies");
|
|
9915
|
+
}
|
|
9916
|
+
return changes;
|
|
9917
|
+
}
|
|
9918
|
+
buildTaskDescription(req) {
|
|
9919
|
+
const lines = [req.description, "", `**Type:** ${req.type}`, `**Priority:** ${req.priority}`];
|
|
9920
|
+
if (req.acceptanceCriteria.length > 0) {
|
|
9921
|
+
lines.push("", "**Acceptance Criteria:**");
|
|
9922
|
+
for (const ac of req.acceptanceCriteria) {
|
|
9923
|
+
lines.push(`- ${ac}`);
|
|
9924
|
+
}
|
|
9925
|
+
}
|
|
9926
|
+
return lines.join("\n");
|
|
9927
|
+
}
|
|
9928
|
+
mapReqType(type) {
|
|
9929
|
+
switch (type) {
|
|
9930
|
+
case "functional":
|
|
9931
|
+
return "feature";
|
|
9932
|
+
case "security":
|
|
9933
|
+
return "feature";
|
|
9934
|
+
case "performance":
|
|
9935
|
+
return "feature";
|
|
9936
|
+
case "ux":
|
|
9937
|
+
return "feature";
|
|
9938
|
+
default:
|
|
9939
|
+
return "feature";
|
|
9940
|
+
}
|
|
9941
|
+
}
|
|
9942
|
+
};
|
|
9943
|
+
|
|
9944
|
+
// src/sdd/auto-executor.ts
|
|
9945
|
+
var AutoExecutor = class {
|
|
9946
|
+
opts;
|
|
9947
|
+
stopped = false;
|
|
9948
|
+
retryMap = /* @__PURE__ */ new Map();
|
|
9949
|
+
constructor(opts) {
|
|
9950
|
+
this.opts = opts;
|
|
9951
|
+
}
|
|
9952
|
+
/**
|
|
9953
|
+
* Execute all tasks in the graph, respecting dependencies.
|
|
9954
|
+
*/
|
|
9955
|
+
async execute(graph, spec) {
|
|
9956
|
+
this.stopped = false;
|
|
9957
|
+
this.retryMap.clear();
|
|
9958
|
+
const startTime = Date.now();
|
|
9959
|
+
const critical = analyzeCriticalPath(graph);
|
|
9960
|
+
let completed = 0;
|
|
9961
|
+
let failed = 0;
|
|
9962
|
+
const skipped = 0;
|
|
9963
|
+
let retried = 0;
|
|
9964
|
+
while (!this.stopped) {
|
|
9965
|
+
const readyTasks = this.getReadyTasks(graph);
|
|
9966
|
+
if (readyTasks.length === 0) {
|
|
9967
|
+
const allDone = Array.from(graph.nodes.values()).every(
|
|
9968
|
+
(n) => n.status === "completed" || n.status === "failed"
|
|
9969
|
+
);
|
|
9970
|
+
if (allDone) break;
|
|
9971
|
+
const hasDeadlock = this.detectDeadlock(graph);
|
|
9972
|
+
if (hasDeadlock) break;
|
|
9973
|
+
break;
|
|
9974
|
+
}
|
|
9975
|
+
const batch = readyTasks.slice(0, this.opts.maxConcurrent ?? 1);
|
|
9976
|
+
const results = await Promise.allSettled(
|
|
9977
|
+
batch.map((task) => this.executeTaskWithRetry(task, graph, spec))
|
|
9978
|
+
);
|
|
9979
|
+
for (let i = 0; i < results.length; i++) {
|
|
9980
|
+
const result = results[i];
|
|
9981
|
+
const task = batch[i];
|
|
9982
|
+
if (!result || !task) continue;
|
|
9983
|
+
if (result.status === "fulfilled") {
|
|
9984
|
+
const { result: execResult, retries } = result.value;
|
|
9985
|
+
if (execResult.success) {
|
|
9986
|
+
this.opts.tracker.updateNodeStatus(task.id, "completed");
|
|
9987
|
+
completed++;
|
|
9988
|
+
if (retries > 0) retried++;
|
|
9989
|
+
this.opts.onTaskComplete?.(task, execResult);
|
|
9990
|
+
} else if (execResult.retry) {
|
|
9991
|
+
retried++;
|
|
9992
|
+
} else {
|
|
9993
|
+
this.opts.tracker.updateNodeStatus(task.id, "failed", execResult.error);
|
|
9994
|
+
failed++;
|
|
9995
|
+
}
|
|
9996
|
+
} else {
|
|
9997
|
+
this.opts.tracker.updateNodeStatus(task.id, "failed", String(result.reason));
|
|
9998
|
+
failed++;
|
|
9999
|
+
this.opts.onTaskFail?.(task, result.reason, 0);
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
const duration = Date.now() - startTime;
|
|
10004
|
+
const summary = {
|
|
10005
|
+
total: graph.nodes.size,
|
|
10006
|
+
completed,
|
|
10007
|
+
failed,
|
|
10008
|
+
skipped,
|
|
10009
|
+
retried,
|
|
10010
|
+
duration,
|
|
10011
|
+
criticalPath: critical.criticalPath
|
|
10012
|
+
};
|
|
10013
|
+
this.opts.onDone?.(summary);
|
|
10014
|
+
return summary;
|
|
10015
|
+
}
|
|
10016
|
+
/** Stop execution. */
|
|
10017
|
+
stop() {
|
|
10018
|
+
this.stopped = true;
|
|
10019
|
+
}
|
|
10020
|
+
/** Get tasks that are ready to execute (all dependencies completed). */
|
|
10021
|
+
getReadyTasks(graph) {
|
|
10022
|
+
const ready = [];
|
|
10023
|
+
for (const node of graph.nodes.values()) {
|
|
10024
|
+
if (node.status !== "pending") continue;
|
|
10025
|
+
const blockers = graph.edges.filter((e) => e.type === "depends_on" && e.from === node.id).map((e) => graph.nodes.get(e.to)).filter(Boolean);
|
|
10026
|
+
const allBlockersDone = blockers.every((b) => b.status === "completed");
|
|
10027
|
+
if (allBlockersDone) {
|
|
10028
|
+
ready.push(node);
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
10032
|
+
ready.sort((a, b) => (priorityOrder[a.priority] ?? 4) - (priorityOrder[b.priority] ?? 4));
|
|
10033
|
+
return ready;
|
|
10034
|
+
}
|
|
10035
|
+
/** Execute a single task with retry logic. */
|
|
10036
|
+
async executeTaskWithRetry(task, graph, spec) {
|
|
10037
|
+
const maxRetries = this.opts.maxRetries ?? 2;
|
|
10038
|
+
let retryCount = this.retryMap.get(task.id) ?? 0;
|
|
10039
|
+
while (retryCount <= maxRetries) {
|
|
10040
|
+
this.opts.tracker.updateNodeStatus(task.id, "in_progress");
|
|
10041
|
+
this.opts.onTaskStart?.(task);
|
|
10042
|
+
const dependencies = this.getTaskDependencies(task.id, graph);
|
|
10043
|
+
const dependents = this.getTaskDependents(task.id, graph);
|
|
10044
|
+
const context = {
|
|
10045
|
+
spec,
|
|
10046
|
+
graph,
|
|
10047
|
+
task,
|
|
10048
|
+
dependencies,
|
|
10049
|
+
dependents,
|
|
10050
|
+
retryCount
|
|
10051
|
+
};
|
|
10052
|
+
try {
|
|
10053
|
+
const result = await this.opts.executeTask(task, context);
|
|
10054
|
+
if (result.success) {
|
|
10055
|
+
const retriesForTask = this.retryMap.get(task.id) ?? 0;
|
|
10056
|
+
this.retryMap.delete(task.id);
|
|
10057
|
+
return { result, retries: retriesForTask };
|
|
10058
|
+
}
|
|
10059
|
+
if (result.retry && retryCount < maxRetries) {
|
|
10060
|
+
retryCount++;
|
|
10061
|
+
this.retryMap.set(task.id, retryCount);
|
|
10062
|
+
this.opts.tracker.updateNodeStatus(task.id, "pending");
|
|
10063
|
+
continue;
|
|
10064
|
+
}
|
|
10065
|
+
return { result, retries: retryCount };
|
|
10066
|
+
} catch (error) {
|
|
10067
|
+
if (retryCount < maxRetries) {
|
|
10068
|
+
retryCount++;
|
|
10069
|
+
this.retryMap.set(task.id, retryCount);
|
|
10070
|
+
this.opts.tracker.updateNodeStatus(task.id, "pending");
|
|
10071
|
+
this.opts.onTaskFail?.(task, error, retryCount);
|
|
10072
|
+
continue;
|
|
10073
|
+
}
|
|
10074
|
+
return {
|
|
10075
|
+
result: {
|
|
10076
|
+
success: false,
|
|
10077
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10078
|
+
},
|
|
10079
|
+
retries: retryCount
|
|
10080
|
+
};
|
|
10081
|
+
}
|
|
10082
|
+
}
|
|
10083
|
+
return { result: { success: false, error: "Max retries exceeded" }, retries: retryCount };
|
|
10084
|
+
}
|
|
10085
|
+
/** Get tasks that this task depends on. */
|
|
10086
|
+
getTaskDependencies(taskId, graph) {
|
|
10087
|
+
return graph.edges.filter((e) => e.type === "depends_on" && e.from === taskId).map((e) => graph.nodes.get(e.to)).filter(Boolean);
|
|
10088
|
+
}
|
|
10089
|
+
/** Get tasks that depend on this task. */
|
|
10090
|
+
getTaskDependents(taskId, graph) {
|
|
10091
|
+
return graph.edges.filter((e) => e.type === "depends_on" && e.to === taskId).map((e) => graph.nodes.get(e.from)).filter(Boolean);
|
|
10092
|
+
}
|
|
10093
|
+
/** Detect deadlock: all remaining tasks are blocked by failed tasks. */
|
|
10094
|
+
detectDeadlock(graph) {
|
|
10095
|
+
const remaining = Array.from(graph.nodes.values()).filter(
|
|
10096
|
+
(n) => n.status === "pending" || n.status === "blocked"
|
|
10097
|
+
);
|
|
10098
|
+
if (remaining.length === 0) return false;
|
|
10099
|
+
return remaining.every((node) => {
|
|
10100
|
+
const blockers = graph.edges.filter((e) => e.type === "depends_on" && e.from === node.id).map((e) => graph.nodes.get(e.to)).filter(Boolean);
|
|
10101
|
+
return blockers.some((b) => b.status === "failed");
|
|
10102
|
+
});
|
|
10103
|
+
}
|
|
10104
|
+
};
|
|
10105
|
+
function createAutoExecutor(opts) {
|
|
10106
|
+
return new AutoExecutor({
|
|
10107
|
+
tracker: opts.tracker,
|
|
10108
|
+
events: opts.events,
|
|
10109
|
+
executeTask: opts.executeTask,
|
|
10110
|
+
maxConcurrent: opts.maxConcurrent,
|
|
10111
|
+
maxRetries: opts.maxRetries
|
|
10112
|
+
});
|
|
10113
|
+
}
|
|
10114
|
+
|
|
10115
|
+
// src/observability/metrics.ts
|
|
10116
|
+
var RESERVOIR_SIZE = 1024;
|
|
10117
|
+
function labelKey(labels) {
|
|
10118
|
+
if (!labels) return "";
|
|
10119
|
+
const keys = Object.keys(labels).sort();
|
|
10120
|
+
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
10121
|
+
}
|
|
10122
|
+
function quantile(sorted, q) {
|
|
10123
|
+
if (sorted.length === 0) return 0;
|
|
10124
|
+
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
10125
|
+
return sorted[idx] ?? 0;
|
|
10126
|
+
}
|
|
10127
|
+
var InMemoryMetricsSink = class {
|
|
10128
|
+
counters = /* @__PURE__ */ new Map();
|
|
10129
|
+
gauges = /* @__PURE__ */ new Map();
|
|
10130
|
+
histograms = /* @__PURE__ */ new Map();
|
|
10131
|
+
counter(name, value = 1, labels) {
|
|
10132
|
+
const series = this.getOrCreate(this.counters, name);
|
|
10133
|
+
const key = labelKey(labels);
|
|
10134
|
+
const state = series.get(key) ?? { value: 0 };
|
|
10135
|
+
state.value += value;
|
|
10136
|
+
series.set(key, state);
|
|
10137
|
+
}
|
|
10138
|
+
gauge(name, value, labels) {
|
|
10139
|
+
const series = this.getOrCreate(this.gauges, name);
|
|
10140
|
+
series.set(labelKey(labels), { value });
|
|
10141
|
+
}
|
|
10142
|
+
histogram(name, value, labels) {
|
|
10143
|
+
const series = this.getOrCreate(this.histograms, name);
|
|
10144
|
+
const key = labelKey(labels);
|
|
10145
|
+
let state = series.get(key);
|
|
10146
|
+
if (!state) {
|
|
8277
10147
|
state = { count: 0, sum: 0, min: value, max: value, samples: [] };
|
|
8278
10148
|
series.set(key, state);
|
|
8279
10149
|
}
|
|
@@ -8586,7 +10456,7 @@ async function startMetricsServer(opts) {
|
|
|
8586
10456
|
const tls = opts.tls;
|
|
8587
10457
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
8588
10458
|
const host = opts.host ?? "127.0.0.1";
|
|
8589
|
-
const
|
|
10459
|
+
const path17 = opts.path ?? "/metrics";
|
|
8590
10460
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
8591
10461
|
const healthRegistry = opts.healthRegistry;
|
|
8592
10462
|
const listener = (req, res) => {
|
|
@@ -8596,7 +10466,7 @@ async function startMetricsServer(opts) {
|
|
|
8596
10466
|
return;
|
|
8597
10467
|
}
|
|
8598
10468
|
const url = req.url.split("?")[0];
|
|
8599
|
-
if (url ===
|
|
10469
|
+
if (url === path17) {
|
|
8600
10470
|
let body;
|
|
8601
10471
|
try {
|
|
8602
10472
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -8660,7 +10530,7 @@ async function startMetricsServer(opts) {
|
|
|
8660
10530
|
const protocol = useHttps ? "https" : "http";
|
|
8661
10531
|
return {
|
|
8662
10532
|
port: boundPort,
|
|
8663
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
10533
|
+
url: `${protocol}://${host}:${boundPort}${path17}`,
|
|
8664
10534
|
close: () => new Promise((resolve2, reject) => {
|
|
8665
10535
|
server.close((err) => err ? reject(err) : resolve2());
|
|
8666
10536
|
})
|
|
@@ -8995,13 +10865,17 @@ function createContextManagerTool(opts = {}) {
|
|
|
8995
10865
|
};
|
|
8996
10866
|
switch (input.action) {
|
|
8997
10867
|
case "check": {
|
|
10868
|
+
const estimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens, messages: beforeTokens, systemPrompt: 0, tools: 0 };
|
|
8998
10869
|
return {
|
|
8999
10870
|
action: "check",
|
|
9000
|
-
beforeTokens,
|
|
10871
|
+
beforeTokens: estimate.total,
|
|
9001
10872
|
messageCount: messages.length,
|
|
9002
10873
|
notes: JSON.stringify({
|
|
9003
10874
|
messages: messages.length,
|
|
9004
|
-
tokens:
|
|
10875
|
+
tokens: estimate.total,
|
|
10876
|
+
msgTokens: estimate.messages,
|
|
10877
|
+
sysTokens: estimate.systemPrompt,
|
|
10878
|
+
toolTokens: estimate.tools,
|
|
9005
10879
|
readFiles: ctx.readFiles.size,
|
|
9006
10880
|
todos: ctx.todos.length,
|
|
9007
10881
|
inProgress: ctx.todos.filter((t) => t.status === "in_progress").length
|
|
@@ -9162,7 +11036,6 @@ var githubServer = () => ({
|
|
|
9162
11036
|
transport: "stdio",
|
|
9163
11037
|
command: "npx",
|
|
9164
11038
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
9165
|
-
env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN ?? "" },
|
|
9166
11039
|
permission: "confirm"
|
|
9167
11040
|
});
|
|
9168
11041
|
var context7Server = () => ({
|
|
@@ -9178,7 +11051,6 @@ var braveSearchServer = () => ({
|
|
|
9178
11051
|
transport: "stdio",
|
|
9179
11052
|
command: "npx",
|
|
9180
11053
|
args: ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
9181
|
-
env: { BRAVE_SEARCH_API_KEY: process.env.BRAVE_SEARCH_API_KEY ?? "" },
|
|
9182
11054
|
permission: "confirm"
|
|
9183
11055
|
});
|
|
9184
11056
|
var blockServer = () => ({
|
|
@@ -9195,7 +11067,6 @@ var everArtServer = () => ({
|
|
|
9195
11067
|
transport: "stdio",
|
|
9196
11068
|
command: "npx",
|
|
9197
11069
|
args: ["-y", "@modelcontextprotocol/server-everart"],
|
|
9198
|
-
env: { EVERART_API_KEY: process.env.EVERART_API_KEY ?? "" },
|
|
9199
11070
|
permission: "confirm"
|
|
9200
11071
|
});
|
|
9201
11072
|
var slackServer = () => ({
|
|
@@ -9204,10 +11075,6 @@ var slackServer = () => ({
|
|
|
9204
11075
|
transport: "stdio",
|
|
9205
11076
|
command: "npx",
|
|
9206
11077
|
args: ["-y", "@modelcontextprotocol/server-slack"],
|
|
9207
|
-
env: {
|
|
9208
|
-
SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN ?? "",
|
|
9209
|
-
SLACK_TEAM_ID: process.env.SLACK_TEAM_ID ?? ""
|
|
9210
|
-
},
|
|
9211
11078
|
permission: "confirm"
|
|
9212
11079
|
});
|
|
9213
11080
|
var awsServer = () => ({
|
|
@@ -9224,7 +11091,6 @@ var googleMapsServer = () => ({
|
|
|
9224
11091
|
transport: "stdio",
|
|
9225
11092
|
command: "npx",
|
|
9226
11093
|
args: ["-y", "@modelcontextprotocol/server-google-maps"],
|
|
9227
|
-
env: { GOOGLE_MAPS_API_KEY: process.env.GOOGLE_MAPS_API_KEY ?? "" },
|
|
9228
11094
|
permission: "confirm"
|
|
9229
11095
|
});
|
|
9230
11096
|
var sentinelServer = () => ({
|
|
@@ -9241,10 +11107,7 @@ var zaiVisionServer = () => ({
|
|
|
9241
11107
|
transport: "stdio",
|
|
9242
11108
|
command: "npx",
|
|
9243
11109
|
args: ["-y", "@z_ai/mcp-server@latest"],
|
|
9244
|
-
env: {
|
|
9245
|
-
Z_AI_API_KEY: process.env.Z_AI_API_KEY ?? "",
|
|
9246
|
-
Z_AI_MODE: process.env.Z_AI_MODE ?? "ZAI"
|
|
9247
|
-
},
|
|
11110
|
+
env: { Z_AI_MODE: "ZAI" },
|
|
9248
11111
|
allowedTools: [
|
|
9249
11112
|
"image_analysis",
|
|
9250
11113
|
"extract_text_from_screenshot",
|
|
@@ -9262,10 +11125,9 @@ var miniMaxVisionServer = () => ({
|
|
|
9262
11125
|
command: "uvx",
|
|
9263
11126
|
args: ["minimax-coding-plan-mcp", "-y"],
|
|
9264
11127
|
env: {
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
MINIMAX_API_RESOURCE_MODE: process.env.MINIMAX_API_RESOURCE_MODE ?? "url"
|
|
11128
|
+
MINIMAX_MCP_BASE_PATH: "./.wrongstack/minimax-output",
|
|
11129
|
+
MINIMAX_API_HOST: "https://api.minimax.io",
|
|
11130
|
+
MINIMAX_API_RESOURCE_MODE: "url"
|
|
9269
11131
|
},
|
|
9270
11132
|
allowedTools: ["understand_image"],
|
|
9271
11133
|
permission: "auto"
|
|
@@ -9285,6 +11147,6 @@ var allServers = () => ({
|
|
|
9285
11147
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
9286
11148
|
});
|
|
9287
11149
|
|
|
9288
|
-
export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents, zaiVisionServer };
|
|
11150
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, getTemplate, githubServer, googleMapsServer, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
9289
11151
|
//# sourceMappingURL=index.js.map
|
|
9290
11152
|
//# sourceMappingURL=index.js.map
|