@wrongstack/core 0.4.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/{agent-bridge-DaOnnHNW.d.ts → agent-bridge-EiUFe3if.d.ts} +1 -1
  2. package/dist/{compactor-CFky6JKM.d.ts → compactor-BP4xhKVi.d.ts} +1 -1
  3. package/dist/{config-RlhKLjme.d.ts → config-BOD_HQBw.d.ts} +1 -1
  4. package/dist/{context-B1kBj1lY.d.ts → context-PH4DvBZV.d.ts} +60 -1
  5. package/dist/coordination/index.d.ts +10 -891
  6. package/dist/coordination/index.js +500 -238
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +20 -19
  9. package/dist/defaults/index.js +2210 -264
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/director-state-CVzkjKRZ.d.ts +539 -0
  12. package/dist/{events-BBaKeMfb.d.ts → events-oxn-Wkub.d.ts} +58 -1
  13. package/dist/execution/index.d.ts +19 -13
  14. package/dist/execution/index.js +15 -6
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/index-CcbWbcpy.d.ts +899 -0
  18. package/dist/{index-meJRuQtc.d.ts → index-a12jc7-r.d.ts} +8 -6
  19. package/dist/index.d.ts +33 -28
  20. package/dist/index.js +3798 -1237
  21. package/dist/index.js.map +1 -1
  22. package/dist/infrastructure/index.d.ts +6 -6
  23. package/dist/infrastructure/index.js +67 -2
  24. package/dist/infrastructure/index.js.map +1 -1
  25. package/dist/kernel/index.d.ts +10 -9
  26. package/dist/kernel/index.js.map +1 -1
  27. package/dist/{mcp-servers-Cf4-bJnd.d.ts → mcp-servers-uPmBxZ1B.d.ts} +18 -3
  28. package/dist/models/index.d.ts +2 -2
  29. package/dist/models/index.js +106 -0
  30. package/dist/models/index.js.map +1 -1
  31. package/dist/{multi-agent-D5m66hzB.d.ts → multi-agent-D6S4Z7H8.d.ts} +71 -16
  32. package/dist/observability/index.d.ts +2 -2
  33. package/dist/{path-resolver-Bg4OP5fi.d.ts → path-resolver-CprD5DhS.d.ts} +9 -2
  34. package/dist/{provider-runner-CU9gnU2E.d.ts → provider-runner-DGisz_lG.d.ts} +3 -3
  35. package/dist/{skill-DayhFUBb.d.ts → retry-policy-DUJ8-Qc_.d.ts} +2 -28
  36. package/dist/sdd/index.d.ts +419 -5
  37. package/dist/sdd/index.js +1580 -1
  38. package/dist/sdd/index.js.map +1 -1
  39. package/dist/{secret-scrubber-DyUAWS09.d.ts → secret-scrubber-CB11A2P7.d.ts} +1 -1
  40. package/dist/{secret-scrubber-Dyh5LVYL.d.ts → secret-scrubber-EqFa0SyI.d.ts} +1 -1
  41. package/dist/security/index.d.ts +8 -4
  42. package/dist/security/index.js +8 -0
  43. package/dist/security/index.js.map +1 -1
  44. package/dist/{selector-DBEiwXBk.d.ts → selector-yqyxt-Ii.d.ts} +1 -1
  45. package/dist/{session-reader-D-z0AgHs.d.ts → session-reader-1tOyoY1s.d.ts} +1 -1
  46. package/dist/session-rewinder-C9HnMkhP.d.ts +23 -0
  47. package/dist/skill-CxuWrsKK.d.ts +29 -0
  48. package/dist/skills/index.d.ts +136 -0
  49. package/dist/skills/index.js +478 -0
  50. package/dist/skills/index.js.map +1 -0
  51. package/dist/storage/index.d.ts +21 -534
  52. package/dist/storage/index.js +246 -3
  53. package/dist/storage/index.js.map +1 -1
  54. package/dist/{system-prompt-DNetCecu.d.ts → system-prompt-BJlzKGO6.d.ts} +1 -1
  55. package/dist/{tool-executor-B5bgmEgE.d.ts → tool-executor-B6kRcWeF.d.ts} +4 -4
  56. package/dist/types/index.d.ts +17 -15
  57. package/dist/types/index.js +116 -0
  58. package/dist/types/index.js.map +1 -1
  59. package/dist/utils/index.d.ts +35 -2
  60. package/dist/utils/index.js +49 -1
  61. package/dist/utils/index.js.map +1 -1
  62. package/package.json +5 -1
  63. package/skills/sdd/SKILL.md +93 -6
  64. package/skills/skill-creator/SKILL.md +98 -0
@@ -309,7 +309,7 @@ var DefaultSessionStore = class {
309
309
  );
310
310
  }
311
311
  try {
312
- return new FileSessionWriter(id, handle, startedAt, meta, { dir: this.dir, filePath: file });
312
+ return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: this.dir, filePath: file });
313
313
  } catch (err) {
314
314
  await handle.close().catch(() => {
315
315
  });
@@ -337,6 +337,7 @@ var DefaultSessionStore = class {
337
337
  model: data.metadata.model,
338
338
  provider: data.metadata.provider
339
339
  },
340
+ this.events,
340
341
  { resumed: true, dir: this.dir, filePath: file }
341
342
  );
342
343
  return { writer, data };
@@ -500,11 +501,12 @@ var DefaultSessionStore = class {
500
501
  }
501
502
  };
502
503
  var FileSessionWriter = class {
503
- constructor(id, handle, startedAt, meta, opts = {}) {
504
+ constructor(id, handle, startedAt, meta, events, opts = {}) {
504
505
  this.id = id;
505
506
  this.handle = handle;
506
507
  this.startedAt = startedAt;
507
508
  this.meta = meta;
509
+ this.events = events;
508
510
  this.resumed = opts.resumed ?? false;
509
511
  this.manifestFile = opts.dir ? path3.join(opts.dir, `${id}.summary.json`) : "";
510
512
  this.filePath = opts.filePath ?? "";
@@ -521,6 +523,7 @@ var FileSessionWriter = class {
521
523
  handle;
522
524
  startedAt;
523
525
  meta;
526
+ events;
524
527
  closed = false;
525
528
  closing = false;
526
529
  manifestFile;
@@ -538,6 +541,13 @@ var FileSessionWriter = class {
538
541
  resumed;
539
542
  appendFailCount = 0;
540
543
  lastAppendWarnAt = 0;
544
+ // Rewind support: track pending file changes and prompt index
545
+ promptIndex = 0;
546
+ pendingFileSnapshots = [];
547
+ // Register a pending file change. Call recordFileChange() from tools.
548
+ recordFileChange(input) {
549
+ this.pendingFileSnapshots.push(input);
550
+ }
541
551
  async writeSessionStartLazy() {
542
552
  const record = `${JSON.stringify({
543
553
  type: this.resumed ? "session_resumed" : "session_start",
@@ -613,6 +623,87 @@ var FileSessionWriter = class {
613
623
  } catch {
614
624
  }
615
625
  }
626
+ async writeCheckpoint(promptIndex, promptPreview) {
627
+ const fileCount = this.pendingFileSnapshots.length;
628
+ if (fileCount > 0) {
629
+ await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
630
+ this.pendingFileSnapshots = [];
631
+ }
632
+ this.promptIndex = promptIndex + 1;
633
+ await this.append({
634
+ type: "checkpoint",
635
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
636
+ promptIndex,
637
+ promptPreview
638
+ });
639
+ this.events?.emit("checkpoint.written", {
640
+ promptIndex,
641
+ promptPreview,
642
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
643
+ fileCount
644
+ });
645
+ }
646
+ async writeFileSnapshot(promptIndex, files) {
647
+ await this.append({
648
+ type: "file_snapshot",
649
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
650
+ promptIndex,
651
+ files
652
+ });
653
+ }
654
+ async truncateToCheckpoint(targetPromptIndex) {
655
+ if (!this.filePath) return 0;
656
+ const raw = await fsp.readFile(this.filePath, "utf8");
657
+ const lines = raw.split("\n");
658
+ const kept = [];
659
+ let removedCount = 0;
660
+ let targetCheckpointLine = -1;
661
+ let afterTarget = false;
662
+ for (let i = 0; i < lines.length; i++) {
663
+ const line = lines[i];
664
+ if (!line.trim()) continue;
665
+ let event;
666
+ try {
667
+ event = JSON.parse(line);
668
+ } catch {
669
+ kept.push(line);
670
+ continue;
671
+ }
672
+ if (event.type === "checkpoint") {
673
+ if (event.promptIndex === targetPromptIndex) {
674
+ targetCheckpointLine = kept.length;
675
+ afterTarget = true;
676
+ } else if (event.promptIndex > targetPromptIndex) {
677
+ afterTarget = true;
678
+ }
679
+ }
680
+ if (event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
681
+ removedCount++;
682
+ } else if (event.promptIndex === void 0) {
683
+ if (!afterTarget || targetCheckpointLine === -1) {
684
+ kept.push(line);
685
+ } else {
686
+ removedCount++;
687
+ }
688
+ } else {
689
+ kept.push(line);
690
+ }
691
+ }
692
+ const truncated = kept.join("\n");
693
+ await fsp.writeFile(this.filePath, truncated + "\n", "utf8");
694
+ await this.append({
695
+ type: "rewound",
696
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
697
+ toPromptIndex: targetPromptIndex,
698
+ revertedFiles: []
699
+ });
700
+ this.events?.emit("session.rewound", {
701
+ toPromptIndex: targetPromptIndex,
702
+ revertedFiles: [],
703
+ removedEvents: removedCount
704
+ });
705
+ return removedCount;
706
+ }
616
707
  };
617
708
  function userInputTitle(content) {
618
709
  if (typeof content === "string") return content.slice(0, 60);
@@ -2587,6 +2678,14 @@ var DefaultPermissionPolicy = class {
2587
2678
  setPromptDelegate(delegate) {
2588
2679
  this.promptDelegate = delegate;
2589
2680
  }
2681
+ /** Toggle YOLO (auto-approve) mode at runtime. */
2682
+ setYolo(enabled) {
2683
+ this.yolo = enabled;
2684
+ }
2685
+ /** Check whether YOLO mode is currently active. */
2686
+ getYolo() {
2687
+ return this.yolo;
2688
+ }
2590
2689
  async reload() {
2591
2690
  try {
2592
2691
  const raw = await fsp.readFile(this.trustFile, "utf8");
@@ -3051,6 +3150,9 @@ var DefaultSkillLoader = class {
3051
3150
  }
3052
3151
  return entries;
3053
3152
  }
3153
+ invalidateCache() {
3154
+ this.cache = void 0;
3155
+ }
3054
3156
  async readBody(name) {
3055
3157
  const m = await this.find(name);
3056
3158
  if (!m) throw new Error(`Skill "${name}" not found`);
@@ -3436,6 +3538,54 @@ function estimateToolResultTokens(content) {
3436
3538
  function estimateTextTokens(text) {
3437
3539
  return RoughTokenEstimate(text);
3438
3540
  }
3541
+ function estimateToolDefTokens(tool) {
3542
+ return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
3543
+ }
3544
+ function estimateRequestTokens(messages, systemPrompt, tools) {
3545
+ let messagesTokens = 0;
3546
+ if (typeof messages === "string") {
3547
+ messagesTokens = RoughTokenEstimate(messages);
3548
+ } else if (Array.isArray(messages)) {
3549
+ for (const m of messages) {
3550
+ if (typeof m === "object" && m !== null && "content" in m) {
3551
+ const content = m.content;
3552
+ if (typeof content === "string") {
3553
+ messagesTokens += RoughTokenEstimate(content);
3554
+ } else if (Array.isArray(content)) {
3555
+ for (const b of content) {
3556
+ if (typeof b === "object" && b !== null) {
3557
+ if (b.type === "text") {
3558
+ messagesTokens += RoughTokenEstimate(b.text);
3559
+ } else {
3560
+ messagesTokens += RoughTokenEstimate(JSON.stringify(b));
3561
+ }
3562
+ }
3563
+ }
3564
+ }
3565
+ }
3566
+ }
3567
+ }
3568
+ let systemTokens = 0;
3569
+ if (typeof systemPrompt === "string") {
3570
+ systemTokens = RoughTokenEstimate(systemPrompt);
3571
+ } else if (Array.isArray(systemPrompt)) {
3572
+ for (const b of systemPrompt) {
3573
+ if (typeof b === "object" && b !== null && b.type === "text") {
3574
+ systemTokens += RoughTokenEstimate(b.text);
3575
+ }
3576
+ }
3577
+ }
3578
+ let toolsTokens = 0;
3579
+ for (const t of tools) {
3580
+ toolsTokens += estimateToolDefTokens(t);
3581
+ }
3582
+ return {
3583
+ messages: messagesTokens,
3584
+ systemPrompt: systemTokens,
3585
+ tools: toolsTokens,
3586
+ total: messagesTokens + systemTokens + toolsTokens
3587
+ };
3588
+ }
3439
3589
 
3440
3590
  // src/execution/compactor.ts
3441
3591
  var HybridCompactor = class {
@@ -4183,11 +4333,12 @@ Summarize the following message range:`;
4183
4333
  var AutoCompactionMiddleware = class {
4184
4334
  name = "AutoCompaction";
4185
4335
  compactor;
4336
+ estimator;
4186
4337
  warnThreshold;
4187
4338
  softThreshold;
4188
4339
  hardThreshold;
4189
- maxContext;
4190
- estimator;
4340
+ /** Writable so model-switch can update the denominator without re-registering the middleware. */
4341
+ _maxContext;
4191
4342
  aggressiveOn;
4192
4343
  events;
4193
4344
  failureMode;
@@ -4206,7 +4357,7 @@ var AutoCompactionMiddleware = class {
4206
4357
  constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
4207
4358
  const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
4208
4359
  this.compactor = compactor;
4209
- this.maxContext = maxContext;
4360
+ this._maxContext = maxContext;
4210
4361
  this.estimator = estimator;
4211
4362
  this.warnThreshold = thresholds.warn;
4212
4363
  this.softThreshold = thresholds.soft;
@@ -4216,10 +4367,15 @@ var AutoCompactionMiddleware = class {
4216
4367
  this.failureMode = opts.failureMode ?? "throw_on_hard";
4217
4368
  this.policyProvider = opts.policyProvider;
4218
4369
  }
4370
+ /** Allow callers (e.g. model-switch in WebUI) to update the context window
4371
+ * denominator when the active model changes. */
4372
+ setMaxContext(maxContext) {
4373
+ this._maxContext = maxContext;
4374
+ }
4219
4375
  handler() {
4220
4376
  return async (ctx, next) => {
4221
4377
  const tokens = this.estimator(ctx);
4222
- const load = tokens / this.maxContext;
4378
+ const load = tokens / this._maxContext;
4223
4379
  const policy = this.policyProvider?.(ctx);
4224
4380
  const thresholds = policy?.thresholds ?? {
4225
4381
  warn: this.warnThreshold,
@@ -4248,7 +4404,7 @@ var AutoCompactionMiddleware = class {
4248
4404
  aggressive,
4249
4405
  level: pressure.level,
4250
4406
  tokens: pressure.tokens,
4251
- maxContext: this.maxContext,
4407
+ maxContext: this._maxContext,
4252
4408
  load: pressure.load,
4253
4409
  fatal
4254
4410
  });
@@ -4260,7 +4416,7 @@ var AutoCompactionMiddleware = class {
4260
4416
  context: {
4261
4417
  level: pressure.level,
4262
4418
  tokens: pressure.tokens,
4263
- maxContext: this.maxContext
4419
+ maxContext: this._maxContext
4264
4420
  },
4265
4421
  cause: err
4266
4422
  });
@@ -5008,7 +5164,9 @@ var FleetBus = class {
5008
5164
  "session.damaged",
5009
5165
  "compaction.fired",
5010
5166
  "compaction.failed",
5011
- "token.threshold"
5167
+ "token.threshold",
5168
+ // Subagent hit a soft budget limit — coordinator can extend or stop.
5169
+ "budget.threshold_reached"
5012
5170
  ];
5013
5171
  const offs = [];
5014
5172
  for (const t of FORWARDED_TYPES) {
@@ -5169,6 +5327,21 @@ var BudgetExceededError = class extends Error {
5169
5327
  this.observed = observed;
5170
5328
  }
5171
5329
  };
5330
+ var BudgetThresholdSignal = class extends Error {
5331
+ kind;
5332
+ limit;
5333
+ used;
5334
+ /** Resolves to 'extend' (with optional new limits) or 'stop' */
5335
+ decision;
5336
+ constructor(kind, limit, used, decision) {
5337
+ super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
5338
+ this.name = "BudgetThresholdSignal";
5339
+ this.kind = kind;
5340
+ this.limit = limit;
5341
+ this.used = used;
5342
+ this.decision = decision;
5343
+ }
5344
+ };
5172
5345
  var SubagentBudget = class {
5173
5346
  limits;
5174
5347
  iterations = 0;
@@ -5177,22 +5350,111 @@ var SubagentBudget = class {
5177
5350
  tokenOutput = 0;
5178
5351
  costUsd = 0;
5179
5352
  startTime = null;
5353
+ _onThreshold;
5354
+ /**
5355
+ * Injected by the runner when wiring the budget to its EventBus.
5356
+ * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
5357
+ */
5358
+ _events;
5359
+ /**
5360
+ * Optional callback for soft-limit handling. When set, the budget will
5361
+ * call this instead of throwing when a limit is first reached. The
5362
+ * handler decides whether to throw, continue, or ask the coordinator.
5363
+ */
5364
+ get onThreshold() {
5365
+ return this._onThreshold;
5366
+ }
5367
+ set onThreshold(fn) {
5368
+ this._onThreshold = fn;
5369
+ }
5180
5370
  constructor(limits = {}) {
5181
5371
  this.limits = Object.freeze({ ...limits });
5182
5372
  }
5183
5373
  start() {
5184
5374
  this.startTime = Date.now();
5185
5375
  }
5376
+ /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
5377
+ isNearLimit() {
5378
+ const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
5379
+ if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
5380
+ if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
5381
+ if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
5382
+ if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
5383
+ return false;
5384
+ }
5385
+ /**
5386
+ * Synchronous budget check — always throws synchronously so callers
5387
+ * (especially test event handlers using `expect().toThrow()`) get an
5388
+ * unhandled rejection when the budget is exceeded without a handler.
5389
+ * When `onThreshold` IS configured, the actual async threshold-handling
5390
+ * is dispatched as a fire-and-forget promise.
5391
+ */
5392
+ checkLimit(kind, used, limit) {
5393
+ if (kind === "timeout" || !this._onThreshold) {
5394
+ throw new BudgetExceededError(kind, limit, used);
5395
+ }
5396
+ void this.checkLimitAsync(kind, used, limit);
5397
+ }
5398
+ /**
5399
+ * Async threshold negotiation with the coordinator. Fire-and-forget —
5400
+ * any error thrown here becomes an unhandled rejection in the test environment
5401
+ * because the runner's catch only handles the synchronous throw from `checkLimit`.
5402
+ */
5403
+ async checkLimitAsync(kind, used, limit) {
5404
+ const result = this._onThreshold({
5405
+ kind,
5406
+ used,
5407
+ limit,
5408
+ // Inject a requestDecision helper the handler can call to emit the
5409
+ // budget.threshold_reached event and wait for the coordinator's verdict.
5410
+ // The runner wires this by injecting its EventBus into ctx.budget._events.
5411
+ requestDecision: () => {
5412
+ return new Promise((resolve2) => {
5413
+ this._events?.emit("budget.threshold_reached", {
5414
+ kind,
5415
+ used,
5416
+ limit,
5417
+ timeoutMs: 3e4,
5418
+ extend: (extra) => resolve2({ extend: extra }),
5419
+ deny: () => resolve2("stop")
5420
+ });
5421
+ });
5422
+ }
5423
+ });
5424
+ if (result === "throw") {
5425
+ throw new BudgetExceededError(kind, limit, used);
5426
+ }
5427
+ if (result === "continue") {
5428
+ return;
5429
+ }
5430
+ const decision = await result;
5431
+ if (decision === "stop") {
5432
+ throw new BudgetExceededError(kind, limit, used);
5433
+ }
5434
+ const ext = decision.extend;
5435
+ if (ext.maxIterations !== void 0) {
5436
+ this.limits.maxIterations = ext.maxIterations;
5437
+ }
5438
+ if (ext.maxToolCalls !== void 0) {
5439
+ this.limits.maxToolCalls = ext.maxToolCalls;
5440
+ }
5441
+ if (ext.maxTokens !== void 0) {
5442
+ this.limits.maxTokens = ext.maxTokens;
5443
+ }
5444
+ if (ext.maxCostUsd !== void 0) {
5445
+ this.limits.maxCostUsd = ext.maxCostUsd;
5446
+ }
5447
+ }
5186
5448
  recordIteration() {
5187
5449
  this.iterations++;
5188
5450
  if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
5189
- throw new BudgetExceededError("iterations", this.limits.maxIterations, this.iterations);
5451
+ void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
5190
5452
  }
5191
5453
  }
5192
5454
  recordToolCall() {
5193
5455
  this.toolCalls++;
5194
5456
  if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
5195
- throw new BudgetExceededError("tool_calls", this.limits.maxToolCalls, this.toolCalls);
5457
+ void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
5196
5458
  }
5197
5459
  }
5198
5460
  recordUsage(usage, costUsd = 0) {
@@ -5201,10 +5463,10 @@ var SubagentBudget = class {
5201
5463
  this.costUsd += costUsd;
5202
5464
  const totalTokens = this.tokenInput + this.tokenOutput;
5203
5465
  if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
5204
- throw new BudgetExceededError("tokens", this.limits.maxTokens, totalTokens);
5466
+ void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
5205
5467
  }
5206
5468
  if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
5207
- throw new BudgetExceededError("cost", this.limits.maxCostUsd, this.costUsd);
5469
+ void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
5208
5470
  }
5209
5471
  }
5210
5472
  /**
@@ -5238,6 +5500,198 @@ var SubagentBudget = class {
5238
5500
  }
5239
5501
  };
5240
5502
 
5503
+ // src/coordination/fleet.ts
5504
+ var AUDIT_LOG_AGENT = {
5505
+ id: "audit-log",
5506
+ name: "Audit Log",
5507
+ role: "audit-log",
5508
+ prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
5509
+ session logs and produce actionable markdown reports.
5510
+
5511
+ Scope:
5512
+ - Parse session logs (iteration counts, tool calls, errors, usage)
5513
+ - Detect repeated failure patterns across multiple runs
5514
+ - Identify tool usage anomalies (over-use, failures, unexpected chains)
5515
+ - Track token consumption trends
5516
+ - Generate structured audit reports with severity ratings
5517
+
5518
+ Input format you accept:
5519
+ { "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
5520
+
5521
+ Output: Markdown audit report with sections:
5522
+ - ## Summary (totals, error rate)
5523
+ - ## Top Errors (count + context)
5524
+ - ## Tool Usage (table with calls, failures, avg duration)
5525
+ - ## Anomalies (pattern \u2192 severity)
5526
+
5527
+ Working rules:
5528
+ - Never fabricate numbers \u2014 read the actual logs first
5529
+ - Always include file:line references for errors
5530
+ - If sessionPath is missing, ask the director to provide it
5531
+ - Report confidence level: high (>90% accuracy), medium, low`
5532
+ // No hardcoded budgets — the orchestrator (delegate tool or
5533
+ // spawn_subagent) decides per-task how much room a subagent gets.
5534
+ // A monorepo audit needs hours; a single-file lint check needs
5535
+ // seconds. Pinning a number here forces the orchestrator to fight
5536
+ // the role's default instead of just asking for what it needs.
5537
+ };
5538
+ var BUG_HUNTER_AGENT = {
5539
+ id: "bug-hunter",
5540
+ name: "Bug Hunter",
5541
+ role: "bug-hunter",
5542
+ prompt: `You are the Bug Hunter agent. Your job is to systematically scan
5543
+ source code for bugs, anti-patterns, and code smells using pattern matching
5544
+ and heuristics. Output a prioritized hit list with file:line references.
5545
+
5546
+ Scope:
5547
+ - Detect common bug patterns (uncaught errors, resource leaks, race conditions)
5548
+ - Identify anti-patterns (callback hell, God objects, circular deps)
5549
+ - Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
5550
+ - Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
5551
+ - Rank findings: critical > high > medium > low
5552
+
5553
+ Input format you accept:
5554
+ { "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
5555
+
5556
+ Output: Markdown bug hunt report:
5557
+ - ## Critical (must fix first)
5558
+ - ## High (should fix)
5559
+ - ## Medium
5560
+ - ## Low (consider)
5561
+ Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
5562
+
5563
+ Bug pattern reference you know:
5564
+ | Pattern | Regex hint | Severity |
5565
+ |---------|------------|----------|
5566
+ | Uncaught promise | /.then\\(.*\\)/ without catch | high |
5567
+ | Event leak | on\\( without off/removeListener | high |
5568
+ | Hardcoded secret | [a-zA-Z0-9/_-]{20,} in config files | critical |
5569
+ | unsafe any | : any\\b or <any> | medium |
5570
+ | innerHTML | innerHTML\\s*= | high |
5571
+
5572
+ Working rules:
5573
+ - Never scan node_modules \u2014 it's noise
5574
+ - Always include file:line for every finding
5575
+ - If >30% of findings are false positives, note the confidence level
5576
+ - Ask director for clarification if paths are ambiguous`
5577
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5578
+ };
5579
+ var REFACTOR_PLANNER_AGENT = {
5580
+ id: "refactor-planner",
5581
+ name: "Refactor Planner",
5582
+ role: "refactor-planner",
5583
+ prompt: `You are the Refactor Planner agent. Your job is to analyze code
5584
+ structure and produce a concrete, phased refactoring plan with risk
5585
+ assessment, dependency ordering, and rollback strategy.
5586
+
5587
+ Scope:
5588
+ - Map module-level dependencies (import graph)
5589
+ - Identify coupling hotspots (high fan-in/out modules)
5590
+ - Assess refactoring risk by complexity and test coverage
5591
+ - Generate phased plans with checkpoint milestones
5592
+ - Produce diff-friendly task lists (one task = one concern)
5593
+
5594
+ Input format you accept:
5595
+ { "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
5596
+
5597
+ Output: Markdown refactor plan:
5598
+ - ## Phase 1: Low Risk / High Payoff (do first)
5599
+ Table: | # | Task | Module | Risk | Est. Time |
5600
+ - ## Phase 2: Medium Risk
5601
+ - ## Phase 3: High Risk (requires full regression)
5602
+ - ## Dependency Graph (abbreviated ASCII)
5603
+ - ## Rollback Strategy
5604
+ - ## Exit Criteria (checkbox list)
5605
+
5606
+ Risk scoring criteria:
5607
+ | Factor | Low | Medium | High |
5608
+ |--------|-----|--------|------|
5609
+ | Cyclomatic complexity | <10 | 10-20 | >20 |
5610
+ | Test coverage | >80% | 50-80% | <50% |
5611
+ | Fan-out (imports) | <5 | 5-15 | >15 |
5612
+
5613
+ Working rules:
5614
+ - Always include rollback strategy \u2014 every refactor can fail
5615
+ - Merge tasks that take <1h into a single phase
5616
+ - Respect team constraints (reviewer availability, parallelization)
5617
+ - Never plan without analyzing the actual code first`
5618
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5619
+ };
5620
+ var SECURITY_SCANNER_AGENT = {
5621
+ id: "security-scanner",
5622
+ name: "Security Scanner",
5623
+ role: "security-scanner",
5624
+ prompt: `You are the Security Scanner agent. Your job is to scan code,
5625
+ configs, and dependencies for security issues from hardcoded secrets to
5626
+ supply chain risks.
5627
+
5628
+ Scope:
5629
+ - Detect hardcoded secrets: API keys, tokens, passwords, private keys
5630
+ - Find injection vectors: eval, innerHTML, SQL concat, shell injection
5631
+ - Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
5632
+ - Scan dependencies for known CVEs (via npm/pnpm audit)
5633
+ - Flag supply chain risks: postinstall hooks, unverified scripts, .npmrc
5634
+
5635
+ Input format you accept:
5636
+ { "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
5637
+
5638
+ Output: Markdown security report:
5639
+ - ## CRITICAL: Secrets Found (with code snippets)
5640
+ - ## HIGH: Injection Vectors
5641
+ - ## MEDIUM: Insecure Patterns
5642
+ - ## Dependency Issues (CVE list)
5643
+ - ## Summary table (severity \u2192 count)
5644
+ - ## Remediation Checklist (with checkboxes)
5645
+
5646
+ Secret patterns you detect:
5647
+ | Pattern | Example | Severity |
5648
+ |---------|---------|----------|
5649
+ | AWS Access Key | AKIAIOSFODNN7EXAMPLE | critical |
5650
+ | AWS Secret Key | [a-zA-Z0-9/+=]{40} base64 | critical |
5651
+ | GitHub Token | ghp_[a-zA-Z0-9]{36} | critical |
5652
+ | Private Key PEM | -----BEGIN.*PRIVATE KEY----- | critical |
5653
+ | JWT | eyJ[a-zA-Z0-9_-]+ | high |
5654
+
5655
+ Injection patterns:
5656
+ | Construct | Safe alternative |
5657
+ |-----------|-----------------|
5658
+ | eval(str) | new Function() or parse |
5659
+ | innerHTML = x | textContent or sanitize |
5660
+ | exec(\`cmd \${x}\`) | execFile with args array |
5661
+
5662
+ Working rules:
5663
+ - Never scan node_modules \u2014 use npm audit instead
5664
+ - Always provide remediation steps, not just findings
5665
+ - Verify regex-based secrets before flagging (false positive risk)
5666
+ - When in doubt, flag as medium rather than ignoring potential issues`
5667
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5668
+ };
5669
+ var FLEET_ROSTER = {
5670
+ "audit-log": AUDIT_LOG_AGENT,
5671
+ "bug-hunter": BUG_HUNTER_AGENT,
5672
+ "refactor-planner": REFACTOR_PLANNER_AGENT,
5673
+ "security-scanner": SECURITY_SCANNER_AGENT
5674
+ };
5675
+ var FLEET_ROSTER_BUDGETS = {
5676
+ "audit-log": { timeoutMs: 5 * 60 * 1e3, maxIterations: 80, maxToolCalls: 300 },
5677
+ "bug-hunter": { timeoutMs: 10 * 60 * 1e3, maxIterations: 120, maxToolCalls: 400 },
5678
+ "refactor-planner": { timeoutMs: 8 * 60 * 1e3, maxIterations: 100, maxToolCalls: 350 },
5679
+ "security-scanner": { timeoutMs: 10 * 60 * 1e3, maxIterations: 120, maxToolCalls: 400 }
5680
+ };
5681
+ function applyRosterBudget(cfg) {
5682
+ const defaultBudget = FLEET_ROSTER_BUDGETS[cfg.role ?? ""];
5683
+ if (!defaultBudget) return cfg;
5684
+ return {
5685
+ ...cfg,
5686
+ timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
5687
+ maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
5688
+ maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
5689
+ maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
5690
+ maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
5691
+ };
5692
+ }
5693
+ var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
5694
+
5241
5695
  // src/coordination/multi-agent-coordinator.ts
5242
5696
  var DefaultMultiAgentCoordinator = class extends EventEmitter {
5243
5697
  coordinatorId;
@@ -5478,12 +5932,13 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5478
5932
  task.subagentId = subagentId;
5479
5933
  subagent.context.tasks.push(task);
5480
5934
  this.emit("task.assigned", { task, subagentId });
5935
+ const configWithRosterDefaults = applyRosterBudget(subagent.config);
5481
5936
  const budget = new SubagentBudget({
5482
- maxIterations: subagent.config.maxIterations ?? this.config.defaultBudget?.maxIterations,
5483
- maxToolCalls: task.maxToolCalls ?? subagent.config.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
5484
- maxTokens: subagent.config.maxTokens ?? this.config.defaultBudget?.maxTokens,
5485
- maxCostUsd: subagent.config.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
5486
- timeoutMs: task.timeoutMs ?? subagent.config.timeoutMs ?? this.config.defaultBudget?.timeoutMs
5937
+ maxIterations: configWithRosterDefaults.maxIterations ?? this.config.defaultBudget?.maxIterations,
5938
+ maxToolCalls: task.maxToolCalls ?? configWithRosterDefaults.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
5939
+ maxTokens: configWithRosterDefaults.maxTokens ?? this.config.defaultBudget?.maxTokens,
5940
+ maxCostUsd: configWithRosterDefaults.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
5941
+ timeoutMs: task.timeoutMs ?? configWithRosterDefaults.timeoutMs ?? this.config.defaultBudget?.timeoutMs
5487
5942
  });
5488
5943
  subagent.activeBudget = budget;
5489
5944
  if (!this.runner) {
@@ -6021,7 +6476,37 @@ var Director = class {
6021
6476
  this.scheduleManifest();
6022
6477
  };
6023
6478
  this.coordinator.on("task.completed", this.taskCompletedListener);
6024
- }
6479
+ const extendCounts = /* @__PURE__ */ new Map();
6480
+ this.fleet.filter("budget.threshold_reached", (e) => {
6481
+ const payload = e.payload;
6482
+ const guardKey = `${e.subagentId}:${payload.kind}`;
6483
+ const prior = extendCounts.get(guardKey) ?? 0;
6484
+ if (prior >= 2) {
6485
+ payload.deny();
6486
+ extendCounts.delete(guardKey);
6487
+ return;
6488
+ }
6489
+ extendCounts.set(guardKey, prior + 1);
6490
+ setTimeout(() => {
6491
+ const extra = {};
6492
+ switch (payload.kind) {
6493
+ case "iterations":
6494
+ extra.maxIterations = Math.min(payload.used + 50, 500);
6495
+ break;
6496
+ case "tool_calls":
6497
+ extra.maxToolCalls = Math.min(Math.ceil(payload.limit * 1.5), 1e3);
6498
+ break;
6499
+ case "tokens":
6500
+ extra.maxTokens = Math.min(Math.ceil(payload.limit * 1.5), 5e5);
6501
+ break;
6502
+ case "cost":
6503
+ extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
6504
+ break;
6505
+ }
6506
+ payload.extend(extra);
6507
+ }, Math.min(payload.timeoutMs, 3e4));
6508
+ });
6509
+ }
6025
6510
  /** Best-effort session-writer append. Swallows failures — the director
6026
6511
  * must not break a fleet run because the session JSONL handle closed. */
6027
6512
  async appendSessionEvent(event) {
@@ -6480,65 +6965,68 @@ function createDelegateTool(opts) {
6480
6965
  if (typeof i.task !== "string" || !i.task.trim()) {
6481
6966
  return { ok: false, error: "`task` is required." };
6482
6967
  }
6483
- let director = await opts.host.ensureDirector();
6484
- if (!director) {
6485
- director = await opts.host.promoteToDirector();
6486
- }
6487
- if (!director) {
6488
- const reason = opts.host.getPromotionBlockReason?.();
6489
- return {
6490
- ok: false,
6491
- error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
6492
- };
6493
- }
6494
- const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
6495
- let cfg;
6496
- if (i.role) {
6497
- const base = opts.roster?.[i.role];
6498
- if (!base) {
6968
+ try {
6969
+ let director = await opts.host.ensureDirector();
6970
+ if (!director) {
6971
+ director = await opts.host.promoteToDirector();
6972
+ }
6973
+ if (!director) {
6974
+ const reason = opts.host.getPromotionBlockReason?.();
6499
6975
  return {
6500
6976
  ok: false,
6501
- error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
6977
+ error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
6502
6978
  };
6503
6979
  }
6504
- cfg = instantiateRosterConfig2(i.role, base);
6505
- if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
6506
- if (i.provider) cfg.provider = i.provider;
6507
- if (i.model) cfg.model = i.model;
6508
- } else {
6509
- if (!i.name) {
6510
- return {
6511
- ok: false,
6512
- error: "Either `role` (from the roster) or `name` is required."
6980
+ const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
6981
+ let cfg;
6982
+ if (i.role) {
6983
+ const base = opts.roster?.[i.role];
6984
+ if (!base) {
6985
+ return {
6986
+ ok: false,
6987
+ error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
6988
+ };
6989
+ }
6990
+ cfg = instantiateRosterConfig2(i.role, base);
6991
+ if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
6992
+ if (i.provider) cfg.provider = i.provider;
6993
+ if (i.model) cfg.model = i.model;
6994
+ } else {
6995
+ if (!i.name) {
6996
+ return {
6997
+ ok: false,
6998
+ error: "Either `role` (from the roster) or `name` is required."
6999
+ };
7000
+ }
7001
+ cfg = {
7002
+ name: i.name,
7003
+ provider: i.provider,
7004
+ model: i.model,
7005
+ systemPromptOverride: i.systemPromptOverride
6513
7006
  };
6514
7007
  }
6515
- cfg = {
6516
- name: i.name,
6517
- provider: i.provider,
6518
- model: i.model,
6519
- systemPromptOverride: i.systemPromptOverride
6520
- };
6521
- }
6522
- if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
6523
- cfg.maxIterations = i.maxIterations;
6524
- }
6525
- if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
6526
- cfg.maxToolCalls = i.maxToolCalls;
6527
- }
6528
- const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
6529
- const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
6530
- if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
6531
- cfg.timeoutMs = desiredSubTimeout;
6532
- }
6533
- try {
7008
+ if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
7009
+ cfg.maxIterations = i.maxIterations;
7010
+ }
7011
+ if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
7012
+ cfg.maxToolCalls = i.maxToolCalls;
7013
+ }
7014
+ const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
7015
+ const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
7016
+ if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
7017
+ cfg.timeoutMs = desiredSubTimeout;
7018
+ }
6534
7019
  const subagentId = await director.spawn(cfg);
6535
7020
  const taskId = await director.assign({
6536
- id: "",
7021
+ id: `${randomUUID()}`,
6537
7022
  description: i.task,
6538
7023
  subagentId
6539
7024
  });
6540
7025
  const result = await Promise.race([
6541
- director.awaitTasks([taskId]).then((r) => r[0]),
7026
+ director.awaitTasks([taskId]).then((r) => {
7027
+ if (!r[0]) throw new Error(`Task "${taskId}" not found in completed results`);
7028
+ return r[0];
7029
+ }),
6542
7030
  new Promise(
6543
7031
  (resolve2) => setTimeout(() => resolve2({ __timeout: true }), timeoutMs)
6544
7032
  )
@@ -6612,7 +7100,7 @@ function hintForKind(kind, retryable, backoffMs) {
6612
7100
  case "budget_tool_calls":
6613
7101
  case "budget_tokens":
6614
7102
  case "budget_cost":
6615
- return "Subagent exhausted its budget. Raise the matching `max*` field on the next delegate or narrow task scope.";
7103
+ 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.";
6616
7104
  case "budget_timeout":
6617
7105
  return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
6618
7106
  case "aborted_by_parent":
@@ -6685,8 +7173,21 @@ function makeAgentSubagentRunner(opts) {
6685
7173
  const { agent, events } = factoryResult;
6686
7174
  const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
6687
7175
  const aborter = new AbortController();
7176
+ ctx.budget._events = events;
6688
7177
  let budgetError = null;
6689
7178
  const onBudgetError = (err) => {
7179
+ if (err instanceof BudgetThresholdSignal) {
7180
+ err.decision.then((decision) => {
7181
+ if (decision === "stop") {
7182
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7183
+ aborter.abort();
7184
+ }
7185
+ }).catch(() => {
7186
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7187
+ aborter.abort();
7188
+ });
7189
+ return;
7190
+ }
6690
7191
  aborter.abort();
6691
7192
  budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
6692
7193
  "tool_calls",
@@ -6716,7 +7217,7 @@ function makeAgentSubagentRunner(opts) {
6716
7217
  try {
6717
7218
  ctx.budget.recordUsage(e.usage);
6718
7219
  } catch (e2) {
6719
- onBudgetError(e2);
7220
+ void onBudgetError(e2);
6720
7221
  }
6721
7222
  }),
6722
7223
  events.on("iteration.started", () => {
@@ -6724,7 +7225,7 @@ function makeAgentSubagentRunner(opts) {
6724
7225
  ctx.budget.recordIteration();
6725
7226
  ctx.budget.checkTimeout();
6726
7227
  } catch (e) {
6727
- onBudgetError(e);
7228
+ void onBudgetError(e);
6728
7229
  }
6729
7230
  }),
6730
7231
  // D3: cooperative timeout enforcement DURING a long tool call.
@@ -6744,7 +7245,7 @@ function makeAgentSubagentRunner(opts) {
6744
7245
  try {
6745
7246
  ctx.budget.checkTimeout();
6746
7247
  } catch (e) {
6747
- onBudgetError(e);
7248
+ void onBudgetError(e);
6748
7249
  }
6749
7250
  })
6750
7251
  );
@@ -6818,180 +7319,6 @@ function makeDirectorSessionFactory(opts) {
6818
7319
  }
6819
7320
  };
6820
7321
  }
6821
-
6822
- // src/coordination/fleet.ts
6823
- var AUDIT_LOG_AGENT = {
6824
- id: "audit-log",
6825
- name: "Audit Log",
6826
- role: "audit-log",
6827
- prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
6828
- session logs and produce actionable markdown reports.
6829
-
6830
- Scope:
6831
- - Parse session logs (iteration counts, tool calls, errors, usage)
6832
- - Detect repeated failure patterns across multiple runs
6833
- - Identify tool usage anomalies (over-use, failures, unexpected chains)
6834
- - Track token consumption trends
6835
- - Generate structured audit reports with severity ratings
6836
-
6837
- Input format you accept:
6838
- { "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
6839
-
6840
- Output: Markdown audit report with sections:
6841
- - ## Summary (totals, error rate)
6842
- - ## Top Errors (count + context)
6843
- - ## Tool Usage (table with calls, failures, avg duration)
6844
- - ## Anomalies (pattern \u2192 severity)
6845
-
6846
- Working rules:
6847
- - Never fabricate numbers \u2014 read the actual logs first
6848
- - Always include file:line references for errors
6849
- - If sessionPath is missing, ask the director to provide it
6850
- - Report confidence level: high (>90% accuracy), medium, low`
6851
- // No hardcoded budgets — the orchestrator (delegate tool or
6852
- // spawn_subagent) decides per-task how much room a subagent gets.
6853
- // A monorepo audit needs hours; a single-file lint check needs
6854
- // seconds. Pinning a number here forces the orchestrator to fight
6855
- // the role's default instead of just asking for what it needs.
6856
- };
6857
- var BUG_HUNTER_AGENT = {
6858
- id: "bug-hunter",
6859
- name: "Bug Hunter",
6860
- role: "bug-hunter",
6861
- prompt: `You are the Bug Hunter agent. Your job is to systematically scan
6862
- source code for bugs, anti-patterns, and code smells using pattern matching
6863
- and heuristics. Output a prioritized hit list with file:line references.
6864
-
6865
- Scope:
6866
- - Detect common bug patterns (uncaught errors, resource leaks, race conditions)
6867
- - Identify anti-patterns (callback hell, God objects, circular deps)
6868
- - Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
6869
- - Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
6870
- - Rank findings: critical > high > medium > low
6871
-
6872
- Input format you accept:
6873
- { "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
6874
-
6875
- Output: Markdown bug hunt report:
6876
- - ## Critical (must fix first)
6877
- - ## High (should fix)
6878
- - ## Medium
6879
- - ## Low (consider)
6880
- Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
6881
-
6882
- Bug pattern reference you know:
6883
- | Pattern | Regex hint | Severity |
6884
- |---------|------------|----------|
6885
- | Uncaught promise | /.then\\(.*\\)/ without catch | high |
6886
- | Event leak | on\\( without off/removeListener | high |
6887
- | Hardcoded secret | [a-zA-Z0-9/_-]{20,} in config files | critical |
6888
- | unsafe any | : any\\b or <any> | medium |
6889
- | innerHTML | innerHTML\\s*= | high |
6890
-
6891
- Working rules:
6892
- - Never scan node_modules \u2014 it's noise
6893
- - Always include file:line for every finding
6894
- - If >30% of findings are false positives, note the confidence level
6895
- - Ask director for clarification if paths are ambiguous`
6896
- // Budgets are set by the orchestrator per task — see fleet.ts header.
6897
- };
6898
- var REFACTOR_PLANNER_AGENT = {
6899
- id: "refactor-planner",
6900
- name: "Refactor Planner",
6901
- role: "refactor-planner",
6902
- prompt: `You are the Refactor Planner agent. Your job is to analyze code
6903
- structure and produce a concrete, phased refactoring plan with risk
6904
- assessment, dependency ordering, and rollback strategy.
6905
-
6906
- Scope:
6907
- - Map module-level dependencies (import graph)
6908
- - Identify coupling hotspots (high fan-in/out modules)
6909
- - Assess refactoring risk by complexity and test coverage
6910
- - Generate phased plans with checkpoint milestones
6911
- - Produce diff-friendly task lists (one task = one concern)
6912
-
6913
- Input format you accept:
6914
- { "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
6915
-
6916
- Output: Markdown refactor plan:
6917
- - ## Phase 1: Low Risk / High Payoff (do first)
6918
- Table: | # | Task | Module | Risk | Est. Time |
6919
- - ## Phase 2: Medium Risk
6920
- - ## Phase 3: High Risk (requires full regression)
6921
- - ## Dependency Graph (abbreviated ASCII)
6922
- - ## Rollback Strategy
6923
- - ## Exit Criteria (checkbox list)
6924
-
6925
- Risk scoring criteria:
6926
- | Factor | Low | Medium | High |
6927
- |--------|-----|--------|------|
6928
- | Cyclomatic complexity | <10 | 10-20 | >20 |
6929
- | Test coverage | >80% | 50-80% | <50% |
6930
- | Fan-out (imports) | <5 | 5-15 | >15 |
6931
-
6932
- Working rules:
6933
- - Always include rollback strategy \u2014 every refactor can fail
6934
- - Merge tasks that take <1h into a single phase
6935
- - Respect team constraints (reviewer availability, parallelization)
6936
- - Never plan without analyzing the actual code first`
6937
- // Budgets are set by the orchestrator per task — see fleet.ts header.
6938
- };
6939
- var SECURITY_SCANNER_AGENT = {
6940
- id: "security-scanner",
6941
- name: "Security Scanner",
6942
- role: "security-scanner",
6943
- prompt: `You are the Security Scanner agent. Your job is to scan code,
6944
- configs, and dependencies for security issues from hardcoded secrets to
6945
- supply chain risks.
6946
-
6947
- Scope:
6948
- - Detect hardcoded secrets: API keys, tokens, passwords, private keys
6949
- - Find injection vectors: eval, innerHTML, SQL concat, shell injection
6950
- - Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
6951
- - Scan dependencies for known CVEs (via npm/pnpm audit)
6952
- - Flag supply chain risks: postinstall hooks, unverified scripts, .npmrc
6953
-
6954
- Input format you accept:
6955
- { "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
6956
-
6957
- Output: Markdown security report:
6958
- - ## CRITICAL: Secrets Found (with code snippets)
6959
- - ## HIGH: Injection Vectors
6960
- - ## MEDIUM: Insecure Patterns
6961
- - ## Dependency Issues (CVE list)
6962
- - ## Summary table (severity \u2192 count)
6963
- - ## Remediation Checklist (with checkboxes)
6964
-
6965
- Secret patterns you detect:
6966
- | Pattern | Example | Severity |
6967
- |---------|---------|----------|
6968
- | AWS Access Key | AKIAIOSFODNN7EXAMPLE | critical |
6969
- | AWS Secret Key | [a-zA-Z0-9/+=]{40} base64 | critical |
6970
- | GitHub Token | ghp_[a-zA-Z0-9]{36} | critical |
6971
- | Private Key PEM | -----BEGIN.*PRIVATE KEY----- | critical |
6972
- | JWT | eyJ[a-zA-Z0-9_-]+ | high |
6973
-
6974
- Injection patterns:
6975
- | Construct | Safe alternative |
6976
- |-----------|-----------------|
6977
- | eval(str) | new Function() or parse |
6978
- | innerHTML = x | textContent or sanitize |
6979
- | exec(\`cmd \${x}\`) | execFile with args array |
6980
-
6981
- Working rules:
6982
- - Never scan node_modules \u2014 use npm audit instead
6983
- - Always provide remediation steps, not just findings
6984
- - Verify regex-based secrets before flagging (false positive risk)
6985
- - When in doubt, flag as medium rather than ignoring potential issues`
6986
- // Budgets are set by the orchestrator per task — see fleet.ts header.
6987
- };
6988
- var FLEET_ROSTER = {
6989
- "audit-log": AUDIT_LOG_AGENT,
6990
- "bug-hunter": BUG_HUNTER_AGENT,
6991
- "refactor-planner": REFACTOR_PLANNER_AGENT,
6992
- "security-scanner": SECURITY_SCANNER_AGENT
6993
- };
6994
- var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
6995
7322
  var DEFAULT_URL = "https://models.dev/api.json";
6996
7323
  var DEFAULT_TTL_SECONDS = 24 * 3600;
6997
7324
  var FAMILY_BY_NPM = {
@@ -7292,6 +7619,112 @@ When refactoring code:
7292
7619
  - Keep performance in mind \u2014 don't regress`,
7293
7620
  tags: ["refactor", "modernization", "improvement"],
7294
7621
  toolPreferences: ["read", "edit", "test", "git", "grep"]
7622
+ },
7623
+ {
7624
+ id: "brief",
7625
+ name: "Brief",
7626
+ description: "Fast, no-nonsense \u2014 get to the point",
7627
+ prompt: `## Brief Mode
7628
+
7629
+ You are WrongStack, a fast, no-nonsense AI coding agent.
7630
+
7631
+ You operate inside the user's terminal. Read files, run commands, make changes \u2014 get to the point.
7632
+
7633
+ ### Operating rules
7634
+
7635
+ 1. **Read first.** Inspect relevant files before touching anything.
7636
+ 2. **Edit surgically.** Use edit tool for existing files, write only for new ones.
7637
+ 3. **One sentence before action.** State what you're doing, then do it. No preambles.
7638
+ 4. **Say what happened.** After tool calls, one line: success, failure, or what's next.
7639
+ 5. **Be honest.** Admit when you don't know or something failed. No fake progress.
7640
+ 6. **Keep moving.** Task done? Stop. More work needed? State it and continue.
7641
+
7642
+ ### Decision rules
7643
+
7644
+ - **Ambiguous task?** Ask. One question, get clarity, proceed.
7645
+ - **Clear task, unknown approach?** Pick one reasonable path, execute, report.
7646
+ - **Tool fails?** Retry once with adjusted params, then report.
7647
+ - **Permission denied?** Stop. Acknowledge. Ask what they want instead.
7648
+ - **Context filling up?** Compact proactively, don't wait.
7649
+
7650
+ ### Output style
7651
+
7652
+ - Prose paragraphs (no bullet points unless unavoidable)
7653
+ - Code blocks for code, backticks for paths/commands
7654
+ - One-liner sufficient? One liner.
7655
+ - No "Great question!", "Here's what I did:", or similar filler.
7656
+ - Max 3 sentences per paragraph.
7657
+
7658
+ ### Focus
7659
+
7660
+ 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.`,
7661
+ tags: ["fast", "concise", "direct"],
7662
+ toolPreferences: ["read", "edit", "bash"]
7663
+ },
7664
+ {
7665
+ id: "teach",
7666
+ name: "Teach",
7667
+ description: "Mentor mode \u2014 explains why, not just what",
7668
+ prompt: `## Teach Mode
7669
+
7670
+ You are WrongStack, an expert AI coding mentor.
7671
+
7672
+ 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.
7673
+
7674
+ ### Teaching philosophy
7675
+
7676
+ 1. **Explain the why.** When you make a change, explain why it works that way \u2014 not just what you did.
7677
+ 2. **Build mental models.** Use analogies, highlight patterns, connect new concepts to things the user already knows.
7678
+ 3. **Read before teaching.** Always inspect relevant files so your explanations are accurate and specific to the actual code.
7679
+ 4. **Surgical edits with context.** When editing code, explain the approach before doing it, and what trade-offs were considered.
7680
+ 5. **Be thorough but not verbose.** A 2-paragraph explanation beats a 5-paragraph one. Depth without padding.
7681
+ 6. **Admit knowledge gaps.** If you're unsure, say so. Speculating teaches bad patterns.
7682
+
7683
+ ### Teaching style
7684
+
7685
+ - **Before action:** Briefly explain what you're going to do and why.
7686
+ - **After action:** Summarize what happened and what the user should take away from this.
7687
+ - **With code:** Show concrete examples, explain syntax choices, point out gotchas.
7688
+ - **With errors:** Explain why the error occurred, what it's actually complaining about, and how to avoid it in the future.
7689
+ - **General principles:** Offer them when the user's question suggests a deeper concept they'd benefit from understanding.
7690
+
7691
+ ### Decision heuristics
7692
+
7693
+ - **Task is ambiguous?** Ask \u2014 but frame the question as "what would you like to learn from this?"
7694
+ - **Task is clear, approach is unknown?** Execute, then teach the approach as you go.
7695
+ - **Tool fails?** Explain what failed, why it failed, and how to avoid the failure.
7696
+ - **User asks "how do I...?"** Don't just give the answer \u2014 explain the underlying mechanism.
7697
+ - **Context window filling up?** Compact, but summarize what was lost so the teaching continuity isn't broken.
7698
+
7699
+ ### Output format
7700
+
7701
+ - Use headings to structure multi-concept explanations.
7702
+ - Code blocks with brief annotations for code examples.
7703
+ - **Bold** key terms and concepts worth remembering.
7704
+ - Callouts like "Key takeaway:" or "Pattern:" to anchor learning.
7705
+ - Max 3 sentences per paragraph \u2014 readability over completeness.
7706
+
7707
+ ### Don'ts
7708
+
7709
+ - Don't lecture condescendingly \u2014 the user is a developer, not a beginner.
7710
+ - Don't pad explanations with obvious things.
7711
+ - Don't skip the "why" \u2014 even quick tasks deserve one sentence of context.
7712
+ - Don't just say "do X" \u2014 say "do X because Y."
7713
+ - Don't leave the user hanging after a complex operation \u2014 explain what just happened.
7714
+
7715
+ ### Core principles
7716
+
7717
+ You follow these principles, but always with explanation:
7718
+ - Read before write
7719
+ - Surgical edits over rewrites
7720
+ - Show your work (explain your reasoning, not just mechanical steps)
7721
+ - Be honest about limits
7722
+ - Format for scanability
7723
+ - Recover explicitly from failures
7724
+
7725
+ Remember: your job is to make the user a better developer, not just to complete tasks faster.`,
7726
+ tags: ["teaching", "mentor", "learning"],
7727
+ toolPreferences: ["read", "edit", "explain"]
7295
7728
  }
7296
7729
  ];
7297
7730
 
@@ -7862,6 +8295,27 @@ function computeTaskProgress(graph) {
7862
8295
  actualHours
7863
8296
  };
7864
8297
  }
8298
+ function topologicalSort(graph) {
8299
+ const visited = /* @__PURE__ */ new Set();
8300
+ const inStack = /* @__PURE__ */ new Set();
8301
+ const result = [];
8302
+ function visit(id) {
8303
+ if (inStack.has(id)) return;
8304
+ if (visited.has(id)) return;
8305
+ if (!graph.nodes.has(id)) return;
8306
+ visited.add(id);
8307
+ inStack.add(id);
8308
+ for (const edge of graph.edges) {
8309
+ if (edge.from === id) visit(edge.to);
8310
+ }
8311
+ inStack.delete(id);
8312
+ result.push(id);
8313
+ }
8314
+ for (const rootId of graph.rootNodes) {
8315
+ visit(rootId);
8316
+ }
8317
+ return result;
8318
+ }
7865
8319
 
7866
8320
  // src/sdd/task-tracker.ts
7867
8321
  var TaskTracker = class {
@@ -8260,20 +8714,1508 @@ var SpecDrivenDev = class {
8260
8714
  }));
8261
8715
  }
8262
8716
  };
8263
-
8264
- // src/observability/metrics.ts
8265
- var RESERVOIR_SIZE = 1024;
8266
- function labelKey(labels) {
8267
- if (!labels) return "";
8268
- const keys = Object.keys(labels).sort();
8269
- return keys.map((k) => `${k}=${labels[k]}`).join(",");
8270
- }
8271
- function quantile(sorted, q) {
8272
- if (sorted.length === 0) return 0;
8273
- const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
8274
- return sorted[idx] ?? 0;
8275
- }
8276
- var InMemoryMetricsSink = class {
8717
+ var SpecStore = class {
8718
+ baseDir;
8719
+ indexPath;
8720
+ constructor(opts) {
8721
+ this.baseDir = opts.baseDir;
8722
+ this.indexPath = path3.join(this.baseDir, "_index.json");
8723
+ }
8724
+ async save(spec) {
8725
+ await ensureDir(this.baseDir);
8726
+ const filePath = this.filePath(spec.id);
8727
+ await atomicWrite(filePath, JSON.stringify(spec, null, 2), { mode: 384 });
8728
+ await this.updateIndex(spec);
8729
+ }
8730
+ async load(id) {
8731
+ try {
8732
+ const raw = await fsp.readFile(this.filePath(id), "utf8");
8733
+ return JSON.parse(raw);
8734
+ } catch {
8735
+ return null;
8736
+ }
8737
+ }
8738
+ async list() {
8739
+ const index = await this.readIndex();
8740
+ return index.entries.sort((a, b) => b.updatedAt - a.updatedAt);
8741
+ }
8742
+ async delete(id) {
8743
+ try {
8744
+ await fsp.unlink(this.filePath(id));
8745
+ await this.removeFromIndex(id);
8746
+ return true;
8747
+ } catch {
8748
+ return false;
8749
+ }
8750
+ }
8751
+ async exists(id) {
8752
+ try {
8753
+ await fsp.access(this.filePath(id));
8754
+ return true;
8755
+ } catch {
8756
+ return false;
8757
+ }
8758
+ }
8759
+ /** Create a new spec with defaults, assign ID, and persist. */
8760
+ async createDraft(title, overview) {
8761
+ const now = Date.now();
8762
+ const spec = {
8763
+ id: randomUUID(),
8764
+ title,
8765
+ version: "0.1.0",
8766
+ status: "draft",
8767
+ overview: overview ?? "",
8768
+ sections: [],
8769
+ requirements: [],
8770
+ createdAt: now,
8771
+ updatedAt: now
8772
+ };
8773
+ await this.save(spec);
8774
+ return spec;
8775
+ }
8776
+ /** Update spec fields and persist. */
8777
+ async update(id, patch) {
8778
+ const spec = await this.load(id);
8779
+ if (!spec) return null;
8780
+ const updated = {
8781
+ ...spec,
8782
+ ...patch,
8783
+ id: spec.id,
8784
+ createdAt: spec.createdAt,
8785
+ updatedAt: Date.now()
8786
+ };
8787
+ await this.save(updated);
8788
+ return updated;
8789
+ }
8790
+ filePath(id) {
8791
+ return path3.join(this.baseDir, `${id}.json`);
8792
+ }
8793
+ async readIndex() {
8794
+ try {
8795
+ const raw = await fsp.readFile(this.indexPath, "utf8");
8796
+ const parsed = JSON.parse(raw);
8797
+ if (parsed?.version === 1) return parsed;
8798
+ } catch {
8799
+ }
8800
+ return { version: 1, entries: [] };
8801
+ }
8802
+ async updateIndex(spec) {
8803
+ const index = await this.readIndex();
8804
+ const entry = {
8805
+ id: spec.id,
8806
+ title: spec.title,
8807
+ version: spec.version,
8808
+ status: spec.status,
8809
+ updatedAt: spec.updatedAt,
8810
+ filePath: this.filePath(spec.id)
8811
+ };
8812
+ const idx = index.entries.findIndex((e) => e.id === spec.id);
8813
+ if (idx >= 0) {
8814
+ index.entries[idx] = entry;
8815
+ } else {
8816
+ index.entries.push(entry);
8817
+ }
8818
+ await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
8819
+ }
8820
+ async removeFromIndex(id) {
8821
+ const index = await this.readIndex();
8822
+ index.entries = index.entries.filter((e) => e.id !== id);
8823
+ await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
8824
+ }
8825
+ };
8826
+ function graphToJSON(graph) {
8827
+ const serialisable = {
8828
+ ...graph,
8829
+ nodes: Array.from(graph.nodes.entries())
8830
+ };
8831
+ return JSON.stringify(serialisable, null, 2);
8832
+ }
8833
+ function graphFromJSON(raw) {
8834
+ const parsed = JSON.parse(raw);
8835
+ return {
8836
+ ...parsed,
8837
+ nodes: new Map(parsed.nodes)
8838
+ };
8839
+ }
8840
+ var TaskGraphStore = class {
8841
+ baseDir;
8842
+ indexPath;
8843
+ constructor(opts) {
8844
+ this.baseDir = opts.baseDir;
8845
+ this.indexPath = path3.join(this.baseDir, "_index.json");
8846
+ }
8847
+ async save(graph) {
8848
+ await ensureDir(this.baseDir);
8849
+ const filePath = this.filePath(graph.id);
8850
+ await atomicWrite(filePath, graphToJSON(graph), { mode: 384 });
8851
+ await this.updateIndex(graph);
8852
+ }
8853
+ async load(id) {
8854
+ try {
8855
+ const raw = await fsp.readFile(this.filePath(id), "utf8");
8856
+ return graphFromJSON(raw);
8857
+ } catch {
8858
+ return null;
8859
+ }
8860
+ }
8861
+ async list() {
8862
+ const index = await this.readIndex();
8863
+ return index.entries.sort((a, b) => b.updatedAt - a.updatedAt);
8864
+ }
8865
+ async delete(id) {
8866
+ try {
8867
+ await fsp.unlink(this.filePath(id));
8868
+ await this.removeFromIndex(id);
8869
+ return true;
8870
+ } catch {
8871
+ return false;
8872
+ }
8873
+ }
8874
+ async exists(id) {
8875
+ try {
8876
+ await fsp.access(this.filePath(id));
8877
+ return true;
8878
+ } catch {
8879
+ return false;
8880
+ }
8881
+ }
8882
+ filePath(id) {
8883
+ return path3.join(this.baseDir, `${id}.json`);
8884
+ }
8885
+ async readIndex() {
8886
+ try {
8887
+ const raw = await fsp.readFile(this.indexPath, "utf8");
8888
+ const parsed = JSON.parse(raw);
8889
+ if (parsed?.version === 1) return parsed;
8890
+ } catch {
8891
+ }
8892
+ return { version: 1, entries: [] };
8893
+ }
8894
+ async updateIndex(graph) {
8895
+ const index = await this.readIndex();
8896
+ const completedCount = Array.from(graph.nodes.values()).filter(
8897
+ (n) => n.status === "completed"
8898
+ ).length;
8899
+ const entry = {
8900
+ id: graph.id,
8901
+ specId: graph.specId,
8902
+ title: graph.title,
8903
+ nodeCount: graph.nodes.size,
8904
+ completedCount,
8905
+ updatedAt: graph.updatedAt,
8906
+ filePath: this.filePath(graph.id)
8907
+ };
8908
+ const idx = index.entries.findIndex((e) => e.id === graph.id);
8909
+ if (idx >= 0) {
8910
+ index.entries[idx] = entry;
8911
+ } else {
8912
+ index.entries.push(entry);
8913
+ }
8914
+ await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
8915
+ }
8916
+ async removeFromIndex(id) {
8917
+ const index = await this.readIndex();
8918
+ index.entries = index.entries.filter((e) => e.id !== id);
8919
+ await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
8920
+ }
8921
+ };
8922
+
8923
+ // src/sdd/spec-builder.ts
8924
+ function buildQuestioningPrompt(session, min, max) {
8925
+ const answered = session.answers.length;
8926
+ const remaining = Math.max(0, min - answered);
8927
+ const budget = max - answered;
8928
+ const lines = [
8929
+ `\u2550\u2550\u2550 SDD Spec Builder \u2550\u2550\u2550`,
8930
+ `Feature: "${session.title}"`,
8931
+ session.userIntent ? `Intent: ${session.userIntent}` : "",
8932
+ `Phase: Questioning (${answered} answered, ${budget} remaining budget)`,
8933
+ "",
8934
+ "**Instructions for AI:**",
8935
+ "",
8936
+ "You are conducting a specification interview. Your job is to ask the user",
8937
+ "intelligent, contextual questions to understand what they want to build.",
8938
+ "",
8939
+ `You have asked ${answered} questions so far.`
8940
+ ];
8941
+ if (remaining > 0) {
8942
+ lines.push(`You MUST ask at least ${remaining} more question(s) before generating the spec.`);
8943
+ } else if (budget <= 0) {
8944
+ lines.push("You have reached the maximum question budget. Generate the spec NOW.");
8945
+ } else {
8946
+ lines.push(
8947
+ "You may ask more questions if needed, or generate the spec if you have enough information.",
8948
+ "Ask a question ONLY if it reveals something you genuinely need to know."
8949
+ );
8950
+ }
8951
+ lines.push(
8952
+ "",
8953
+ "**Rules:**",
8954
+ "- Ask ONE question at a time",
8955
+ "- Questions must be specific and contextual \u2014 never generic",
8956
+ "- Adapt based on previous answers",
8957
+ "- Cover: scope, constraints, edge cases, integrations, security, performance as relevant",
8958
+ "- When you have enough info, respond with the full specification in JSON format",
8959
+ "",
8960
+ `**Question budget:** ${budget}/${max} remaining`,
8961
+ `**Minimum required:** ${remaining > 0 ? remaining : "met"}`
8962
+ );
8963
+ if (session.projectContext) {
8964
+ lines.push("", "**Project Context:**", "```", session.projectContext, "```");
8965
+ }
8966
+ if (answered > 0) {
8967
+ lines.push("", "**Conversation so far:**");
8968
+ for (let i = 0; i < answered; i++) {
8969
+ const a = session.answers[i];
8970
+ lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
8971
+ }
8972
+ }
8973
+ lines.push(
8974
+ "",
8975
+ "---",
8976
+ "Now either:",
8977
+ `1. Ask your next question (if you need more info)`,
8978
+ `2. Generate the complete specification as JSON (if ready)`,
8979
+ "",
8980
+ "If generating spec, output JSON inside ```json code block with this structure:",
8981
+ "```json",
8982
+ "{",
8983
+ ' "title": "...",',
8984
+ ' "overview": "...",',
8985
+ ' "sections": [{ "type": "overview|requirements|architecture|api|data|security|acceptance", "title": "...", "content": "...", "level": 1 }],',
8986
+ ' "requirements": [{ "id": "REQ-1", "type": "functional|non-functional|security|performance|ux", "priority": "critical|high|medium|low", "description": "...", "acceptanceCriteria": ["..."] }]',
8987
+ "}",
8988
+ "```"
8989
+ );
8990
+ return lines.filter(Boolean).join("\n");
8991
+ }
8992
+ function buildSpecReviewPrompt(session) {
8993
+ const spec = session.spec;
8994
+ if (!spec) return "No spec generated yet.";
8995
+ const reqSummary = spec.requirements.map((r) => ` [${r.priority}] ${r.description}`).join("\n");
8996
+ return [
8997
+ `\u2550\u2550\u2550 Spec Review \u2550\u2550\u2550`,
8998
+ `Feature: "${spec.title}"`,
8999
+ `Requirements: ${spec.requirements.length}`,
9000
+ "",
9001
+ "**Specification:**",
9002
+ spec.overview,
9003
+ "",
9004
+ "**Requirements:**",
9005
+ reqSummary,
9006
+ "",
9007
+ "---",
9008
+ "Approve this spec? The AI will then generate an implementation plan and tasks.",
9009
+ 'Say "approve" to proceed, or describe what needs to change.'
9010
+ ].join("\n");
9011
+ }
9012
+ function buildImplementationPrompt(session) {
9013
+ const spec = session.spec;
9014
+ if (!spec) return "No spec to implement.";
9015
+ const reqList = spec.requirements.map((r) => ` - [${r.priority}] ${r.description}`).join("\n");
9016
+ return [
9017
+ `\u2550\u2550\u2550 Implementation Planning \u2550\u2550\u2550`,
9018
+ `Feature: "${spec.title}"`,
9019
+ `Requirements: ${spec.requirements.length}`,
9020
+ "",
9021
+ "**Requirements to implement:**",
9022
+ reqList,
9023
+ "",
9024
+ "**Instructions for AI:**",
9025
+ "Generate a detailed implementation plan for this specification.",
9026
+ "Include:",
9027
+ "1. Architecture decisions",
9028
+ "2. File structure changes",
9029
+ "3. Key implementation details",
9030
+ "4. Dependency requirements",
9031
+ "5. Testing strategy",
9032
+ "",
9033
+ "**IMPORTANT:** After the plan, you MUST generate executable tasks as a JSON array.",
9034
+ "Each task should be a concrete, actionable step. Output the JSON inside a ```json code block:",
9035
+ "```json",
9036
+ "[",
9037
+ " {",
9038
+ ' "title": "Create auth middleware",',
9039
+ ' "description": "Implement JWT verification middleware for protected routes",',
9040
+ ' "type": "feature",',
9041
+ ' "priority": "critical",',
9042
+ ' "estimateHours": 3,',
9043
+ ' "tags": ["auth", "middleware"]',
9044
+ " },",
9045
+ " {",
9046
+ ' "title": "Write auth tests",',
9047
+ ' "description": "Unit and integration tests for authentication flow",',
9048
+ ' "type": "test",',
9049
+ ' "priority": "high",',
9050
+ ' "estimateHours": 2,',
9051
+ ' "tags": ["test", "auth"]',
9052
+ " }",
9053
+ "]",
9054
+ "```",
9055
+ "",
9056
+ "Rules:",
9057
+ "- Each task must be independently executable",
9058
+ "- Order tasks by dependency (things that block others come first)",
9059
+ '- Use type: "feature" for code, "test" for tests, "docs" for documentation, "chore" for config',
9060
+ '- Use priority: "critical" for blockers, "high" for core features, "medium" for nice-to-haves, "low" for polish'
9061
+ ].join("\n");
9062
+ }
9063
+ function buildTaskReviewPrompt(session) {
9064
+ return [
9065
+ `\u2550\u2550\u2550 Task Review \u2550\u2550\u2550`,
9066
+ `Feature: "${session.spec?.title ?? session.title}"`,
9067
+ "",
9068
+ session.implementation ?? "No implementation plan yet.",
9069
+ "",
9070
+ "---",
9071
+ 'Ready to execute these tasks? Say "execute" to begin, or describe changes needed.'
9072
+ ].join("\n");
9073
+ }
9074
+ function buildExecutingPrompt(session) {
9075
+ return [
9076
+ `\u2550\u2550\u2550 Task Execution \u2550\u2550\u2550`,
9077
+ `Feature: "${session.spec?.title ?? session.title}"`,
9078
+ "",
9079
+ "**Instructions for AI:**",
9080
+ "Execute the tasks one by one in the order shown in the task list above.",
9081
+ "",
9082
+ "For each task:",
9083
+ "1. Implement the code (create/modify files)",
9084
+ "2. Write tests if applicable",
9085
+ "3. After completing a task, tell the user to run: /sdd done <task number or title>",
9086
+ "4. Then move to the next task",
9087
+ "",
9088
+ "**Important:**",
9089
+ "- Focus on ONE task at a time",
9090
+ "- After completing each task, explicitly state what you did",
9091
+ '- Tell the user: "Run /sdd done <N> to mark this task complete"',
9092
+ "- Then proceed to the next task automatically",
9093
+ "- When ALL tasks are done, provide a summary of everything implemented",
9094
+ "",
9095
+ "Start executing the first pending task now."
9096
+ ].join("\n");
9097
+ }
9098
+ var AISpecBuilder = class {
9099
+ session;
9100
+ store;
9101
+ minQuestions;
9102
+ maxQuestions;
9103
+ sessionPath;
9104
+ constructor(opts) {
9105
+ this.store = opts.store;
9106
+ this.minQuestions = opts.minQuestions ?? 2;
9107
+ this.maxQuestions = opts.maxQuestions ?? 10;
9108
+ this.sessionPath = opts.sessionPath;
9109
+ this.session = {
9110
+ id: crypto.randomUUID(),
9111
+ phase: "questioning",
9112
+ title: "",
9113
+ userIntent: "",
9114
+ projectContext: opts.projectContext ?? "",
9115
+ answers: [],
9116
+ questionCount: 0,
9117
+ approved: false,
9118
+ createdAt: Date.now(),
9119
+ updatedAt: Date.now()
9120
+ };
9121
+ }
9122
+ // ── Session Persistence ──────────────────────────────────────────────────
9123
+ /** Save session state to disk. */
9124
+ async saveSession() {
9125
+ if (!this.sessionPath) return;
9126
+ try {
9127
+ const fsp13 = await import('fs/promises');
9128
+ const path17 = await import('path');
9129
+ await fsp13.mkdir(path17.dirname(this.sessionPath), { recursive: true });
9130
+ await fsp13.writeFile(this.sessionPath, JSON.stringify(this.session, null, 2), "utf8");
9131
+ } catch {
9132
+ }
9133
+ }
9134
+ /** Load session state from disk. Returns true if a session was loaded. */
9135
+ async loadSession() {
9136
+ if (!this.sessionPath) return false;
9137
+ try {
9138
+ const fsp13 = await import('fs/promises');
9139
+ const raw = await fsp13.readFile(this.sessionPath, "utf8");
9140
+ const loaded = JSON.parse(raw);
9141
+ if (loaded?.id && loaded?.phase && loaded?.title) {
9142
+ this.session = loaded;
9143
+ return true;
9144
+ }
9145
+ } catch {
9146
+ }
9147
+ return false;
9148
+ }
9149
+ /** Delete saved session from disk. */
9150
+ async deleteSession() {
9151
+ if (!this.sessionPath) return;
9152
+ try {
9153
+ const fsp13 = await import('fs/promises');
9154
+ await fsp13.unlink(this.sessionPath);
9155
+ } catch {
9156
+ }
9157
+ }
9158
+ /** Auto-save helper — calls saveSession() but never throws. */
9159
+ autoSave() {
9160
+ this.saveSession().catch(() => {
9161
+ });
9162
+ }
9163
+ // ── Session Lifecycle ─────────────────────────────────────────────────────
9164
+ /** Start a new session with a title and optional intent. */
9165
+ startSession(title, intent) {
9166
+ this.session.title = title;
9167
+ this.session.userIntent = intent ?? "";
9168
+ this.session.phase = "questioning";
9169
+ this.session.updatedAt = Date.now();
9170
+ this.autoSave();
9171
+ }
9172
+ /** Get current session state (readonly). */
9173
+ getSession() {
9174
+ return { ...this.session };
9175
+ }
9176
+ /** Get the current phase. */
9177
+ getPhase() {
9178
+ return this.session.phase;
9179
+ }
9180
+ // ── AI Prompt Generation ──────────────────────────────────────────────────
9181
+ /**
9182
+ * Get the AI prompt for the current phase.
9183
+ * This prompt is injected into the conversation so the AI agent knows
9184
+ * what to do next (ask a question, generate a spec, etc.).
9185
+ */
9186
+ getAIPrompt() {
9187
+ switch (this.session.phase) {
9188
+ case "questioning":
9189
+ return buildQuestioningPrompt(this.session, this.minQuestions, this.maxQuestions);
9190
+ case "spec_review":
9191
+ return buildSpecReviewPrompt(this.session);
9192
+ case "implementation":
9193
+ return buildImplementationPrompt(this.session);
9194
+ case "task_review":
9195
+ return buildTaskReviewPrompt(this.session);
9196
+ case "executing":
9197
+ return buildExecutingPrompt(this.session);
9198
+ case "done":
9199
+ return "All tasks completed. Specification is fully implemented.";
9200
+ }
9201
+ }
9202
+ // ── Answer Processing ─────────────────────────────────────────────────────
9203
+ /**
9204
+ * Record a question/answer pair from the AI conversation.
9205
+ * Call this when the AI asks a question and the user responds.
9206
+ */
9207
+ addAnswer(question, answer) {
9208
+ this.session.answers.push({ question, answer, timestamp: Date.now() });
9209
+ this.session.questionCount++;
9210
+ this.session.updatedAt = Date.now();
9211
+ this.autoSave();
9212
+ }
9213
+ /**
9214
+ * Check if more questions should be asked.
9215
+ * Returns false if max reached or if the AI has signaled it has enough info.
9216
+ */
9217
+ shouldContinueQuestioning() {
9218
+ return this.session.questionCount < this.maxQuestions;
9219
+ }
9220
+ /**
9221
+ * Check if minimum questions have been asked.
9222
+ */
9223
+ hasMetMinimumQuestions() {
9224
+ return this.session.questionCount >= this.minQuestions;
9225
+ }
9226
+ // ── Phase Transitions ─────────────────────────────────────────────────────
9227
+ /**
9228
+ * Set the generated specification and move to spec_review phase.
9229
+ */
9230
+ setSpec(spec) {
9231
+ this.session.spec = spec;
9232
+ this.session.phase = "spec_review";
9233
+ this.session.updatedAt = Date.now();
9234
+ this.autoSave();
9235
+ }
9236
+ /**
9237
+ * Approve the current phase and advance to the next.
9238
+ * questioning → spec_review (requires spec to be set)
9239
+ * spec_review → implementation
9240
+ * implementation → task_review (requires implementation to be set)
9241
+ * task_review → executing
9242
+ * executing → done
9243
+ */
9244
+ approve() {
9245
+ switch (this.session.phase) {
9246
+ case "questioning":
9247
+ if (!this.session.spec) {
9248
+ throw new Error("Cannot approve: no spec generated yet.");
9249
+ }
9250
+ this.session.phase = "spec_review";
9251
+ break;
9252
+ case "spec_review":
9253
+ this.session.phase = "implementation";
9254
+ break;
9255
+ case "implementation":
9256
+ this.session.phase = "task_review";
9257
+ break;
9258
+ case "task_review":
9259
+ this.session.phase = "executing";
9260
+ break;
9261
+ case "executing":
9262
+ this.session.phase = "done";
9263
+ break;
9264
+ }
9265
+ this.session.approved = true;
9266
+ this.session.updatedAt = Date.now();
9267
+ this.autoSave();
9268
+ return this.session.phase;
9269
+ }
9270
+ /**
9271
+ * Set the implementation plan text.
9272
+ */
9273
+ setImplementation(plan) {
9274
+ this.session.implementation = plan;
9275
+ this.session.phase = "task_review";
9276
+ this.session.updatedAt = Date.now();
9277
+ this.autoSave();
9278
+ }
9279
+ /**
9280
+ * Mark session as done.
9281
+ */
9282
+ markDone() {
9283
+ this.session.phase = "done";
9284
+ this.session.updatedAt = Date.now();
9285
+ this.autoSave();
9286
+ }
9287
+ /**
9288
+ * Set the task graph ID for this session.
9289
+ */
9290
+ setTaskGraphId(graphId) {
9291
+ this.session.taskGraphId = graphId;
9292
+ this.autoSave();
9293
+ }
9294
+ /**
9295
+ * Get the task graph ID for this session.
9296
+ */
9297
+ getTaskGraphId() {
9298
+ return this.session.taskGraphId;
9299
+ }
9300
+ // ── Spec Persistence ──────────────────────────────────────────────────────
9301
+ /**
9302
+ * Save the current spec to the store.
9303
+ */
9304
+ async saveSpec() {
9305
+ if (!this.session.spec) {
9306
+ throw new Error("No spec to save.");
9307
+ }
9308
+ await this.store.save(this.session.spec);
9309
+ return this.session.spec;
9310
+ }
9311
+ // ── Spec Generation Helpers ───────────────────────────────────────────────
9312
+ /**
9313
+ * Parse a spec from a JSON string (from AI output).
9314
+ * Validates and normalizes the structure.
9315
+ */
9316
+ parseSpecFromJSON(jsonStr) {
9317
+ let parsed;
9318
+ try {
9319
+ parsed = JSON.parse(jsonStr);
9320
+ } catch (e) {
9321
+ throw new Error(`Invalid JSON for spec: ${e instanceof Error ? e.message : "parse error"}`);
9322
+ }
9323
+ if (!parsed || typeof parsed !== "object") {
9324
+ throw new Error("Spec JSON must be an object.");
9325
+ }
9326
+ const raw = parsed;
9327
+ const now = Date.now();
9328
+ const title = String(raw.title ?? this.session.title ?? "Untitled");
9329
+ const overview = String(raw.overview ?? "");
9330
+ if (!overview || overview === "undefined") {
9331
+ throw new Error("Spec must have an overview.");
9332
+ }
9333
+ const rawSections = Array.isArray(raw.sections) ? raw.sections : [];
9334
+ const sections = rawSections.filter((s) => s && typeof s === "object").map((s) => ({
9335
+ type: ["overview", "requirements", "architecture", "api", "data", "security", "acceptance"].includes(String(s.type)) ? String(s.type) : "overview",
9336
+ title: String(s.title ?? ""),
9337
+ content: String(s.content ?? ""),
9338
+ level: Number(s.level) || 1
9339
+ }));
9340
+ const rawReqs = Array.isArray(raw.requirements) ? raw.requirements : [];
9341
+ const requirements = rawReqs.filter((r) => r && typeof r === "object").map((r, i) => ({
9342
+ id: String(r.id ?? `REQ-${i + 1}`),
9343
+ type: ["functional", "non-functional", "security", "performance", "ux"].includes(String(r.type)) ? String(r.type) : "functional",
9344
+ priority: ["critical", "high", "medium", "low"].includes(String(r.priority)) ? String(r.priority) : "medium",
9345
+ description: String(r.description ?? ""),
9346
+ acceptanceCriteria: Array.isArray(r.acceptanceCriteria) ? r.acceptanceCriteria.map(String) : []
9347
+ }));
9348
+ const spec = {
9349
+ id: crypto.randomUUID(),
9350
+ title,
9351
+ version: "0.1.0",
9352
+ status: "draft",
9353
+ overview,
9354
+ sections,
9355
+ requirements,
9356
+ createdAt: now,
9357
+ updatedAt: now,
9358
+ metadata: {
9359
+ generatedBy: "AISpecBuilder",
9360
+ sessionId: this.session.id
9361
+ }
9362
+ };
9363
+ return spec;
9364
+ }
9365
+ /**
9366
+ * Extract JSON from AI output (handles ```json blocks and raw JSON).
9367
+ */
9368
+ extractJSON(text) {
9369
+ const codeBlockMatch = text.match(/```json\s*([\s\S]*?)```/);
9370
+ if (codeBlockMatch?.[1]) {
9371
+ return codeBlockMatch[1].trim();
9372
+ }
9373
+ const genericBlockMatch = text.match(/```\s*([\s\S]*?)```/);
9374
+ if (genericBlockMatch?.[1]) {
9375
+ const trimmed = genericBlockMatch[1].trim();
9376
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
9377
+ return trimmed;
9378
+ }
9379
+ }
9380
+ const jsonMatch = text.match(/(\{[\s\S]*\})/);
9381
+ if (jsonMatch?.[1]) {
9382
+ try {
9383
+ JSON.parse(jsonMatch[1]);
9384
+ return jsonMatch[1];
9385
+ } catch {
9386
+ }
9387
+ }
9388
+ return null;
9389
+ }
9390
+ /**
9391
+ * Detect if AI output contains a spec (JSON block).
9392
+ */
9393
+ hasSpecInOutput(text) {
9394
+ return this.extractJSON(text) !== null;
9395
+ }
9396
+ /**
9397
+ * Try to parse a spec from AI output text.
9398
+ * Returns null if no valid spec found.
9399
+ */
9400
+ tryParseSpecFromOutput(text) {
9401
+ const json = this.extractJSON(text);
9402
+ if (!json) return null;
9403
+ try {
9404
+ return this.parseSpecFromJSON(json);
9405
+ } catch {
9406
+ return null;
9407
+ }
9408
+ }
9409
+ // ── JSON Array Extraction (for tasks) ─────────────────────────────────────
9410
+ /**
9411
+ * Extract a JSON array from AI output (for task lists).
9412
+ */
9413
+ extractJSONArray(text) {
9414
+ const codeBlockMatch = text.match(/```json\s*([\s\S]*?)```/);
9415
+ if (codeBlockMatch?.[1]) {
9416
+ const trimmed = codeBlockMatch[1].trim();
9417
+ if (trimmed.startsWith("[")) return trimmed;
9418
+ }
9419
+ const arrayMatch = text.match(/(\[[\s\S]*\])/);
9420
+ if (arrayMatch?.[1]) {
9421
+ try {
9422
+ const parsed = JSON.parse(arrayMatch[1]);
9423
+ if (Array.isArray(parsed)) return arrayMatch[1];
9424
+ } catch {
9425
+ }
9426
+ }
9427
+ return null;
9428
+ }
9429
+ };
9430
+
9431
+ // src/sdd/spec-templates.ts
9432
+ var SPEC_TEMPLATES = [
9433
+ {
9434
+ id: "feature",
9435
+ name: "New Feature",
9436
+ description: "Template for new feature development",
9437
+ sections: [
9438
+ { type: "overview", title: "Overview", level: 2 },
9439
+ { type: "requirements", title: "Requirements", level: 2 },
9440
+ { type: "architecture", title: "Architecture", level: 2 },
9441
+ { type: "api", title: "API Design", level: 2 },
9442
+ { type: "data", title: "Data Model", level: 2 },
9443
+ { type: "security", title: "Security", level: 2 },
9444
+ { type: "acceptance", title: "Acceptance Criteria", level: 2 }
9445
+ ],
9446
+ defaultRequirements: [
9447
+ { type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
9448
+ { type: "non-functional", priority: "medium", acceptanceCriteria: [], blockedBy: [], implements: [] }
9449
+ ]
9450
+ },
9451
+ {
9452
+ id: "bugfix",
9453
+ name: "Bug Fix",
9454
+ description: "Template for bug fix specifications",
9455
+ sections: [
9456
+ { type: "overview", title: "Bug Description", level: 2 },
9457
+ { type: "requirements", title: "Root Cause Analysis", level: 2 },
9458
+ { type: "acceptance", title: "Fix Verification", level: 2 }
9459
+ ],
9460
+ defaultRequirements: [
9461
+ { type: "functional", priority: "critical", acceptanceCriteria: [], blockedBy: [], implements: [] }
9462
+ ]
9463
+ },
9464
+ {
9465
+ id: "refactor",
9466
+ name: "Refactor",
9467
+ description: "Template for code refactoring",
9468
+ sections: [
9469
+ { type: "overview", title: "Current State", level: 2 },
9470
+ { type: "requirements", title: "Refactoring Goals", level: 2 },
9471
+ { type: "architecture", title: "Target Architecture", level: 2 },
9472
+ { type: "acceptance", title: "Verification", level: 2 }
9473
+ ],
9474
+ defaultRequirements: [
9475
+ { type: "non-functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
9476
+ ]
9477
+ },
9478
+ {
9479
+ id: "infra",
9480
+ name: "Infrastructure",
9481
+ description: "Template for infrastructure/tooling changes",
9482
+ sections: [
9483
+ { type: "overview", title: "What and Why", level: 2 },
9484
+ { type: "requirements", title: "Requirements", level: 2 },
9485
+ { type: "architecture", title: "Design", level: 2 },
9486
+ { type: "security", title: "Security Impact", level: 2 },
9487
+ { type: "acceptance", title: "Rollout Plan", level: 2 }
9488
+ ],
9489
+ defaultRequirements: [
9490
+ { type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
9491
+ { type: "security", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
9492
+ ]
9493
+ },
9494
+ {
9495
+ id: "integration",
9496
+ name: "Integration",
9497
+ description: "Template for integrating external services or APIs",
9498
+ sections: [
9499
+ { type: "overview", title: "Integration Overview", level: 2 },
9500
+ { type: "requirements", title: "Integration Requirements", level: 2 },
9501
+ { type: "api", title: "API Contract", level: 2 },
9502
+ { type: "architecture", title: "Architecture", level: 2 },
9503
+ { type: "security", title: "Auth & Security", level: 2 },
9504
+ { type: "acceptance", title: "Testing Strategy", level: 2 }
9505
+ ],
9506
+ defaultRequirements: [
9507
+ { type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
9508
+ { type: "security", priority: "critical", acceptanceCriteria: [], blockedBy: [], implements: [] },
9509
+ { type: "performance", priority: "medium", acceptanceCriteria: [], blockedBy: [], implements: [] }
9510
+ ]
9511
+ },
9512
+ {
9513
+ id: "cli-command",
9514
+ name: "CLI Command",
9515
+ description: "Template for new CLI commands/slash commands",
9516
+ sections: [
9517
+ { type: "overview", title: "Command Overview", level: 2 },
9518
+ { type: "requirements", title: "Command Requirements", level: 2 },
9519
+ { type: "api", title: "Command Interface", level: 2 },
9520
+ { type: "acceptance", title: "Usage Examples", level: 2 }
9521
+ ],
9522
+ defaultRequirements: [
9523
+ { type: "ux", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
9524
+ { type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] }
9525
+ ]
9526
+ }
9527
+ ];
9528
+ function getTemplate(id) {
9529
+ return SPEC_TEMPLATES.find((t) => t.id === id);
9530
+ }
9531
+ function listTemplates() {
9532
+ return SPEC_TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description }));
9533
+ }
9534
+ function templateToMarkdown(template, title) {
9535
+ const lines = [];
9536
+ lines.push(`# ${title ?? "Untitled Specification"}`);
9537
+ lines.push("Version: 0.1.0");
9538
+ lines.push("");
9539
+ for (const section of template.sections) {
9540
+ lines.push(`${"#".repeat(section.level + 1)} ${section.title}`);
9541
+ lines.push(`_<!-- ${section.type} section content -->_`);
9542
+ lines.push("");
9543
+ }
9544
+ return lines.join("\n");
9545
+ }
9546
+
9547
+ // src/sdd/task-visualizer.ts
9548
+ var STATUS_ICON = {
9549
+ pending: "\u25CB",
9550
+ in_progress: "\u25D0",
9551
+ blocked: "\u2298",
9552
+ failed: "\u2717",
9553
+ review: "\u25D1",
9554
+ completed: "\u25CF"
9555
+ };
9556
+ var PRIORITY_ICON = {
9557
+ critical: "\u{1F534}",
9558
+ high: "\u{1F7E0}",
9559
+ medium: "\u{1F7E1}",
9560
+ low: "\u{1F7E2}"
9561
+ };
9562
+ var TYPE_ICON = {
9563
+ feature: "\u26A1",
9564
+ bugfix: "\u{1F41B}",
9565
+ refactor: "\u267B\uFE0F",
9566
+ docs: "\u{1F4DD}",
9567
+ test: "\u{1F9EA}",
9568
+ chore: "\u{1F527}"
9569
+ };
9570
+ function renderTaskGraph(graph, opts) {
9571
+ const lines = [];
9572
+ const compact = opts?.compact ?? false;
9573
+ lines.push(`\u256D\u2500 Task Graph: ${graph.title} \u2500\u256E`);
9574
+ lines.push(`\u2502 Spec: ${graph.specId.slice(0, 8)}... \u2502 Nodes: ${graph.nodes.size} \u2502 Edges: ${graph.edges.length} \u2502`);
9575
+ lines.push("\u2570" + "\u2500".repeat(Math.max(50, graph.title.length + 30)) + "\u256F");
9576
+ lines.push("");
9577
+ const progress = computeTaskProgress(graph);
9578
+ lines.push(renderProgress(progress));
9579
+ lines.push("");
9580
+ const childrenMap = /* @__PURE__ */ new Map();
9581
+ for (const edge of graph.edges) {
9582
+ if (edge.type === "depends_on") {
9583
+ const deps = childrenMap.get(edge.from) ?? [];
9584
+ deps.push(edge.to);
9585
+ childrenMap.set(edge.from, deps);
9586
+ }
9587
+ }
9588
+ const rendered = /* @__PURE__ */ new Set();
9589
+ const rootNodes = graph.rootNodes.filter((id) => graph.nodes.has(id));
9590
+ const startNodes = rootNodes.length > 0 ? rootNodes : Array.from(graph.nodes.keys()).filter((id) => {
9591
+ const deps = childrenMap.get(id);
9592
+ return !deps || deps.length === 0;
9593
+ });
9594
+ for (const rootId of startNodes) {
9595
+ renderNode(graph, rootId, lines, rendered, childrenMap, compact, "");
9596
+ }
9597
+ for (const [id] of graph.nodes) {
9598
+ if (!rendered.has(id)) {
9599
+ renderNode(graph, id, lines, rendered, childrenMap, compact, "");
9600
+ }
9601
+ }
9602
+ lines.push("");
9603
+ lines.push("Legend: \u25CF done \u25D0 in-progress \u25CB pending \u2297 blocked \u2717 failed \u25D2 review");
9604
+ return lines.join("\n");
9605
+ }
9606
+ function renderNode(graph, nodeId, lines, rendered, childrenMap, compact, prefix) {
9607
+ if (rendered.has(nodeId)) return;
9608
+ rendered.add(nodeId);
9609
+ const node = graph.nodes.get(nodeId);
9610
+ if (!node) return;
9611
+ const icon = STATUS_ICON[node.status];
9612
+ const prioIcon = PRIORITY_ICON[node.priority];
9613
+ const typeIcon = TYPE_ICON[node.type];
9614
+ const title = compact ? truncate2(node.title, 40) : node.title;
9615
+ const blockedBy = childrenMap.get(nodeId) ?? [];
9616
+ const depsStr = blockedBy.length > 0 ? ` \u2190 [${blockedBy.map((d) => graph.nodes.get(d)?.title?.slice(0, 12) ?? "?").join(", ")}]` : "";
9617
+ lines.push(`${prefix}${icon} ${typeIcon} ${prioIcon} ${title}${depsStr}`);
9618
+ if (!compact && node.description) {
9619
+ const descLines = node.description.split("\n").slice(0, 3);
9620
+ for (const dl of descLines) {
9621
+ lines.push(`${prefix} \u2514 ${truncate2(dl, 60)}`);
9622
+ }
9623
+ }
9624
+ const dependents = graph.edges.filter((e) => e.type === "depends_on" && e.to === nodeId).map((e) => e.from).filter((id) => graph.nodes.has(id));
9625
+ for (const depId of dependents) {
9626
+ renderNode(graph, depId, lines, rendered, childrenMap, compact, prefix + " ");
9627
+ }
9628
+ }
9629
+ function renderProgress(progress) {
9630
+ const barWidth = 30;
9631
+ const filled = Math.round(progress.percentComplete / 100 * barWidth);
9632
+ const empty = barWidth - filled;
9633
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
9634
+ return [
9635
+ `Progress: [${bar}] ${progress.percentComplete}%`,
9636
+ ` ${progress.completed} done \u2502 ${progress.inProgress} active \u2502 ${progress.pending} pending \u2502 ${progress.blocked} blocked \u2502 ${progress.failed} failed`
9637
+ ].join("\n");
9638
+ }
9639
+ function renderTaskList(graph) {
9640
+ const lines = [];
9641
+ const nodes = Array.from(graph.nodes.values());
9642
+ const groups = {
9643
+ in_progress: [],
9644
+ pending: [],
9645
+ blocked: [],
9646
+ review: [],
9647
+ failed: [],
9648
+ completed: []
9649
+ };
9650
+ for (const node of nodes) {
9651
+ groups[node.status]?.push(node);
9652
+ }
9653
+ for (const [status, group] of Object.entries(groups)) {
9654
+ if (group.length === 0) continue;
9655
+ const icon = STATUS_ICON[status];
9656
+ lines.push(`${icon} ${status.toUpperCase()} (${group.length})`);
9657
+ for (const node of group) {
9658
+ const prio = PRIORITY_ICON[node.priority];
9659
+ const type = TYPE_ICON[node.type];
9660
+ lines.push(` ${type} ${prio} ${node.title}`);
9661
+ }
9662
+ lines.push("");
9663
+ }
9664
+ return lines.join("\n");
9665
+ }
9666
+ function renderSpecAnalysis(spec, analysis) {
9667
+ const lines = [];
9668
+ lines.push(`\u256D\u2500 Spec Analysis: ${spec.title} \u2500\u256E`);
9669
+ lines.push("");
9670
+ const barWidth = 20;
9671
+ const filled = Math.round(analysis.completeness / 100 * barWidth);
9672
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
9673
+ lines.push(`Completeness: [${bar}] ${analysis.completeness}%`);
9674
+ lines.push("");
9675
+ if (analysis.gaps.length > 0) {
9676
+ lines.push("\u26A0 Gaps:");
9677
+ for (const gap of analysis.gaps) {
9678
+ lines.push(` \u2022 ${gap}`);
9679
+ }
9680
+ lines.push("");
9681
+ }
9682
+ if (analysis.risks.length > 0) {
9683
+ lines.push("\u{1F534} Risks:");
9684
+ for (const risk of analysis.risks) {
9685
+ lines.push(` \u2022 ${risk}`);
9686
+ }
9687
+ lines.push("");
9688
+ }
9689
+ if (analysis.suggestions.length > 0) {
9690
+ lines.push("\u{1F4A1} Suggestions:");
9691
+ for (const sug of analysis.suggestions) {
9692
+ lines.push(` \u2022 ${sug}`);
9693
+ }
9694
+ }
9695
+ return lines.join("\n");
9696
+ }
9697
+ function truncate2(str, maxLen) {
9698
+ if (str.length <= maxLen) return str;
9699
+ return str.slice(0, maxLen - 1) + "\u2026";
9700
+ }
9701
+
9702
+ // src/sdd/critical-path.ts
9703
+ function analyzeCriticalPath(graph) {
9704
+ const nodes = Array.from(graph.nodes.values());
9705
+ const topoOrder = topologicalSort(graph);
9706
+ const blockedByMap = /* @__PURE__ */ new Map();
9707
+ const blocksMap = /* @__PURE__ */ new Map();
9708
+ for (const edge of graph.edges) {
9709
+ if (edge.type === "depends_on") {
9710
+ if (!blockedByMap.has(edge.from)) blockedByMap.set(edge.from, /* @__PURE__ */ new Set());
9711
+ blockedByMap.get(edge.from).add(edge.to);
9712
+ if (!blocksMap.has(edge.to)) blocksMap.set(edge.to, /* @__PURE__ */ new Set());
9713
+ blocksMap.get(edge.to).add(edge.from);
9714
+ }
9715
+ }
9716
+ const readyTasks = [];
9717
+ const blockedTasks = [];
9718
+ for (const node of nodes) {
9719
+ if (node.status === "completed") continue;
9720
+ const blockers = blockedByMap.get(node.id);
9721
+ if (!blockers || blockers.size === 0) {
9722
+ readyTasks.push(node.id);
9723
+ } else {
9724
+ const allCompleted = Array.from(blockers).every((id) => {
9725
+ const n = graph.nodes.get(id);
9726
+ return n?.status === "completed";
9727
+ });
9728
+ if (allCompleted) {
9729
+ readyTasks.push(node.id);
9730
+ } else {
9731
+ blockedTasks.push(node.id);
9732
+ }
9733
+ }
9734
+ }
9735
+ const bottlenecks = [];
9736
+ for (const node of nodes) {
9737
+ if (node.status === "completed") continue;
9738
+ const downstream = getTransitiveBlocked(graph, node.id, blocksMap);
9739
+ if (downstream.size > 0) {
9740
+ const blockedHours = Array.from(downstream).reduce((sum, id) => {
9741
+ const n = graph.nodes.get(id);
9742
+ return sum + (n?.estimateHours ?? 0);
9743
+ }, 0);
9744
+ bottlenecks.push({
9745
+ taskId: node.id,
9746
+ title: node.title,
9747
+ blockedCount: downstream.size,
9748
+ blockedHours,
9749
+ severity: Math.min(100, Math.round(downstream.size / nodes.length * 100))
9750
+ });
9751
+ }
9752
+ }
9753
+ bottlenecks.sort((a, b) => b.severity - a.severity);
9754
+ const criticalPath = computeCriticalPath(graph, topoOrder, blockedByMap);
9755
+ const totalHours = criticalPath.reduce((sum, id) => {
9756
+ const n = graph.nodes.get(id);
9757
+ return sum + (n?.estimateHours ?? 0);
9758
+ }, 0);
9759
+ const parallelGroups = computeParallelGroups(graph, blockedByMap);
9760
+ const executionOrder = topoOrder.filter((id) => {
9761
+ const n = graph.nodes.get(id);
9762
+ return n && n.status !== "completed";
9763
+ });
9764
+ return {
9765
+ criticalPath,
9766
+ totalHours,
9767
+ bottlenecks,
9768
+ parallelGroups,
9769
+ executionOrder,
9770
+ readyTasks,
9771
+ blockedTasks
9772
+ };
9773
+ }
9774
+ function getTransitiveBlocked(graph, taskId, blocksMap) {
9775
+ const visited = /* @__PURE__ */ new Set();
9776
+ const queue = [taskId];
9777
+ while (queue.length > 0) {
9778
+ const current = queue.shift();
9779
+ const blocked = blocksMap.get(current);
9780
+ if (!blocked) continue;
9781
+ for (const id of blocked) {
9782
+ if (!visited.has(id) && id !== taskId) {
9783
+ visited.add(id);
9784
+ queue.push(id);
9785
+ }
9786
+ }
9787
+ }
9788
+ return visited;
9789
+ }
9790
+ function computeCriticalPath(graph, topoOrder, blockedByMap) {
9791
+ const allIds = Array.from(graph.nodes.keys());
9792
+ if (allIds.length === 0) return [];
9793
+ const dist = /* @__PURE__ */ new Map();
9794
+ const prev = /* @__PURE__ */ new Map();
9795
+ for (const id of allIds) {
9796
+ dist.set(id, graph.nodes.get(id)?.estimateHours ?? 1);
9797
+ prev.set(id, null);
9798
+ }
9799
+ const blocksMap = /* @__PURE__ */ new Map();
9800
+ for (const [taskId, blockers] of blockedByMap) {
9801
+ for (const blockerId of blockers) {
9802
+ if (!blocksMap.has(blockerId)) blocksMap.set(blockerId, /* @__PURE__ */ new Set());
9803
+ blocksMap.get(blockerId).add(taskId);
9804
+ }
9805
+ }
9806
+ const n = allIds.length;
9807
+ for (let i = 0; i < n - 1; i++) {
9808
+ let changed = false;
9809
+ for (const id of allIds) {
9810
+ const blocked = blocksMap.get(id);
9811
+ if (!blocked) continue;
9812
+ for (const blockedId of blocked) {
9813
+ const candidateDist = (dist.get(id) ?? 0) + (graph.nodes.get(blockedId)?.estimateHours ?? 1);
9814
+ if (candidateDist > (dist.get(blockedId) ?? 0)) {
9815
+ dist.set(blockedId, candidateDist);
9816
+ prev.set(blockedId, id);
9817
+ changed = true;
9818
+ }
9819
+ }
9820
+ }
9821
+ if (!changed) break;
9822
+ }
9823
+ let maxDist = 0;
9824
+ let maxId = allIds[0];
9825
+ for (const id of allIds) {
9826
+ const d = dist.get(id) ?? 0;
9827
+ if (d > maxDist) {
9828
+ maxDist = d;
9829
+ maxId = id;
9830
+ }
9831
+ }
9832
+ const path17 = [];
9833
+ let current = maxId;
9834
+ const visited = /* @__PURE__ */ new Set();
9835
+ while (current && !visited.has(current)) {
9836
+ visited.add(current);
9837
+ path17.unshift(current);
9838
+ current = prev.get(current) ?? null;
9839
+ }
9840
+ return path17;
9841
+ }
9842
+ function computeParallelGroups(graph, blockedByMap) {
9843
+ const groups = [];
9844
+ const assigned = /* @__PURE__ */ new Set();
9845
+ const nodes = Array.from(graph.nodes.values()).filter((n) => n.status !== "completed");
9846
+ const remaining = new Set(nodes.map((n) => n.id));
9847
+ while (remaining.size > 0) {
9848
+ const group = [];
9849
+ for (const id of remaining) {
9850
+ const blockers = blockedByMap.get(id);
9851
+ if (!blockers || blockers.size === 0) {
9852
+ group.push(id);
9853
+ } else {
9854
+ const allAssigned = Array.from(blockers).every((b) => assigned.has(b));
9855
+ if (allAssigned) {
9856
+ group.push(id);
9857
+ }
9858
+ }
9859
+ }
9860
+ if (group.length === 0) {
9861
+ const first = Array.from(remaining)[0];
9862
+ if (first) group.push(first);
9863
+ }
9864
+ for (const id of group) {
9865
+ assigned.add(id);
9866
+ remaining.delete(id);
9867
+ }
9868
+ groups.push(group);
9869
+ }
9870
+ return groups;
9871
+ }
9872
+
9873
+ // src/sdd/spec-versioning.ts
9874
+ var SpecVersioning = class {
9875
+ versions = /* @__PURE__ */ new Map();
9876
+ /** Record a new version of a spec. */
9877
+ recordVersion(spec, changeDescription) {
9878
+ const version = {
9879
+ version: spec.version,
9880
+ spec: { ...spec },
9881
+ timestamp: Date.now(),
9882
+ changeDescription
9883
+ };
9884
+ const history = this.versions.get(spec.id) ?? [];
9885
+ history.push(version);
9886
+ this.versions.set(spec.id, history);
9887
+ return version;
9888
+ }
9889
+ /** Get version history for a spec. */
9890
+ getHistory(specId) {
9891
+ return this.versions.get(specId) ?? [];
9892
+ }
9893
+ /** Get a specific version of a spec. */
9894
+ getVersion(specId, version) {
9895
+ const history = this.versions.get(specId) ?? [];
9896
+ return history.find((v) => v.version === version);
9897
+ }
9898
+ /** Get the latest version of a spec. */
9899
+ getLatest(specId) {
9900
+ const history = this.versions.get(specId) ?? [];
9901
+ return history[history.length - 1];
9902
+ }
9903
+ /** Compute diff between two versions of a spec. */
9904
+ diff(oldSpec, newSpec) {
9905
+ const oldReqs = new Map(oldSpec.requirements.map((r) => [r.id, r]));
9906
+ const newReqs = new Map(newSpec.requirements.map((r) => [r.id, r]));
9907
+ const added = [];
9908
+ const removed = [];
9909
+ const modified = [];
9910
+ for (const [id, newReq] of newReqs) {
9911
+ const oldReq = oldReqs.get(id);
9912
+ if (!oldReq) {
9913
+ added.push(newReq);
9914
+ } else {
9915
+ const changes = this.compareRequirements(oldReq, newReq);
9916
+ if (changes.length > 0) {
9917
+ modified.push({
9918
+ requirement: newReq,
9919
+ previousVersion: oldReq,
9920
+ changes
9921
+ });
9922
+ }
9923
+ }
9924
+ }
9925
+ for (const [id, oldReq] of oldReqs) {
9926
+ if (!newReqs.has(id)) {
9927
+ removed.push(oldReq);
9928
+ }
9929
+ }
9930
+ const parts = [];
9931
+ if (added.length > 0) parts.push(`${added.length} added`);
9932
+ if (removed.length > 0) parts.push(`${removed.length} removed`);
9933
+ if (modified.length > 0) parts.push(`${modified.length} modified`);
9934
+ return {
9935
+ added,
9936
+ removed,
9937
+ modified,
9938
+ summary: parts.length > 0 ? parts.join(", ") : "No changes"
9939
+ };
9940
+ }
9941
+ /**
9942
+ * Update a task graph incrementally based on spec changes.
9943
+ * - Added requirements → new tasks
9944
+ * - Removed requirements → remove tasks
9945
+ * - Modified requirements → update task descriptions
9946
+ * Returns the updated graph and list of changes made.
9947
+ */
9948
+ updateTaskGraph(graph, oldSpec, newSpec) {
9949
+ const specDiff = this.diff(oldSpec, newSpec);
9950
+ const changes = [];
9951
+ const reqToTask = /* @__PURE__ */ new Map();
9952
+ for (const node of graph.nodes.values()) {
9953
+ if (node.specRequirementId) {
9954
+ reqToTask.set(node.specRequirementId, node);
9955
+ }
9956
+ }
9957
+ for (const req of specDiff.removed) {
9958
+ const task = reqToTask.get(req.id);
9959
+ if (task) {
9960
+ graph.nodes.delete(task.id);
9961
+ graph.edges = graph.edges.filter((e) => e.from !== task.id && e.to !== task.id);
9962
+ changes.push(`Removed task: ${task.title}`);
9963
+ }
9964
+ }
9965
+ for (const mod of specDiff.modified) {
9966
+ const task = reqToTask.get(mod.requirement.id);
9967
+ if (task) {
9968
+ task.title = mod.requirement.description;
9969
+ task.description = this.buildTaskDescription(mod.requirement);
9970
+ task.priority = mod.requirement.priority;
9971
+ task.updatedAt = Date.now();
9972
+ changes.push(`Updated task: ${task.title} (${mod.changes.join(", ")})`);
9973
+ }
9974
+ }
9975
+ for (const req of specDiff.added) {
9976
+ const now = Date.now();
9977
+ const newTask = {
9978
+ id: crypto.randomUUID(),
9979
+ title: req.description,
9980
+ description: this.buildTaskDescription(req),
9981
+ type: this.mapReqType(req.type),
9982
+ priority: req.priority,
9983
+ status: "pending",
9984
+ specRequirementId: req.id,
9985
+ tags: [req.type, req.priority],
9986
+ createdAt: now,
9987
+ updatedAt: now
9988
+ };
9989
+ graph.nodes.set(newTask.id, newTask);
9990
+ graph.rootNodes.push(newTask.id);
9991
+ changes.push(`Added task: ${newTask.title}`);
9992
+ }
9993
+ graph.updatedAt = Date.now();
9994
+ return { graph, changes };
9995
+ }
9996
+ compareRequirements(old, current) {
9997
+ const changes = [];
9998
+ if (old.description !== current.description) changes.push("description");
9999
+ if (old.priority !== current.priority) changes.push("priority");
10000
+ if (old.type !== current.type) changes.push("type");
10001
+ if (JSON.stringify(old.acceptanceCriteria) !== JSON.stringify(current.acceptanceCriteria)) {
10002
+ changes.push("acceptance criteria");
10003
+ }
10004
+ if (JSON.stringify(old.blockedBy) !== JSON.stringify(current.blockedBy)) {
10005
+ changes.push("dependencies");
10006
+ }
10007
+ return changes;
10008
+ }
10009
+ buildTaskDescription(req) {
10010
+ const lines = [req.description, "", `**Type:** ${req.type}`, `**Priority:** ${req.priority}`];
10011
+ if (req.acceptanceCriteria.length > 0) {
10012
+ lines.push("", "**Acceptance Criteria:**");
10013
+ for (const ac of req.acceptanceCriteria) {
10014
+ lines.push(`- ${ac}`);
10015
+ }
10016
+ }
10017
+ return lines.join("\n");
10018
+ }
10019
+ mapReqType(type) {
10020
+ switch (type) {
10021
+ case "functional":
10022
+ return "feature";
10023
+ case "security":
10024
+ return "feature";
10025
+ case "performance":
10026
+ return "feature";
10027
+ case "ux":
10028
+ return "feature";
10029
+ default:
10030
+ return "feature";
10031
+ }
10032
+ }
10033
+ };
10034
+
10035
+ // src/sdd/auto-executor.ts
10036
+ var AutoExecutor = class {
10037
+ opts;
10038
+ stopped = false;
10039
+ retryMap = /* @__PURE__ */ new Map();
10040
+ constructor(opts) {
10041
+ this.opts = opts;
10042
+ }
10043
+ /**
10044
+ * Execute all tasks in the graph, respecting dependencies.
10045
+ */
10046
+ async execute(graph, spec) {
10047
+ this.stopped = false;
10048
+ this.retryMap.clear();
10049
+ const startTime = Date.now();
10050
+ const critical = analyzeCriticalPath(graph);
10051
+ let completed = 0;
10052
+ let failed = 0;
10053
+ const skipped = 0;
10054
+ let retried = 0;
10055
+ while (!this.stopped) {
10056
+ const readyTasks = this.getReadyTasks(graph);
10057
+ if (readyTasks.length === 0) {
10058
+ const allDone = Array.from(graph.nodes.values()).every(
10059
+ (n) => n.status === "completed" || n.status === "failed"
10060
+ );
10061
+ if (allDone) break;
10062
+ const hasDeadlock = this.detectDeadlock(graph);
10063
+ if (hasDeadlock) break;
10064
+ break;
10065
+ }
10066
+ const batch = readyTasks.slice(0, this.opts.maxConcurrent ?? 1);
10067
+ const results = await Promise.allSettled(
10068
+ batch.map((task) => this.executeTaskWithRetry(task, graph, spec))
10069
+ );
10070
+ for (let i = 0; i < results.length; i++) {
10071
+ const result = results[i];
10072
+ const task = batch[i];
10073
+ if (!result || !task) continue;
10074
+ if (result.status === "fulfilled") {
10075
+ const { result: execResult, retries } = result.value;
10076
+ if (execResult.success) {
10077
+ this.opts.tracker.updateNodeStatus(task.id, "completed");
10078
+ completed++;
10079
+ if (retries > 0) retried++;
10080
+ this.opts.onTaskComplete?.(task, execResult);
10081
+ } else if (execResult.retry) {
10082
+ retried++;
10083
+ } else {
10084
+ this.opts.tracker.updateNodeStatus(task.id, "failed", execResult.error);
10085
+ failed++;
10086
+ }
10087
+ } else {
10088
+ this.opts.tracker.updateNodeStatus(task.id, "failed", String(result.reason));
10089
+ failed++;
10090
+ this.opts.onTaskFail?.(task, result.reason, 0);
10091
+ }
10092
+ }
10093
+ }
10094
+ const duration = Date.now() - startTime;
10095
+ const summary = {
10096
+ total: graph.nodes.size,
10097
+ completed,
10098
+ failed,
10099
+ skipped,
10100
+ retried,
10101
+ duration,
10102
+ criticalPath: critical.criticalPath
10103
+ };
10104
+ this.opts.onDone?.(summary);
10105
+ return summary;
10106
+ }
10107
+ /** Stop execution. */
10108
+ stop() {
10109
+ this.stopped = true;
10110
+ }
10111
+ /** Get tasks that are ready to execute (all dependencies completed). */
10112
+ getReadyTasks(graph) {
10113
+ const ready = [];
10114
+ for (const node of graph.nodes.values()) {
10115
+ if (node.status !== "pending") continue;
10116
+ const blockers = graph.edges.filter((e) => e.type === "depends_on" && e.from === node.id).map((e) => graph.nodes.get(e.to)).filter(Boolean);
10117
+ const allBlockersDone = blockers.every((b) => b.status === "completed");
10118
+ if (allBlockersDone) {
10119
+ ready.push(node);
10120
+ }
10121
+ }
10122
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
10123
+ ready.sort((a, b) => (priorityOrder[a.priority] ?? 4) - (priorityOrder[b.priority] ?? 4));
10124
+ return ready;
10125
+ }
10126
+ /** Execute a single task with retry logic. */
10127
+ async executeTaskWithRetry(task, graph, spec) {
10128
+ const maxRetries = this.opts.maxRetries ?? 2;
10129
+ let retryCount = this.retryMap.get(task.id) ?? 0;
10130
+ while (retryCount <= maxRetries) {
10131
+ this.opts.tracker.updateNodeStatus(task.id, "in_progress");
10132
+ this.opts.onTaskStart?.(task);
10133
+ const dependencies = this.getTaskDependencies(task.id, graph);
10134
+ const dependents = this.getTaskDependents(task.id, graph);
10135
+ const context = {
10136
+ spec,
10137
+ graph,
10138
+ task,
10139
+ dependencies,
10140
+ dependents,
10141
+ retryCount
10142
+ };
10143
+ try {
10144
+ const result = await this.opts.executeTask(task, context);
10145
+ if (result.success) {
10146
+ const retriesForTask = this.retryMap.get(task.id) ?? 0;
10147
+ this.retryMap.delete(task.id);
10148
+ return { result, retries: retriesForTask };
10149
+ }
10150
+ if (result.retry && retryCount < maxRetries) {
10151
+ retryCount++;
10152
+ this.retryMap.set(task.id, retryCount);
10153
+ this.opts.tracker.updateNodeStatus(task.id, "pending");
10154
+ continue;
10155
+ }
10156
+ return { result, retries: retryCount };
10157
+ } catch (error) {
10158
+ if (retryCount < maxRetries) {
10159
+ retryCount++;
10160
+ this.retryMap.set(task.id, retryCount);
10161
+ this.opts.tracker.updateNodeStatus(task.id, "pending");
10162
+ this.opts.onTaskFail?.(task, error, retryCount);
10163
+ continue;
10164
+ }
10165
+ return {
10166
+ result: {
10167
+ success: false,
10168
+ error: error instanceof Error ? error.message : String(error)
10169
+ },
10170
+ retries: retryCount
10171
+ };
10172
+ }
10173
+ }
10174
+ return { result: { success: false, error: "Max retries exceeded" }, retries: retryCount };
10175
+ }
10176
+ /** Get tasks that this task depends on. */
10177
+ getTaskDependencies(taskId, graph) {
10178
+ return graph.edges.filter((e) => e.type === "depends_on" && e.from === taskId).map((e) => graph.nodes.get(e.to)).filter(Boolean);
10179
+ }
10180
+ /** Get tasks that depend on this task. */
10181
+ getTaskDependents(taskId, graph) {
10182
+ return graph.edges.filter((e) => e.type === "depends_on" && e.to === taskId).map((e) => graph.nodes.get(e.from)).filter(Boolean);
10183
+ }
10184
+ /** Detect deadlock: all remaining tasks are blocked by failed tasks. */
10185
+ detectDeadlock(graph) {
10186
+ const remaining = Array.from(graph.nodes.values()).filter(
10187
+ (n) => n.status === "pending" || n.status === "blocked"
10188
+ );
10189
+ if (remaining.length === 0) return false;
10190
+ return remaining.every((node) => {
10191
+ const blockers = graph.edges.filter((e) => e.type === "depends_on" && e.from === node.id).map((e) => graph.nodes.get(e.to)).filter(Boolean);
10192
+ return blockers.some((b) => b.status === "failed");
10193
+ });
10194
+ }
10195
+ };
10196
+ function createAutoExecutor(opts) {
10197
+ return new AutoExecutor({
10198
+ tracker: opts.tracker,
10199
+ events: opts.events,
10200
+ executeTask: opts.executeTask,
10201
+ maxConcurrent: opts.maxConcurrent,
10202
+ maxRetries: opts.maxRetries
10203
+ });
10204
+ }
10205
+
10206
+ // src/observability/metrics.ts
10207
+ var RESERVOIR_SIZE = 1024;
10208
+ function labelKey(labels) {
10209
+ if (!labels) return "";
10210
+ const keys = Object.keys(labels).sort();
10211
+ return keys.map((k) => `${k}=${labels[k]}`).join(",");
10212
+ }
10213
+ function quantile(sorted, q) {
10214
+ if (sorted.length === 0) return 0;
10215
+ const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
10216
+ return sorted[idx] ?? 0;
10217
+ }
10218
+ var InMemoryMetricsSink = class {
8277
10219
  counters = /* @__PURE__ */ new Map();
8278
10220
  gauges = /* @__PURE__ */ new Map();
8279
10221
  histograms = /* @__PURE__ */ new Map();
@@ -8605,7 +10547,7 @@ async function startMetricsServer(opts) {
8605
10547
  const tls = opts.tls;
8606
10548
  const useHttps = !!(tls?.cert && tls?.key);
8607
10549
  const host = opts.host ?? "127.0.0.1";
8608
- const path15 = opts.path ?? "/metrics";
10550
+ const path17 = opts.path ?? "/metrics";
8609
10551
  const healthPath = opts.healthPath ?? "/healthz";
8610
10552
  const healthRegistry = opts.healthRegistry;
8611
10553
  const listener = (req, res) => {
@@ -8615,7 +10557,7 @@ async function startMetricsServer(opts) {
8615
10557
  return;
8616
10558
  }
8617
10559
  const url = req.url.split("?")[0];
8618
- if (url === path15) {
10560
+ if (url === path17) {
8619
10561
  let body;
8620
10562
  try {
8621
10563
  body = renderPrometheus(opts.sink.snapshot());
@@ -8679,7 +10621,7 @@ async function startMetricsServer(opts) {
8679
10621
  const protocol = useHttps ? "https" : "http";
8680
10622
  return {
8681
10623
  port: boundPort,
8682
- url: `${protocol}://${host}:${boundPort}${path15}`,
10624
+ url: `${protocol}://${host}:${boundPort}${path17}`,
8683
10625
  close: () => new Promise((resolve2, reject) => {
8684
10626
  server.close((err) => err ? reject(err) : resolve2());
8685
10627
  })
@@ -9014,13 +10956,17 @@ function createContextManagerTool(opts = {}) {
9014
10956
  };
9015
10957
  switch (input.action) {
9016
10958
  case "check": {
10959
+ const estimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens, messages: beforeTokens, systemPrompt: 0, tools: 0 };
9017
10960
  return {
9018
10961
  action: "check",
9019
- beforeTokens,
10962
+ beforeTokens: estimate.total,
9020
10963
  messageCount: messages.length,
9021
10964
  notes: JSON.stringify({
9022
10965
  messages: messages.length,
9023
- tokens: beforeTokens,
10966
+ tokens: estimate.total,
10967
+ msgTokens: estimate.messages,
10968
+ sysTokens: estimate.systemPrompt,
10969
+ toolTokens: estimate.tools,
9024
10970
  readFiles: ctx.readFiles.size,
9025
10971
  todos: ctx.todos.length,
9026
10972
  inProgress: ctx.todos.filter((t) => t.status === "in_progress").length
@@ -9292,6 +11238,6 @@ var allServers = () => ({
9292
11238
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
9293
11239
  });
9294
11240
 
9295
- 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 };
11241
+ 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 };
9296
11242
  //# sourceMappingURL=index.js.map
9297
11243
  //# sourceMappingURL=index.js.map