@wrongstack/core 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sdd/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { randomBytes, randomUUID } from 'crypto';
2
2
  import * as fs from 'fs/promises';
3
3
  import * as path from 'path';
4
+ import { EventEmitter } from 'events';
4
5
 
5
6
  var __defProp = Object.defineProperty;
6
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -2493,6 +2494,3623 @@ function createAutoExecutor(opts) {
2493
2494
  });
2494
2495
  }
2495
2496
 
2496
- export { AISpecBuilder, AutoExecutor, DefaultTaskStore, SPEC_TEMPLATES, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, analyzeCriticalPath, createAutoExecutor, getTemplate, listTemplates, renderProgress, renderSpecAnalysis, renderTaskGraph, renderTaskList, templateToMarkdown };
2497
+ // src/sdd/sdd-task-decomposer.ts
2498
+ var SddTaskDecomposer = class {
2499
+ constructor(tracker, graph, opts = {}) {
2500
+ this.tracker = tracker;
2501
+ this.graph = graph;
2502
+ this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
2503
+ }
2504
+ tracker;
2505
+ graph;
2506
+ slots;
2507
+ wave = 0;
2508
+ // -------------------------------------------------------------------
2509
+ // Public API
2510
+ // -------------------------------------------------------------------
2511
+ /**
2512
+ * Return the next batch of runnable tasks.
2513
+ * Returns `allDone: true` when every node is completed.
2514
+ * Returns `deadlocked: true` when no batch can be produced because
2515
+ * all remaining tasks are blocked by failed nodes.
2516
+ */
2517
+ nextBatch() {
2518
+ if (this.isDone()) {
2519
+ return { tasks: [], wave: this.wave, allDone: true, deadlocked: false };
2520
+ }
2521
+ const pending = this.pendingReadyNodes();
2522
+ if (pending.length === 0) {
2523
+ const hasBlockedTasks = this.hasAnyBlockedTasks();
2524
+ return { tasks: [], wave: this.wave, allDone: false, deadlocked: hasBlockedTasks };
2525
+ }
2526
+ const batch = pending.slice(0, this.slots);
2527
+ return { tasks: batch, wave: this.wave, allDone: false, deadlocked: false };
2528
+ }
2529
+ /**
2530
+ * Advance the wave counter after a batch completes.
2531
+ * Call this once per `nextBatch()` result that was fan-out.
2532
+ */
2533
+ acknowledgeBatch(_completedTaskIds) {
2534
+ this.wave++;
2535
+ }
2536
+ /**
2537
+ * True when every node in the graph is completed.
2538
+ * Use this to exit the fan-out loop after `isDone() || deadlocked`.
2539
+ */
2540
+ isDone() {
2541
+ const progress = this.tracker.getProgress();
2542
+ return progress.total > 0 && progress.completed === progress.total;
2543
+ }
2544
+ /**
2545
+ * Total waves produced so far.
2546
+ */
2547
+ getWaveCount() {
2548
+ return this.wave;
2549
+ }
2550
+ // -------------------------------------------------------------------
2551
+ // Internal helpers
2552
+ // -------------------------------------------------------------------
2553
+ /**
2554
+ * Return pending nodes whose blockers are all completed.
2555
+ * Sorted by priority (critical first), then by creation time.
2556
+ */
2557
+ pendingReadyNodes() {
2558
+ const allPending = this.tracker.getAllNodes({ status: ["pending"] });
2559
+ const ready = [];
2560
+ for (const node of allPending) {
2561
+ if (this.tracker.canStart(node.id)) {
2562
+ ready.push(node);
2563
+ }
2564
+ }
2565
+ const priorityRank = {
2566
+ critical: 0,
2567
+ high: 1,
2568
+ medium: 2,
2569
+ low: 3
2570
+ };
2571
+ ready.sort((a, b) => {
2572
+ const pr = priorityRank[a.priority] - priorityRank[b.priority];
2573
+ if (pr !== 0) return pr;
2574
+ return a.createdAt - b.createdAt;
2575
+ });
2576
+ return ready;
2577
+ }
2578
+ /** True when at least one non-completed, non-failed task is blocked. */
2579
+ hasAnyBlockedTasks() {
2580
+ const nodes = this.tracker.getAllNodes({
2581
+ status: ["pending", "in_progress", "blocked"]
2582
+ });
2583
+ return nodes.some((n) => n.status === "blocked");
2584
+ }
2585
+ };
2586
+
2587
+ // src/coordination/subagent-budget.ts
2588
+ var BudgetExceededError = class extends Error {
2589
+ kind;
2590
+ limit;
2591
+ observed;
2592
+ constructor(kind, limit, observed) {
2593
+ super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
2594
+ this.name = "BudgetExceededError";
2595
+ this.kind = kind;
2596
+ this.limit = limit;
2597
+ this.observed = observed;
2598
+ }
2599
+ };
2600
+ var BudgetThresholdSignal = class extends Error {
2601
+ kind;
2602
+ limit;
2603
+ used;
2604
+ /** Resolves to 'extend' (with optional new limits) or 'stop' */
2605
+ decision;
2606
+ constructor(kind, limit, used, decision) {
2607
+ super(`Budget soft limit: ${kind} (limit=${limit}, used=${used})`);
2608
+ this.name = "BudgetThresholdSignal";
2609
+ this.kind = kind;
2610
+ this.limit = limit;
2611
+ this.used = used;
2612
+ this.decision = decision;
2613
+ }
2614
+ };
2615
+ var SubagentBudget = class _SubagentBudget {
2616
+ limits;
2617
+ iterations = 0;
2618
+ toolCalls = 0;
2619
+ tokenInput = 0;
2620
+ tokenOutput = 0;
2621
+ costUsd = 0;
2622
+ startTime = null;
2623
+ _onThreshold;
2624
+ /**
2625
+ * Tracks which budget kinds currently have an extension request
2626
+ * in flight. While a kind is here, further `checkLimit` calls for the
2627
+ * same kind are no-ops — without this dedup, every `recordIteration`
2628
+ * after the limit is reached spawns a fresh decision Promise (until
2629
+ * the first one lands and patches limits), flooding the FleetBus
2630
+ * with redundant threshold events. Cleared in `checkLimitAsync`'s
2631
+ * `finally`.
2632
+ */
2633
+ pendingExtensions = /* @__PURE__ */ new Set();
2634
+ /**
2635
+ * Hard cap on how long `checkLimitAsync` waits for the coordinator to
2636
+ * respond before defaulting to 'stop'. Without this fallback an absent
2637
+ * or hung listener (Director not built / event filter detached mid-run)
2638
+ * leaves the budget over-limit and never enforces anything, since
2639
+ * `checkLimit` returns synchronously via `void this.checkLimitAsync`.
2640
+ * Raised from 30s to 60s to give subagents more headroom before
2641
+ * the threshold negotiation times out and triggers a hard stop.
2642
+ */
2643
+ static DECISION_TIMEOUT_MS = 6e4;
2644
+ /**
2645
+ * Injected by the runner when wiring the budget to its EventBus.
2646
+ * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
2647
+ */
2648
+ _events;
2649
+ /**
2650
+ * Optional callback for soft-limit handling. When set, the budget will
2651
+ * call this instead of throwing when a limit is first reached. The
2652
+ * handler decides whether to throw, continue, or ask the coordinator.
2653
+ */
2654
+ get onThreshold() {
2655
+ return this._onThreshold;
2656
+ }
2657
+ set onThreshold(fn) {
2658
+ this._onThreshold = fn;
2659
+ }
2660
+ constructor(limits = {}) {
2661
+ this.limits = { ...limits };
2662
+ }
2663
+ start() {
2664
+ this.startTime = Date.now();
2665
+ }
2666
+ /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
2667
+ isNearLimit() {
2668
+ const { maxIterations, maxToolCalls, maxTokens, maxCostUsd } = this.limits;
2669
+ if (maxIterations && this.iterations >= maxIterations * 0.9) return true;
2670
+ if (maxToolCalls && this.toolCalls >= maxToolCalls * 0.9) return true;
2671
+ if (maxTokens && this.tokenInput + this.tokenOutput >= maxTokens * 0.9) return true;
2672
+ if (maxCostUsd && this.costUsd >= maxCostUsd * 0.9) return true;
2673
+ return false;
2674
+ }
2675
+ /**
2676
+ * Synchronous budget check — always throws synchronously so callers
2677
+ * (especially test event handlers using `expect().toThrow()`) get an
2678
+ * unhandled rejection when the budget is exceeded without a handler.
2679
+ * When `onThreshold` IS configured, the actual async threshold-handling
2680
+ * is dispatched as a fire-and-forget promise.
2681
+ */
2682
+ checkLimit(kind, used, limit) {
2683
+ if (!this._onThreshold) {
2684
+ throw new BudgetExceededError(kind, limit, used);
2685
+ }
2686
+ const bus = this._events;
2687
+ if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
2688
+ throw new BudgetExceededError(kind, limit, used);
2689
+ }
2690
+ if (this.pendingExtensions.has(kind)) return;
2691
+ this.pendingExtensions.add(kind);
2692
+ const decision = this.negotiateExtension(kind, used, limit);
2693
+ throw new BudgetThresholdSignal(kind, limit, used, decision);
2694
+ }
2695
+ /**
2696
+ * Drive the threshold handler to a decision. Resolves with `'stop'`
2697
+ * (signal the runner to abort) or `{ extend: ... }` (limits already
2698
+ * patched in-place; the runner should not abort). Always releases the
2699
+ * `pendingExtensions` slot in `finally`.
2700
+ *
2701
+ * The 'continue' return from a sync handler is treated as
2702
+ * `{ extend: {} }` — keep going without patching, next overrun will
2703
+ * fire a fresh signal.
2704
+ */
2705
+ async negotiateExtension(kind, used, limit) {
2706
+ try {
2707
+ const result = this._onThreshold({
2708
+ kind,
2709
+ used,
2710
+ limit,
2711
+ // Inject a requestDecision helper the handler can call to emit the
2712
+ // budget.threshold_reached event and wait for the coordinator's verdict.
2713
+ // A hard fallback timer guarantees the promise eventually resolves
2714
+ // even if no listener responds — without it, an absent/detached
2715
+ // Director would leave the budget permanently in "asking" state.
2716
+ requestDecision: () => {
2717
+ const bus = this._events;
2718
+ if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
2719
+ return Promise.resolve("stop");
2720
+ }
2721
+ return new Promise((resolve) => {
2722
+ let resolved = false;
2723
+ const respond = (d) => {
2724
+ if (resolved) return;
2725
+ resolved = true;
2726
+ resolve(d);
2727
+ };
2728
+ const fallback = setTimeout(
2729
+ () => respond("stop"),
2730
+ _SubagentBudget.DECISION_TIMEOUT_MS
2731
+ );
2732
+ bus.emit("budget.threshold_reached", {
2733
+ kind,
2734
+ used,
2735
+ limit,
2736
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2737
+ extend: (extra) => {
2738
+ clearTimeout(fallback);
2739
+ respond({ extend: extra });
2740
+ },
2741
+ deny: () => {
2742
+ clearTimeout(fallback);
2743
+ respond("stop");
2744
+ }
2745
+ });
2746
+ });
2747
+ }
2748
+ });
2749
+ if (result === "throw") return "stop";
2750
+ if (result === "continue") return { extend: {} };
2751
+ const decision = await result;
2752
+ if (decision === "stop") return "stop";
2753
+ const ext = decision.extend;
2754
+ if (ext.maxIterations !== void 0) {
2755
+ this.limits.maxIterations = ext.maxIterations;
2756
+ }
2757
+ if (ext.maxToolCalls !== void 0) {
2758
+ this.limits.maxToolCalls = ext.maxToolCalls;
2759
+ }
2760
+ if (ext.maxTokens !== void 0) {
2761
+ this.limits.maxTokens = ext.maxTokens;
2762
+ }
2763
+ if (ext.maxCostUsd !== void 0) {
2764
+ this.limits.maxCostUsd = ext.maxCostUsd;
2765
+ }
2766
+ if (ext.timeoutMs !== void 0) {
2767
+ this.limits.timeoutMs = ext.timeoutMs;
2768
+ }
2769
+ return decision;
2770
+ } finally {
2771
+ this.pendingExtensions.delete(kind);
2772
+ }
2773
+ }
2774
+ recordIteration() {
2775
+ this.iterations++;
2776
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2777
+ void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
2778
+ }
2779
+ }
2780
+ recordToolCall() {
2781
+ this.toolCalls++;
2782
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2783
+ void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
2784
+ }
2785
+ }
2786
+ recordUsage(usage, costUsd = 0) {
2787
+ this.tokenInput += usage.input;
2788
+ this.tokenOutput += usage.output;
2789
+ this.costUsd += costUsd;
2790
+ const totalTokens = this.tokenInput + this.tokenOutput;
2791
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2792
+ void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
2793
+ }
2794
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2795
+ void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
2796
+ }
2797
+ }
2798
+ /**
2799
+ * Wall-clock budget check. Unlike other limits, timeout is treated as a
2800
+ * warning-only event — it NEVER hard-stops the subagent. When the
2801
+ * elapsed time exceeds timeoutMs, emits `budget.threshold_reached` with
2802
+ * kind='timeout' so the Director can decide whether to extend or warn.
2803
+ * Call this from the iteration loop so a hung tool gets a chance to
2804
+ * negotiate more time before the coordinator's Promise.race kills it.
2805
+ */
2806
+ checkTimeout() {
2807
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return;
2808
+ const elapsed = Date.now() - this.startTime;
2809
+ if (elapsed > this.limits.timeoutMs) {
2810
+ void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
2811
+ }
2812
+ }
2813
+ /** Returns true if a timeout has occurred without throwing. Useful for races. */
2814
+ isTimedOut() {
2815
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
2816
+ return Date.now() - this.startTime > this.limits.timeoutMs;
2817
+ }
2818
+ usage() {
2819
+ return {
2820
+ iterations: this.iterations,
2821
+ toolCalls: this.toolCalls,
2822
+ tokens: {
2823
+ input: this.tokenInput,
2824
+ output: this.tokenOutput,
2825
+ total: this.tokenInput + this.tokenOutput
2826
+ },
2827
+ costUsd: this.costUsd,
2828
+ elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
2829
+ };
2830
+ }
2831
+ };
2832
+
2833
+ // src/coordination/agent-subagent-runner.ts
2834
+ function makeAgentSubagentRunner(opts) {
2835
+ const format = opts.formatTaskInput ?? defaultFormatTaskInput;
2836
+ return async (task, ctx) => {
2837
+ const factoryResult = await opts.factory(ctx.config);
2838
+ const { agent, events } = factoryResult;
2839
+ const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
2840
+ const aborter = new AbortController();
2841
+ ctx.budget._events = events;
2842
+ ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
2843
+ let budgetError = null;
2844
+ const onBudgetError = (err) => {
2845
+ if (err instanceof BudgetThresholdSignal) {
2846
+ err.decision.then((decision) => {
2847
+ if (decision === "stop") {
2848
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2849
+ aborter.abort();
2850
+ }
2851
+ }).catch(() => {
2852
+ budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
2853
+ aborter.abort();
2854
+ });
2855
+ return;
2856
+ }
2857
+ aborter.abort();
2858
+ budgetError = err instanceof BudgetExceededError ? err : new BudgetExceededError(
2859
+ "tool_calls",
2860
+ 0,
2861
+ 0
2862
+ );
2863
+ if (budgetError !== err && err instanceof Error) {
2864
+ budgetError.message += ` (caused by: ${err.message})`;
2865
+ }
2866
+ };
2867
+ let lastToolFailed = null;
2868
+ const unsub = [];
2869
+ unsub.push(
2870
+ events.on("tool.executed", (e) => {
2871
+ try {
2872
+ ctx.budget.recordToolCall();
2873
+ } catch (eb) {
2874
+ onBudgetError(eb);
2875
+ }
2876
+ if (e.ok === false) {
2877
+ lastToolFailed = e.name;
2878
+ } else if (e.ok === true) {
2879
+ lastToolFailed = null;
2880
+ }
2881
+ }),
2882
+ events.on("provider.response", (e) => {
2883
+ try {
2884
+ ctx.budget.recordUsage(e.usage);
2885
+ } catch (e2) {
2886
+ void onBudgetError(e2);
2887
+ }
2888
+ }),
2889
+ events.on("iteration.started", () => {
2890
+ try {
2891
+ ctx.budget.recordIteration();
2892
+ } catch (e) {
2893
+ void onBudgetError(e);
2894
+ }
2895
+ const u = ctx.budget.usage();
2896
+ const since = u.iterations - lastSummaryAtIteration;
2897
+ if (since >= SUMMARY_INTERVAL) {
2898
+ lastSummaryAtIteration = u.iterations;
2899
+ events.emit("subagent.iteration_summary", {
2900
+ subagentId: ctx.subagentId,
2901
+ iteration: u.iterations,
2902
+ toolCalls: u.toolCalls,
2903
+ costUsd: u.costUsd,
2904
+ currentTool: currentToolName,
2905
+ partialText: streamingTextAcc.trim() || void 0
2906
+ });
2907
+ }
2908
+ }),
2909
+ // D3: cooperative timeout enforcement DURING a long tool call.
2910
+ // The iteration-loop checkTimeout() only fires between agent
2911
+ // iterations — a single `bash sleep 3600` call would otherwise
2912
+ // park inside one tool execution while the timeout silently
2913
+ // passes, relying solely on the coordinator's hard Promise.race
2914
+ // to interrupt. Tools that emit `tool.progress` (bash chunks,
2915
+ // fetch byte progress, spawn-stream stdout) give us a heartbeat
2916
+ // we can hang the check on. When the budget trips here:
2917
+ // 1. onBudgetError sets budgetError + aborter.abort()
2918
+ // 2. aborter signal propagates to agent.run → tool executor
2919
+ // 3. tool's own signal listener kills the child process
2920
+ // Cheap: O(1) per progress event, and the budget short-circuits
2921
+ // when timeoutMs is unset (most subagents have one set anyway).
2922
+ events.on("tool.progress", () => {
2923
+ try {
2924
+ ctx.budget.checkTimeout();
2925
+ } catch (e) {
2926
+ void onBudgetError(e);
2927
+ }
2928
+ })
2929
+ );
2930
+ let currentToolName;
2931
+ let streamingTextAcc = "";
2932
+ let lastSummaryAtIteration = 0;
2933
+ const SUMMARY_INTERVAL = 25;
2934
+ unsub.push(
2935
+ events.on("tool.started", (e) => {
2936
+ currentToolName = e.name;
2937
+ }),
2938
+ events.on("provider.text_delta", (e) => {
2939
+ streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
2940
+ })
2941
+ );
2942
+ const onParentAbort = () => aborter.abort();
2943
+ ctx.signal.addEventListener("abort", onParentAbort);
2944
+ let result;
2945
+ try {
2946
+ result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
2947
+ } finally {
2948
+ detachFleet?.();
2949
+ ctx.signal.removeEventListener("abort", onParentAbort);
2950
+ for (const u of unsub) u();
2951
+ if (factoryResult.dispose) {
2952
+ try {
2953
+ await factoryResult.dispose();
2954
+ } catch {
2955
+ }
2956
+ }
2957
+ }
2958
+ if (budgetError) {
2959
+ if ("decision" in budgetError) {
2960
+ const decision = await budgetError.decision;
2961
+ if (decision === "stop") {
2962
+ budgetError = new BudgetExceededError(
2963
+ budgetError.kind,
2964
+ budgetError.limit,
2965
+ budgetError.used
2966
+ );
2967
+ } else {
2968
+ budgetError = null;
2969
+ }
2970
+ }
2971
+ if (budgetError) throw budgetError;
2972
+ }
2973
+ if (result.status === "failed") {
2974
+ throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
2975
+ }
2976
+ if (result.status === "aborted") {
2977
+ throw new Error("agent aborted");
2978
+ }
2979
+ if (result.status === "max_iterations") {
2980
+ throw new Error("agent exhausted iteration limit");
2981
+ }
2982
+ const usage = ctx.budget.usage();
2983
+ const finalText = (result.finalText ?? "").trim();
2984
+ if (finalText.length === 0 && usage.toolCalls === 0) {
2985
+ throw new Error("empty response");
2986
+ }
2987
+ if (finalText.length === 0 && lastToolFailed !== null) {
2988
+ throw new Error(`tool failed: ${lastToolFailed}`);
2989
+ }
2990
+ return {
2991
+ result: result.finalText,
2992
+ iterations: result.iterations,
2993
+ toolCalls: usage.toolCalls
2994
+ };
2995
+ };
2996
+ }
2997
+ function defaultFormatTaskInput(task) {
2998
+ return task.description ?? "";
2999
+ }
3000
+
3001
+ // src/types/errors.ts
3002
+ var ERROR_CODES = {
3003
+ // Provider
3004
+ PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
3005
+ PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
3006
+ PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
3007
+ PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
3008
+ PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
3009
+ PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR"};
3010
+ var WrongStackError = class extends Error {
3011
+ code;
3012
+ subsystem;
3013
+ severity;
3014
+ recoverable;
3015
+ context;
3016
+ constructor(opts) {
3017
+ super(opts.message, { cause: opts.cause });
3018
+ this.name = "WrongStackError";
3019
+ this.code = opts.code;
3020
+ this.subsystem = opts.subsystem;
3021
+ this.severity = opts.severity ?? "error";
3022
+ this.recoverable = opts.recoverable ?? false;
3023
+ this.context = opts.context;
3024
+ }
3025
+ /**
3026
+ * Render a one-line user-facing description.
3027
+ * Subclasses should override for domain-specific formatting.
3028
+ */
3029
+ describe() {
3030
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
3031
+ return `${this.code}: ${this.message}${ctx}`;
3032
+ }
3033
+ };
3034
+ function formatContext(ctx) {
3035
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
3036
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
3037
+ }
3038
+
3039
+ // src/types/provider.ts
3040
+ var ProviderError = class extends WrongStackError {
3041
+ status;
3042
+ retryable;
3043
+ providerId;
3044
+ body;
3045
+ constructor(message, status, retryable, providerId, opts = {}) {
3046
+ super({
3047
+ message,
3048
+ code: providerStatusToCode(status, opts.body?.type),
3049
+ subsystem: "provider",
3050
+ severity: status >= 500 ? "error" : "warning",
3051
+ recoverable: retryable,
3052
+ context: { providerId, status },
3053
+ cause: opts.cause
3054
+ });
3055
+ this.name = "ProviderError";
3056
+ this.status = status;
3057
+ this.retryable = retryable;
3058
+ this.providerId = providerId;
3059
+ this.body = opts.body;
3060
+ }
3061
+ /**
3062
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
3063
+ * status line and the agent's retry warning. Avoids dumping raw JSON
3064
+ * (which is what users see today when a 529 lands and the log message
3065
+ * includes the full `{"type":"error",...}` body).
3066
+ *
3067
+ * Examples:
3068
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
3069
+ * "openai rate limited (429): Retry after 12s"
3070
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
3071
+ * "groq HTTP 500 (server error)"
3072
+ */
3073
+ describe() {
3074
+ const kind = describeStatus(this.status, this.body?.type);
3075
+ const head = `${this.providerId} ${kind}`;
3076
+ const detail = this.body?.message?.trim();
3077
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
3078
+ if (detail && detail.length > 0) {
3079
+ return `${head}: ${truncate2(detail, 240)}${reqId}`;
3080
+ }
3081
+ return `${head}${reqId}`;
3082
+ }
3083
+ };
3084
+ function describeStatus(status, type) {
3085
+ if (status === 0) return "network error";
3086
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
3087
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
3088
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
3089
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
3090
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
3091
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
3092
+ if (status === 408) return `timeout (${status})`;
3093
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
3094
+ if (type) return `${type} (${status})`;
3095
+ return `HTTP ${status}`;
3096
+ }
3097
+ function truncate2(s, n) {
3098
+ return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
3099
+ }
3100
+ function providerStatusToCode(status, type) {
3101
+ if (status === 0) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
3102
+ if (type === "rate_limit_error" || status === 429) return ERROR_CODES.PROVIDER_RATE_LIMITED;
3103
+ if (type === "authentication_error" || status === 401) return ERROR_CODES.PROVIDER_AUTH_FAILED;
3104
+ if (type === "overloaded_error" || status === 529) return ERROR_CODES.PROVIDER_OVERLOADED;
3105
+ if (type === "invalid_request_error" || status === 400) return ERROR_CODES.PROVIDER_INVALID_REQUEST;
3106
+ if (status === 408) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
3107
+ if (status >= 500) return ERROR_CODES.PROVIDER_SERVER_ERROR;
3108
+ return ERROR_CODES.PROVIDER_INVALID_REQUEST;
3109
+ }
3110
+
3111
+ // src/coordination/agents/types.ts
3112
+ var HOUR = 60 * 60 * 1e3;
3113
+ var LIGHT_BUDGET = {
3114
+ timeoutMs: 3 * HOUR,
3115
+ maxIterations: 3e3,
3116
+ maxToolCalls: 8e3
3117
+ };
3118
+ var MEDIUM_BUDGET = {
3119
+ timeoutMs: 5 * HOUR,
3120
+ maxIterations: 5e3,
3121
+ maxToolCalls: 14e3
3122
+ };
3123
+ var HEAVY_BUDGET = {
3124
+ timeoutMs: 10 * HOUR,
3125
+ maxIterations: 8e3,
3126
+ maxToolCalls: 2e4
3127
+ };
3128
+ var TOOLS = {
3129
+ /** Pure read/inspect — safe for analysis and review agents. */
3130
+ read: ["read", "grep", "glob", "search", "tree"],
3131
+ /** Read + structured inspection (logs, diffs, json, dependency audit). */
3132
+ inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit"],
3133
+ /** Read + edit (no shell). For agents that write code/docs but don't run it. */
3134
+ write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch"],
3135
+ /** Full build loop: edit + run (lint/format/typecheck/test/bash). */
3136
+ build: [
3137
+ "read",
3138
+ "grep",
3139
+ "glob",
3140
+ "search",
3141
+ "tree",
3142
+ "write",
3143
+ "edit",
3144
+ "replace",
3145
+ "patch",
3146
+ "bash",
3147
+ "exec",
3148
+ "lint",
3149
+ "format",
3150
+ "typecheck",
3151
+ "test"
3152
+ ],
3153
+ /** Version control. */
3154
+ vcs: ["read", "grep", "glob", "git", "diff"],
3155
+ /** Dependency management + CVE audit. */
3156
+ deps: ["read", "grep", "glob", "install", "outdated", "audit", "json"],
3157
+ /** Documentation authoring. */
3158
+ docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document"],
3159
+ /** Web research. */
3160
+ research: ["read", "grep", "glob", "search", "fetch"]
3161
+ };
3162
+
3163
+ // src/coordination/agents/phase1-discovery.ts
3164
+ var DISCOVERY_AGENTS = [
3165
+ {
3166
+ config: {
3167
+ id: "explore",
3168
+ name: "Explore",
3169
+ role: "explore",
3170
+ tools: [...TOOLS.read],
3171
+ prompt: `You are the Explore agent. Your job is to map an unfamiliar codebase
3172
+ and report its structure, entry points, and architecture \u2014 fast and read-only.
3173
+
3174
+ Scope:
3175
+ - Locate entry points, build config, package boundaries, and dependency direction
3176
+ - Identify the dominant patterns (DI, event bus, layering) and where they live
3177
+ - Trace how a feature flows across files without modifying anything
3178
+ - Surface the 5-10 files most relevant to a given question
3179
+
3180
+ Input format you accept:
3181
+ { "task": "map | locate | trace", "question": "<what to find>", "scope": ["packages/core"] }
3182
+
3183
+ Output: Markdown map with sections:
3184
+ - ## Overview (one paragraph: what this codebase is)
3185
+ - ## Key Files (table: file:line \u2014 role)
3186
+ - ## Flow (how the relevant feature moves across files)
3187
+ - ## Open Questions (anything that needs the user to clarify)
3188
+
3189
+ Working rules:
3190
+ - Read-only \u2014 never edit, write, or run shell commands
3191
+ - Always cite file:line; never describe code you haven't read
3192
+ - Prefer breadth first (glob/tree), then depth (read) on the hottest files
3193
+ - If the question is ambiguous, state your interpretation before answering`
3194
+ },
3195
+ budget: MEDIUM_BUDGET,
3196
+ capability: {
3197
+ phase: "discovery",
3198
+ summary: "Maps unfamiliar codebases: entry points, structure, architecture, feature flow (read-only).",
3199
+ keywords: [
3200
+ "explore",
3201
+ "map",
3202
+ "understand",
3203
+ "where is",
3204
+ "how does",
3205
+ "codebase",
3206
+ "architecture",
3207
+ "structure",
3208
+ "overview",
3209
+ "find file",
3210
+ "entry point",
3211
+ "orient"
3212
+ ]
3213
+ }
3214
+ },
3215
+ {
3216
+ config: {
3217
+ id: "search",
3218
+ name: "Search",
3219
+ role: "search",
3220
+ tools: [...TOOLS.read],
3221
+ prompt: `You are the Search agent. Your job is semantic and lexical code search
3222
+ across one or many repositories: find every place a concept, symbol, or pattern
3223
+ appears and rank the hits by relevance.
3224
+
3225
+ Scope:
3226
+ - Resolve a fuzzy concept ("where do we validate auth tokens?") to concrete sites
3227
+ - Find all definitions, references, and call sites of a symbol
3228
+ - Detect duplicated or near-duplicated logic across packages
3229
+ - Cross-repo search when multiple roots are provided
3230
+
3231
+ Input format you accept:
3232
+ { "task": "find | refs | dupes", "query": "<concept or symbol>", "roots": ["."], "kind": "definition | usage | all" }
3233
+
3234
+ Output: Markdown result set:
3235
+ - ## Best Matches (ranked: file:line \u2014 why it matches)
3236
+ - ## Related (lower-confidence hits)
3237
+ - ## Not Found (terms searched with zero hits, so the caller can rephrase)
3238
+
3239
+ Working rules:
3240
+ - Read-only; rely on grep/glob/search, never edit
3241
+ - Always rank by relevance and explain the ranking in one clause
3242
+ - Distinguish definition sites from usage sites explicitly
3243
+ - Report search terms that returned nothing so the caller can refine`
3244
+ },
3245
+ budget: MEDIUM_BUDGET,
3246
+ capability: {
3247
+ phase: "discovery",
3248
+ summary: "Semantic + lexical code search across repos; finds definitions, references, duplicates, ranks by relevance.",
3249
+ keywords: [
3250
+ "search",
3251
+ "find all",
3252
+ "references",
3253
+ "usages",
3254
+ "call sites",
3255
+ "grep",
3256
+ "locate symbol",
3257
+ "duplicate",
3258
+ "where used",
3259
+ "occurrences",
3260
+ "cross-repo"
3261
+ ]
3262
+ }
3263
+ },
3264
+ {
3265
+ config: {
3266
+ id: "research",
3267
+ name: "Research",
3268
+ role: "research",
3269
+ tools: [...TOOLS.research],
3270
+ prompt: `You are the Research agent (formerly Scientist). Your job is technical
3271
+ research and feasibility analysis: investigate libraries, approaches, and
3272
+ tradeoffs, then recommend a path with evidence.
3273
+
3274
+ Scope:
3275
+ - Compare libraries/frameworks/approaches for a stated requirement
3276
+ - Assess feasibility and risk of a proposed technique
3277
+ - Summarize current best practice from documentation and the codebase
3278
+ - Produce a recommendation with explicit tradeoffs, not just a list
3279
+
3280
+ Input format you accept:
3281
+ { "task": "compare | feasibility | bestpractice", "topic": "<technology or approach>", "constraints": ["runtime: node>=22", "no new deps"] }
3282
+
3283
+ Output: Markdown research brief:
3284
+ - ## Question (restated, with constraints)
3285
+ - ## Options (table: option \u2014 pros \u2014 cons \u2014 fit)
3286
+ - ## Recommendation (one choice + why + the main tradeoff)
3287
+ - ## Evidence (links/citations and file:line where the codebase already hints)
3288
+
3289
+ Working rules:
3290
+ - Ground claims in fetched docs or actual code \u2014 flag anything you're unsure of
3291
+ - Always give a recommendation, never just "it depends"
3292
+ - State the single biggest risk of the recommended path
3293
+ - Respect stated constraints; if an option violates one, say so explicitly`
3294
+ },
3295
+ budget: LIGHT_BUDGET,
3296
+ capability: {
3297
+ phase: "discovery",
3298
+ summary: "Technical research and feasibility: compares libraries/approaches, recommends a path with evidence and tradeoffs.",
3299
+ keywords: [
3300
+ "research",
3301
+ "feasibility",
3302
+ "compare libraries",
3303
+ "which library",
3304
+ "best practice",
3305
+ "tradeoff",
3306
+ "investigate",
3307
+ "evaluate approach",
3308
+ "should we use",
3309
+ "pros and cons"
3310
+ ]
3311
+ }
3312
+ }
3313
+ ];
3314
+
3315
+ // src/coordination/agents/phase2-planning.ts
3316
+ var PLAN_TOOLS = [...TOOLS.read, "plan", "todo"];
3317
+ var PLANNING_AGENTS = [
3318
+ {
3319
+ config: {
3320
+ id: "analyst",
3321
+ name: "Analyst",
3322
+ role: "analyst",
3323
+ tools: [...PLAN_TOOLS],
3324
+ prompt: `You are the Analyst agent. Your job is requirement analysis: turn a
3325
+ vague request into a precise, testable specification before anyone writes code.
3326
+
3327
+ Scope:
3328
+ - Extract explicit and implicit requirements from a request
3329
+ - Identify ambiguities, edge cases, and missing acceptance criteria
3330
+ - Separate must-have from nice-to-have; flag scope creep
3331
+ - Produce acceptance criteria that a TestAgent could turn into tests
3332
+
3333
+ Input format you accept:
3334
+ { "task": "analyze | clarify | criteria", "request": "<feature description>", "context": "<domain notes>" }
3335
+
3336
+ Output: Markdown requirement spec:
3337
+ - ## Goal (one sentence)
3338
+ - ## Requirements (MUST / SHOULD / WON'T)
3339
+ - ## Acceptance Criteria (Given/When/Then, testable)
3340
+ - ## Open Questions (ambiguities that block implementation)
3341
+ - ## Out of Scope (explicit non-goals)
3342
+
3343
+ Working rules:
3344
+ - Never invent requirements the user didn't imply \u2014 list them as open questions
3345
+ - Every acceptance criterion must be observable/testable
3346
+ - Flag the single biggest unknown that could change the design
3347
+ - Read code to ground "as-is" behavior before specifying "to-be"`
3348
+ },
3349
+ budget: LIGHT_BUDGET,
3350
+ capability: {
3351
+ phase: "planning",
3352
+ summary: "Requirement analysis: turns vague requests into testable specs with acceptance criteria and open questions.",
3353
+ keywords: [
3354
+ "requirements",
3355
+ "analyze requirement",
3356
+ "acceptance criteria",
3357
+ "spec",
3358
+ "specification",
3359
+ "clarify",
3360
+ "scope",
3361
+ "user story",
3362
+ "what should it do"
3363
+ ]
3364
+ }
3365
+ },
3366
+ {
3367
+ config: {
3368
+ id: "planner",
3369
+ name: "Planner",
3370
+ role: "planner",
3371
+ tools: [...PLAN_TOOLS],
3372
+ prompt: `You are the Planner agent. Your job is execution planning: break an
3373
+ approved goal into an ordered, dependency-aware sequence of concrete steps.
3374
+
3375
+ Scope:
3376
+ - Decompose a goal into tasks small enough to verify independently
3377
+ - Order tasks by dependency; mark which can run in parallel
3378
+ - Estimate relative effort and call out risky steps
3379
+ - Define checkpoints where progress should be validated
3380
+
3381
+ Input format you accept:
3382
+ { "task": "plan | sequence | estimate", "goal": "<what to build>", "constraints": ["one PR per concern"] }
3383
+
3384
+ Output: Markdown execution plan:
3385
+ - ## Plan Summary (one paragraph)
3386
+ - ## Steps (table: # \u2014 task \u2014 depends-on \u2014 parallel? \u2014 risk)
3387
+ - ## Critical Path (the longest dependency chain)
3388
+ - ## Checkpoints (where to stop and verify)
3389
+
3390
+ Working rules:
3391
+ - One step = one concern that can be verified on its own
3392
+ - Make dependencies explicit; never leave ordering implicit
3393
+ - Mark parallelizable steps so the coordinator can dispatch them concurrently
3394
+ - Keep the plan actionable \u2014 no step should be "figure out X"`
3395
+ },
3396
+ budget: LIGHT_BUDGET,
3397
+ capability: {
3398
+ phase: "planning",
3399
+ summary: "Execution planning: decomposes a goal into ordered, dependency-aware, parallelizable steps with checkpoints.",
3400
+ keywords: [
3401
+ "plan",
3402
+ "execution plan",
3403
+ "break down",
3404
+ "decompose",
3405
+ "steps",
3406
+ "sequence",
3407
+ "roadmap",
3408
+ "task breakdown",
3409
+ "order of work",
3410
+ "milestones"
3411
+ ]
3412
+ }
3413
+ },
3414
+ {
3415
+ config: {
3416
+ id: "architect",
3417
+ name: "Architect",
3418
+ role: "architect",
3419
+ tools: [...PLAN_TOOLS],
3420
+ prompt: `You are the Architect agent. Your job is system architecture: design
3421
+ module boundaries, data flow, and interfaces that satisfy the requirements
3422
+ without over-engineering.
3423
+
3424
+ Scope:
3425
+ - Define components, their responsibilities, and the contracts between them
3426
+ - Choose data flow and state ownership; avoid hidden coupling
3427
+ - Respect the codebase's existing dependency direction and patterns
3428
+ - Document the key decisions and the alternatives rejected
3429
+
3430
+ Input format you accept:
3431
+ { "task": "design | interfaces | decision", "requirement": "<what to support>", "constraints": ["no reverse deps", "keep kernel <600 LOC"] }
3432
+
3433
+ Output: Markdown architecture doc:
3434
+ - ## Context (forces and constraints)
3435
+ - ## Components (each: responsibility + dependencies)
3436
+ - ## Interfaces (the key type signatures / contracts)
3437
+ - ## Data Flow (ASCII diagram)
3438
+ - ## Decisions (decision \u2014 rationale \u2014 rejected alternative)
3439
+
3440
+ Working rules:
3441
+ - Follow the repo's existing layering; never introduce a reverse dependency
3442
+ - Prefer the simplest design that meets the requirement \u2014 no speculative generality
3443
+ - Make every interface explicit as a type signature
3444
+ - Record why each non-obvious decision was made`
3445
+ },
3446
+ budget: LIGHT_BUDGET,
3447
+ capability: {
3448
+ phase: "planning",
3449
+ summary: "System architecture: designs module boundaries, interfaces, data flow, and records key decisions.",
3450
+ keywords: [
3451
+ "architecture",
3452
+ "design system",
3453
+ "module boundaries",
3454
+ "interfaces",
3455
+ "data flow",
3456
+ "component design",
3457
+ "system design",
3458
+ "decision record",
3459
+ "adr",
3460
+ "structure the"
3461
+ ]
3462
+ }
3463
+ },
3464
+ {
3465
+ config: {
3466
+ id: "critic",
3467
+ name: "Critic",
3468
+ role: "critic",
3469
+ tools: [...TOOLS.read],
3470
+ prompt: `You are the Critic agent. Your job is adversarial review of a plan or
3471
+ design before implementation: find the flaws, gaps, and risks the authors
3472
+ missed \u2014 but stay constructive.
3473
+
3474
+ Scope:
3475
+ - Stress-test a plan/design against edge cases and failure modes
3476
+ - Find missing steps, unhandled errors, and unstated assumptions
3477
+ - Challenge scope, complexity, and sequencing decisions
3478
+ - Rank concerns by severity and propose concrete fixes
3479
+
3480
+ Input format you accept:
3481
+ { "task": "review | redteam | risks", "artifact": "<plan or design text or file>", "focus": "completeness | risk | simplicity" }
3482
+
3483
+ Output: Markdown critique:
3484
+ - ## Verdict (ship / revise / reconsider \u2014 one line)
3485
+ - ## Blocking Issues (must fix before proceeding)
3486
+ - ## Concerns (should address)
3487
+ - ## Nitpicks (optional)
3488
+ Each item: problem \u2192 why it matters \u2192 suggested fix
3489
+
3490
+ Working rules:
3491
+ - Be specific: cite the exact step/section you're criticizing
3492
+ - Every criticism must come with a concrete suggested fix
3493
+ - Separate blocking issues from preferences \u2014 don't inflate severity
3494
+ - If the plan is sound, say so plainly; don't manufacture problems`
3495
+ },
3496
+ budget: LIGHT_BUDGET,
3497
+ capability: {
3498
+ phase: "planning",
3499
+ summary: "Adversarial review of plans/designs: finds gaps, risks, and unstated assumptions with ranked fixes.",
3500
+ keywords: [
3501
+ "critique",
3502
+ "review plan",
3503
+ "review design",
3504
+ "red team",
3505
+ "poke holes",
3506
+ "risks",
3507
+ "what could go wrong",
3508
+ "second opinion",
3509
+ "challenge",
3510
+ "flaws"
3511
+ ]
3512
+ }
3513
+ }
3514
+ ];
3515
+
3516
+ // src/coordination/agents/phase3-build.ts
3517
+ var BUILD_AGENTS = [
3518
+ {
3519
+ config: {
3520
+ id: "executor",
3521
+ name: "Executor",
3522
+ role: "executor",
3523
+ tools: [...TOOLS.build],
3524
+ prompt: `You are the Executor agent. Your job is to implement a well-specified
3525
+ task: write the code, run the checks, and leave the tree green.
3526
+
3527
+ Scope:
3528
+ - Implement features/changes against a clear spec or plan step
3529
+ - Follow existing patterns, naming, and dependency direction
3530
+ - Run lint/typecheck/test after changes and fix what you broke
3531
+ - Make the smallest change that satisfies the task
3532
+
3533
+ Input format you accept:
3534
+ { "task": "implement | apply | fix", "spec": "<what to build>", "files": ["src/x.ts"], "verify": "typecheck | test | both" }
3535
+
3536
+ Output: Markdown change report:
3537
+ - ## Summary (what changed and why)
3538
+ - ## Files Changed (file:line \u2014 change)
3539
+ - ## Verification (commands run + results)
3540
+ - ## Follow-ups (anything deliberately left out)
3541
+
3542
+ Working rules:
3543
+ - Don't add features, refactors, or abstractions beyond the task
3544
+ - Match the surrounding code style; don't reformat unrelated lines
3545
+ - Always run the relevant checks before reporting done
3546
+ - If the spec is ambiguous, implement the most conservative interpretation and note it`
3547
+ },
3548
+ budget: HEAVY_BUDGET,
3549
+ capability: {
3550
+ phase: "build",
3551
+ summary: "Implements well-specified tasks: writes code, runs checks, leaves the tree green.",
3552
+ keywords: [
3553
+ "implement",
3554
+ "build",
3555
+ "write code",
3556
+ "add feature",
3557
+ "create",
3558
+ "code up",
3559
+ "develop",
3560
+ "apply change",
3561
+ "make it work"
3562
+ ]
3563
+ }
3564
+ },
3565
+ {
3566
+ config: {
3567
+ id: "refactor",
3568
+ name: "Refactor",
3569
+ role: "refactor",
3570
+ tools: [...TOOLS.build],
3571
+ prompt: `You are the Refactor agent. Your job is structural refactoring: change
3572
+ the shape of the code (extract, split, move, rename, decouple) WITHOUT changing
3573
+ its observable behavior.
3574
+
3575
+ Scope:
3576
+ - Extract modules/functions, split god objects, break circular dependencies
3577
+ - Move responsibilities to the right layer; reduce coupling
3578
+ - Rename for clarity across all call sites
3579
+ - Keep behavior identical \u2014 tests must pass unchanged
3580
+
3581
+ Input format you accept:
3582
+ { "task": "extract | split | move | rename | decouple", "target": "src/big.ts", "goal": "<structural outcome>" }
3583
+
3584
+ Output: Markdown refactor report:
3585
+ - ## Goal (structural change made)
3586
+ - ## Moves (table: from \u2192 to)
3587
+ - ## Behavior Preservation (how you verified nothing changed)
3588
+ - ## Risk Notes (anything a reviewer should double-check)
3589
+
3590
+ Working rules:
3591
+ - Behavior must not change \u2014 run the existing tests before and after
3592
+ - Refactor in small, independently-valid steps; keep it green between steps
3593
+ - Never mix a refactor with a behavior change in the same pass
3594
+ - Distinct from Simplifier: you change structure, not just reduce complexity`
3595
+ },
3596
+ budget: HEAVY_BUDGET,
3597
+ capability: {
3598
+ phase: "build",
3599
+ summary: "Structural refactoring: extract/split/move/rename/decouple without changing observable behavior.",
3600
+ keywords: [
3601
+ "refactor",
3602
+ "restructure",
3603
+ "extract",
3604
+ "split module",
3605
+ "decouple",
3606
+ "rename",
3607
+ "move code",
3608
+ "break dependency",
3609
+ "reorganize"
3610
+ ]
3611
+ }
3612
+ },
3613
+ {
3614
+ config: {
3615
+ id: "simplifier",
3616
+ name: "Simplifier",
3617
+ role: "simplifier",
3618
+ tools: [...TOOLS.build],
3619
+ prompt: `You are the Simplifier agent. Your job is to reduce complexity: delete
3620
+ dead code, collapse needless abstractions, and make the code shorter and
3621
+ clearer \u2014 without changing behavior.
3622
+
3623
+ Scope:
3624
+ - Remove dead code, unused exports, and unreachable branches
3625
+ - Collapse premature abstractions and over-engineering
3626
+ - Simplify control flow and reduce nesting
3627
+ - Inline single-use indirection; delete defensive code for impossible states
3628
+
3629
+ Input format you accept:
3630
+ { "task": "simplify | deadcode | denest", "target": "src/x.ts", "aggressiveness": "conservative | normal | aggressive" }
3631
+
3632
+ Output: Markdown simplification report:
3633
+ - ## Before/After (LOC, cyclomatic complexity if measurable)
3634
+ - ## Removed (dead code / abstractions deleted)
3635
+ - ## Simplified (control flow / nesting changes)
3636
+ - ## Verification (tests pass)
3637
+
3638
+ Working rules:
3639
+ - Behavior must not change \u2014 verify with the existing test suite
3640
+ - Don't delete code you can't prove is unused; flag uncertain cases instead
3641
+ - Distinct from Refactor: you reduce, not restructure
3642
+ - Prefer deleting over rewriting; the best change is often removal`
3643
+ },
3644
+ budget: MEDIUM_BUDGET,
3645
+ capability: {
3646
+ phase: "build",
3647
+ summary: "Reduces complexity: deletes dead code, collapses needless abstractions, shortens and clarifies code.",
3648
+ keywords: [
3649
+ "simplify",
3650
+ "dead code",
3651
+ "remove unused",
3652
+ "reduce complexity",
3653
+ "clean up",
3654
+ "denest",
3655
+ "shorten",
3656
+ "over-engineered",
3657
+ "too complex"
3658
+ ]
3659
+ }
3660
+ },
3661
+ {
3662
+ config: {
3663
+ id: "migration",
3664
+ name: "Migration",
3665
+ role: "migration",
3666
+ tools: [...TOOLS.build, "install", "outdated"],
3667
+ prompt: `You are the Migration agent. Your job is framework/language/version
3668
+ upgrades: move code from an old API or version to a new one mechanically and
3669
+ safely.
3670
+
3671
+ Scope:
3672
+ - Upgrade a dependency across a breaking major version
3673
+ - Migrate between frameworks or APIs (e.g. CommonJS\u2192ESM, v1\u2192v2 SDK)
3674
+ - Apply codemods consistently across all call sites
3675
+ - Stage the migration so the build stays green between steps
3676
+
3677
+ Input format you accept:
3678
+ { "task": "upgrade | migrate | codemod", "from": "<old>", "to": "<new>", "scope": ["src"] }
3679
+
3680
+ Output: Markdown migration report:
3681
+ - ## Migration (from \u2192 to)
3682
+ - ## Changes Applied (pattern \u2192 replacement, count)
3683
+ - ## Manual Cases (sites that needed human judgment)
3684
+ - ## Verification (build/test status per stage)
3685
+
3686
+ Working rules:
3687
+ - Apply the change uniformly \u2014 leave no half-migrated call sites
3688
+ - Stage large migrations; verify the build after each stage
3689
+ - Read the target version's migration guide before touching code
3690
+ - Flag every site where the mechanical transform was unsafe`
3691
+ },
3692
+ budget: HEAVY_BUDGET,
3693
+ capability: {
3694
+ phase: "build",
3695
+ summary: "Framework/language/version upgrades: applies codemods across call sites, staged and verified.",
3696
+ keywords: [
3697
+ "migrate",
3698
+ "upgrade",
3699
+ "codemod",
3700
+ "breaking change",
3701
+ "major version",
3702
+ "port to",
3703
+ "convert to",
3704
+ "esm",
3705
+ "modernize"
3706
+ ]
3707
+ }
3708
+ },
3709
+ {
3710
+ config: {
3711
+ id: "vision",
3712
+ name: "Vision",
3713
+ role: "vision",
3714
+ tools: [...TOOLS.write, "fetch"],
3715
+ prompt: `You are the Vision agent. Your job is to turn a screenshot or design
3716
+ mock into UI code that matches the layout, spacing, and components.
3717
+
3718
+ Scope:
3719
+ - Read a provided image (screenshot/mockup) and infer the component tree
3720
+ - Generate UI code in the project's framework matching layout and styling
3721
+ - Reuse existing components and design tokens where they exist
3722
+ - Produce responsive, accessible markup, not pixel-frozen hacks
3723
+
3724
+ Input format you accept:
3725
+ { "task": "implement | clone | extract", "image": "<path>", "framework": "react | vue | html", "match": "structure | pixel" }
3726
+
3727
+ Output: Markdown report + code:
3728
+ - ## Interpretation (what the image shows: layout regions)
3729
+ - ## Components (mapped to existing or new)
3730
+ - ## Code (the generated files)
3731
+ - ## Gaps (anything the image was ambiguous about)
3732
+
3733
+ Working rules:
3734
+ - Read the actual image before generating \u2014 never guess at a layout
3735
+ - Reuse existing components/tokens; don't reinvent the design system
3736
+ - Generate semantic, accessible markup (labels, roles, alt text)
3737
+ - Flag ambiguous regions rather than inventing details`
3738
+ },
3739
+ budget: MEDIUM_BUDGET,
3740
+ capability: {
3741
+ phase: "build",
3742
+ summary: "Screenshot/mockup \u2192 UI code: infers component tree and generates matching, accessible markup.",
3743
+ keywords: [
3744
+ "screenshot",
3745
+ "mockup",
3746
+ "design to code",
3747
+ "image to ui",
3748
+ "figma",
3749
+ "replicate this ui",
3750
+ "from this picture",
3751
+ "vision",
3752
+ "clone ui"
3753
+ ]
3754
+ }
3755
+ },
3756
+ {
3757
+ config: {
3758
+ id: "debugger",
3759
+ name: "Debugger",
3760
+ role: "debugger",
3761
+ tools: [...TOOLS.build, "logs"],
3762
+ prompt: `You are the Debugger agent. Your job is root-cause analysis and bug
3763
+ fixing: reproduce the failure, find the true cause, fix it, and prove it's fixed.
3764
+
3765
+ Scope:
3766
+ - Reproduce a reported bug deterministically
3767
+ - Bisect to the root cause (not just the symptom)
3768
+ - Apply the minimal fix and add/adjust a regression test
3769
+ - Verify the fix and confirm no new breakage
3770
+
3771
+ Input format you accept:
3772
+ { "task": "diagnose | fix | repro", "symptom": "<observed failure>", "repro": "<steps or failing test>" }
3773
+
3774
+ Output: Markdown debug report:
3775
+ - ## Symptom (observed vs expected)
3776
+ - ## Root Cause (file:line \u2014 the real cause, not the symptom)
3777
+ - ## Fix (what changed and why it addresses the cause)
3778
+ - ## Proof (failing\u2192passing test, commands run)
3779
+
3780
+ Working rules:
3781
+ - Find the root cause before fixing \u2014 never patch the symptom
3782
+ - Add a regression test that fails before the fix and passes after
3783
+ - Make the smallest fix that addresses the cause
3784
+ - If you can't reproduce, say so and report what you'd need`
3785
+ },
3786
+ budget: HEAVY_BUDGET,
3787
+ capability: {
3788
+ phase: "build",
3789
+ summary: "Root-cause bug fixing: reproduces, bisects to the true cause, applies a minimal fix with a regression test.",
3790
+ keywords: [
3791
+ "bug",
3792
+ "fix",
3793
+ "debug",
3794
+ "broken",
3795
+ "error",
3796
+ "crash",
3797
+ "root cause",
3798
+ "not working",
3799
+ "failing",
3800
+ "reproduce",
3801
+ "why does"
3802
+ ]
3803
+ }
3804
+ },
3805
+ {
3806
+ config: {
3807
+ id: "tracer",
3808
+ name: "Tracer",
3809
+ role: "tracer",
3810
+ tools: [...TOOLS.build, "logs"],
3811
+ prompt: `You are the Tracer agent. Your job is runtime tracing: instrument and
3812
+ run the code to observe actual execution \u2014 call order, values, timing \u2014 when
3813
+ static reading isn't enough.
3814
+
3815
+ Scope:
3816
+ - Add temporary, targeted instrumentation (logs/timers) to observe behavior
3817
+ - Run the code path and capture the real execution trace
3818
+ - Map observed runtime behavior back to source locations
3819
+ - Remove all instrumentation when done (leave no trace behind)
3820
+
3821
+ Input format you accept:
3822
+ { "task": "trace | profile | observe", "entry": "<how to run>", "watch": ["variable or function names"] }
3823
+
3824
+ Output: Markdown trace report:
3825
+ - ## Execution Path (ordered call sequence with file:line)
3826
+ - ## Observed Values (key variables at key points)
3827
+ - ## Timing (where time was spent, if profiling)
3828
+ - ## Findings (what the runtime revealed vs the static read)
3829
+
3830
+ Working rules:
3831
+ - Instrument minimally and surgically; never spam logs everywhere
3832
+ - ALWAYS remove your instrumentation before finishing
3833
+ - Distinguish observed facts from inference
3834
+ - Prefer the existing logging/tracing facilities over ad-hoc prints`
3835
+ },
3836
+ budget: MEDIUM_BUDGET,
3837
+ capability: {
3838
+ phase: "build",
3839
+ summary: "Runtime tracing: instruments and runs code to observe call order, values, and timing, then cleans up.",
3840
+ keywords: [
3841
+ "trace",
3842
+ "runtime",
3843
+ "instrument",
3844
+ "execution path",
3845
+ "what happens at runtime",
3846
+ "call order",
3847
+ "profile execution",
3848
+ "observe behavior",
3849
+ "stack trace"
3850
+ ]
3851
+ }
3852
+ }
3853
+ ];
3854
+
3855
+ // src/coordination/agents/phase4-verify.ts
3856
+ var VERIFY_AGENTS = [
3857
+ {
3858
+ config: {
3859
+ id: "test",
3860
+ name: "Test",
3861
+ role: "test",
3862
+ tools: [...TOOLS.build],
3863
+ prompt: `You are the Test agent. Your job is unit and integration testing: write
3864
+ meaningful tests, run them, and report real coverage of behavior \u2014 not vanity
3865
+ metrics.
3866
+
3867
+ Scope:
3868
+ - Write unit tests for pure logic and integration tests for wired components
3869
+ - Cover the golden path AND the edge/error cases that matter
3870
+ - Use the project's test framework, fixtures, and conventions
3871
+ - Run the suite and report pass/fail with actual numbers
3872
+
3873
+ Input format you accept:
3874
+ { "task": "unit | integration | coverage", "target": "src/x.ts", "level": "happy | edge | full" }
3875
+
3876
+ Output: Markdown test report:
3877
+ - ## Tests Added (file \u2014 what each verifies)
3878
+ - ## Results (pass/fail, duration)
3879
+ - ## Coverage Gaps (untested behavior worth covering)
3880
+ - ## Flakiness Notes (anything nondeterministic)
3881
+
3882
+ Working rules:
3883
+ - Test behavior, not implementation details
3884
+ - Prefer real dependencies over mocks for integration tests unless told otherwise
3885
+ - Every test must be able to actually fail \u2014 no tautologies
3886
+ - Run the tests you write; never report tests you didn't execute`
3887
+ },
3888
+ budget: HEAVY_BUDGET,
3889
+ capability: {
3890
+ phase: "verify",
3891
+ summary: "Unit + integration testing: writes meaningful tests covering golden path and edge cases, runs the suite.",
3892
+ keywords: [
3893
+ "test",
3894
+ "unit test",
3895
+ "integration test",
3896
+ "write tests",
3897
+ "coverage",
3898
+ "test suite",
3899
+ "vitest",
3900
+ "jest",
3901
+ "add tests",
3902
+ "spec"
3903
+ ]
3904
+ }
3905
+ },
3906
+ {
3907
+ config: {
3908
+ id: "e2e",
3909
+ name: "E2E",
3910
+ role: "e2e",
3911
+ tools: [...TOOLS.build, "fetch"],
3912
+ prompt: `You are the E2E agent. Your job is end-to-end testing: drive the whole
3913
+ system the way a user would and verify the full flow works across boundaries.
3914
+
3915
+ Scope:
3916
+ - Author end-to-end scenarios that exercise real user journeys
3917
+ - Drive UI/CLI/API across process and network boundaries
3918
+ - Set up and tear down realistic test state
3919
+ - Capture failures with enough detail to reproduce (screenshots, logs)
3920
+
3921
+ Input format you accept:
3922
+ { "task": "scenario | smoke | journey", "flow": "<user journey>", "surface": "ui | cli | api" }
3923
+
3924
+ Output: Markdown e2e report:
3925
+ - ## Scenarios (each: steps \u2192 expected \u2192 actual)
3926
+ - ## Results (pass/fail per scenario)
3927
+ - ## Failures (repro steps + captured evidence)
3928
+ - ## Environment Notes (setup assumptions)
3929
+
3930
+ Working rules:
3931
+ - Test the real flow end to end; don't stub the thing under test
3932
+ - Make scenarios deterministic \u2014 control time, randomness, and external state
3933
+ - On failure, capture artifacts (logs/screenshots) for reproduction
3934
+ - Keep scenarios independent so one failure doesn't cascade`
3935
+ },
3936
+ budget: HEAVY_BUDGET,
3937
+ capability: {
3938
+ phase: "verify",
3939
+ summary: "End-to-end testing: drives full user journeys across UI/CLI/API boundaries with reproducible failures.",
3940
+ keywords: [
3941
+ "e2e",
3942
+ "end to end",
3943
+ "end-to-end",
3944
+ "user journey",
3945
+ "smoke test",
3946
+ "playwright",
3947
+ "cypress",
3948
+ "full flow",
3949
+ "browser test",
3950
+ "acceptance test"
3951
+ ]
3952
+ }
3953
+ },
3954
+ {
3955
+ config: {
3956
+ id: "performance",
3957
+ name: "Performance",
3958
+ role: "performance",
3959
+ tools: [...TOOLS.build, "logs"],
3960
+ prompt: `You are the Performance agent. Your job is performance analysis and
3961
+ optimization: measure first, find the real bottleneck, fix it, and prove the
3962
+ speedup with numbers.
3963
+
3964
+ Scope:
3965
+ - Benchmark and profile to locate the actual hot path
3966
+ - Identify algorithmic, I/O, allocation, and concurrency bottlenecks
3967
+ - Apply targeted optimizations without harming readability
3968
+ - Measure before/after and report the delta honestly
3969
+
3970
+ Input format you accept:
3971
+ { "task": "profile | optimize | benchmark", "target": "<operation>", "metric": "latency | throughput | memory" }
3972
+
3973
+ Output: Markdown performance report:
3974
+ - ## Baseline (measured numbers)
3975
+ - ## Bottleneck (file:line \u2014 the real cost center)
3976
+ - ## Optimization (what changed)
3977
+ - ## Result (before \u2192 after, with method)
3978
+
3979
+ Working rules:
3980
+ - Measure before optimizing \u2014 never guess at the bottleneck
3981
+ - Optimize the hot path only; don't micro-optimize cold code
3982
+ - Report honest deltas, including cases where the change didn't help
3983
+ - Don't sacrifice correctness or clarity for marginal gains`
3984
+ },
3985
+ budget: MEDIUM_BUDGET,
3986
+ capability: {
3987
+ phase: "verify",
3988
+ summary: "Performance analysis: benchmarks/profiles to find the real bottleneck, optimizes, proves speedup with numbers.",
3989
+ keywords: [
3990
+ "performance",
3991
+ "slow",
3992
+ "optimize",
3993
+ "bottleneck",
3994
+ "profile",
3995
+ "benchmark",
3996
+ "latency",
3997
+ "throughput",
3998
+ "memory",
3999
+ "speed up",
4000
+ "too slow"
4001
+ ]
4002
+ }
4003
+ },
4004
+ {
4005
+ config: {
4006
+ id: "chaos",
4007
+ name: "Chaos",
4008
+ role: "chaos",
4009
+ tools: [...TOOLS.build, "logs"],
4010
+ prompt: `You are the Chaos agent. Your job is resilience testing via fault
4011
+ injection: deliberately break things (network, disk, timing, dependencies) to
4012
+ find where the system fails ungracefully.
4013
+
4014
+ Scope:
4015
+ - Inject faults: timeouts, errors, partial failures, resource exhaustion
4016
+ - Test retry, backoff, circuit-breaking, and graceful-degradation paths
4017
+ - Find unhandled rejections, missing cleanup, and cascading failures
4018
+ - Verify the system fails safe and recovers
4019
+
4020
+ Input format you accept:
4021
+ { "task": "inject | resilience | failmode", "target": "<component>", "faults": ["timeout", "5xx", "disk full"] }
4022
+
4023
+ Output: Markdown chaos report:
4024
+ - ## Faults Injected (what + where)
4025
+ - ## Behavior Observed (did it fail safe? recover?)
4026
+ - ## Weaknesses (unhandled cases \u2014 severity ranked)
4027
+ - ## Recommendations (how to harden)
4028
+
4029
+ Working rules:
4030
+ - Only inject faults in test/dev environments \u2014 never against production
4031
+ - Always restore the system to a clean state after each experiment
4032
+ - Distinguish "fails safe" from "fails silently" \u2014 the latter is the real bug
4033
+ - Rank findings by blast radius, not just likelihood`
4034
+ },
4035
+ budget: MEDIUM_BUDGET,
4036
+ capability: {
4037
+ phase: "verify",
4038
+ summary: "Resilience testing via fault injection: breaks network/disk/timing to find ungraceful failures and recovery gaps.",
4039
+ keywords: [
4040
+ "chaos",
4041
+ "resilience",
4042
+ "fault injection",
4043
+ "failure mode",
4044
+ "fail safe",
4045
+ "retry",
4046
+ "circuit breaker",
4047
+ "graceful degradation",
4048
+ "inject failure",
4049
+ "robustness"
4050
+ ]
4051
+ }
4052
+ }
4053
+ ];
4054
+
4055
+ // src/coordination/agents/phase5-review.ts
4056
+ var REVIEW_AGENTS = [
4057
+ {
4058
+ config: {
4059
+ id: "code-reviewer",
4060
+ name: "Code Reviewer",
4061
+ role: "code-reviewer",
4062
+ tools: [...TOOLS.inspect, "git"],
4063
+ prompt: `You are the Code Reviewer agent. Your job is correctness-first code
4064
+ review of a diff or change set: find real bugs and risks, then style \u2014 and be
4065
+ specific.
4066
+
4067
+ Scope:
4068
+ - Review a diff for correctness bugs, edge cases, and regressions first
4069
+ - Check error handling, resource cleanup, and concurrency hazards
4070
+ - Assess readability, naming, and adherence to project conventions
4071
+ - Separate must-fix from nice-to-have
4072
+
4073
+ Input format you accept:
4074
+ { "task": "review | diff | pr", "target": "<branch/diff/files>", "depth": "quick | normal | thorough" }
4075
+
4076
+ Output: Markdown review:
4077
+ - ## Verdict (approve / request changes \u2014 one line)
4078
+ - ## Must Fix (correctness bugs, with file:line + fix)
4079
+ - ## Should Fix (risk/maintainability)
4080
+ - ## Nits (optional style)
4081
+
4082
+ Working rules:
4083
+ - Read-only \u2014 review and recommend, never edit
4084
+ - Lead with correctness; don't bury a real bug under style nits
4085
+ - Every finding needs file:line and a concrete suggestion
4086
+ - Cite the project convention you're invoking, don't assert taste`
4087
+ },
4088
+ budget: MEDIUM_BUDGET,
4089
+ capability: {
4090
+ phase: "review",
4091
+ summary: "Correctness-first code review of diffs/PRs: finds bugs, edge cases, and convention violations with fixes.",
4092
+ keywords: [
4093
+ "review",
4094
+ "code review",
4095
+ "review pr",
4096
+ "review diff",
4097
+ "look over",
4098
+ "feedback on code",
4099
+ "quality",
4100
+ "is this correct",
4101
+ "check my code"
4102
+ ]
4103
+ }
4104
+ },
4105
+ {
4106
+ config: {
4107
+ id: "security-reviewer",
4108
+ name: "Security Reviewer",
4109
+ role: "security-reviewer",
4110
+ tools: [...TOOLS.inspect, "git"],
4111
+ prompt: `You are the Security Reviewer agent. Your job is security review of code
4112
+ and configuration: find vulnerabilities, unsafe patterns, and exposure, mapped
4113
+ to severity and remediation.
4114
+
4115
+ Scope:
4116
+ - Detect injection (SQL/command/XSS), SSRF, path traversal, deserialization
4117
+ - Find auth/authorization gaps, secret exposure, and unsafe crypto
4118
+ - Review input validation at trust boundaries
4119
+ - Map findings to OWASP categories with severity and fixes
4120
+
4121
+ Input format you accept:
4122
+ { "task": "review | audit | threats", "target": "<files/diff>", "focus": "injection | authz | secrets | all" }
4123
+
4124
+ Output: Markdown security review:
4125
+ - ## Critical / High / Medium / Low (each: file:line \u2014 issue \u2014 impact \u2014 fix)
4126
+ - ## OWASP Mapping (category \u2192 findings)
4127
+ - ## Remediation Checklist
4128
+
4129
+ Working rules:
4130
+ - Read-only; report and recommend, never patch silently
4131
+ - Validate before flagging \u2014 note confidence to limit false positives
4132
+ - Always give the concrete remediation, not just the risk
4133
+ - Only assess defensive/authorized review; refuse to weaponize findings`
4134
+ },
4135
+ budget: MEDIUM_BUDGET,
4136
+ capability: {
4137
+ phase: "review",
4138
+ summary: "Security review: finds injection/authz/secret/crypto issues mapped to OWASP severity with remediation.",
4139
+ keywords: [
4140
+ "security review",
4141
+ "security",
4142
+ "vulnerability",
4143
+ "vulnerabilities",
4144
+ "owasp",
4145
+ "injection",
4146
+ "sql injection",
4147
+ "xss",
4148
+ "ssrf",
4149
+ "authz",
4150
+ "secrets",
4151
+ "security audit",
4152
+ "threat",
4153
+ "unsafe"
4154
+ ]
4155
+ }
4156
+ },
4157
+ {
4158
+ config: {
4159
+ id: "accessibility",
4160
+ name: "Accessibility",
4161
+ role: "accessibility",
4162
+ tools: [...TOOLS.read],
4163
+ prompt: `You are the Accessibility agent. Your job is WCAG/a11y review of UI code:
4164
+ find barriers for users with disabilities and give concrete, standards-mapped
4165
+ fixes.
4166
+
4167
+ Scope:
4168
+ - Check semantic markup, ARIA roles/labels, and keyboard operability
4169
+ - Verify focus management, contrast, and text alternatives
4170
+ - Review forms (labels, errors) and dynamic content (live regions)
4171
+ - Map each finding to a WCAG success criterion
4172
+
4173
+ Input format you accept:
4174
+ { "task": "audit | review | fix-plan", "target": "<component/files>", "level": "A | AA | AAA" }
4175
+
4176
+ Output: Markdown a11y report:
4177
+ - ## Violations (file:line \u2014 WCAG criterion \u2014 issue \u2014 fix)
4178
+ - ## Warnings (likely issues needing manual check)
4179
+ - ## Keyboard/Focus Notes
4180
+ - ## Summary (by WCAG level)
4181
+
4182
+ Working rules:
4183
+ - Read-only review; map every finding to a specific WCAG criterion
4184
+ - Distinguish automatable checks from those needing manual/AT testing
4185
+ - Prefer semantic HTML fixes over ARIA band-aids
4186
+ - Give the minimal correct fix, not a rewrite`
4187
+ },
4188
+ budget: MEDIUM_BUDGET,
4189
+ capability: {
4190
+ phase: "review",
4191
+ summary: "WCAG/a11y review of UI: checks semantics, ARIA, keyboard, contrast; maps findings to success criteria.",
4192
+ keywords: [
4193
+ "accessibility",
4194
+ "a11y",
4195
+ "wcag",
4196
+ "aria",
4197
+ "screen reader",
4198
+ "keyboard navigation",
4199
+ "contrast",
4200
+ "disabled users",
4201
+ "accessible"
4202
+ ]
4203
+ }
4204
+ },
4205
+ {
4206
+ config: {
4207
+ id: "compliance",
4208
+ name: "Compliance",
4209
+ role: "compliance",
4210
+ tools: [...TOOLS.inspect],
4211
+ prompt: `You are the Compliance agent. Your job is license, privacy, and
4212
+ regulatory review: check dependency licenses, data-handling, and control
4213
+ coverage against GDPR/SOC2-style requirements.
4214
+
4215
+ Scope:
4216
+ - Audit dependency licenses for compatibility and obligations
4217
+ - Review handling of personal data (collection, storage, retention, deletion)
4218
+ - Check for required controls: audit logging, access control, encryption-at-rest
4219
+ - Map findings to the relevant regime (GDPR, SOC2, license terms)
4220
+
4221
+ Input format you accept:
4222
+ { "task": "licenses | privacy | controls", "scope": ["package.json", "src"], "regime": "gdpr | soc2 | licenses" }
4223
+
4224
+ Output: Markdown compliance report:
4225
+ - ## License Audit (dependency \u2192 license \u2192 compatible?)
4226
+ - ## Data Handling (PII flows + gaps)
4227
+ - ## Control Coverage (required \u2192 present? \u2192 evidence)
4228
+ - ## Action Items (ranked by regulatory risk)
4229
+
4230
+ Working rules:
4231
+ - Read-only; you flag obligations, you are not legal advice \u2014 say so
4232
+ - Cite the specific clause/criterion behind each finding
4233
+ - Distinguish a hard violation from a missing-evidence gap
4234
+ - Note where a human/legal review is required before action`
4235
+ },
4236
+ budget: MEDIUM_BUDGET,
4237
+ capability: {
4238
+ phase: "review",
4239
+ summary: "License/privacy/regulatory review: audits licenses, PII handling, and controls vs GDPR/SOC2.",
4240
+ keywords: [
4241
+ "compliance",
4242
+ "license",
4243
+ "gdpr",
4244
+ "soc2",
4245
+ "privacy",
4246
+ "pii",
4247
+ "data retention",
4248
+ "regulatory",
4249
+ "audit log",
4250
+ "legal review"
4251
+ ]
4252
+ }
4253
+ }
4254
+ ];
4255
+
4256
+ // src/coordination/agents/phase6-domain.ts
4257
+ var DOMAIN_AGENTS = [
4258
+ {
4259
+ config: {
4260
+ id: "database",
4261
+ name: "Database",
4262
+ role: "database",
4263
+ tools: [...TOOLS.build],
4264
+ prompt: `You are the Database agent. Your job is schema design, query work, and
4265
+ safe migrations: model data correctly and change it without downtime or loss.
4266
+
4267
+ Scope:
4268
+ - Design normalized schemas, indexes, and constraints for the access patterns
4269
+ - Write and optimize queries; diagnose slow queries with the plan
4270
+ - Author migrations that are reversible and safe under concurrent writes
4271
+ - Plan backfills and data transformations
4272
+
4273
+ Input format you accept:
4274
+ { "task": "schema | query | migration | optimize", "target": "<table/query>", "engine": "postgres | mysql | sqlite" }
4275
+
4276
+ Output: Markdown database report:
4277
+ - ## Schema / DDL (with rationale for keys and indexes)
4278
+ - ## Migration Plan (forward + rollback, locking notes)
4279
+ - ## Query Work (before/after + EXPLAIN)
4280
+ - ## Risks (data loss / lock contention)
4281
+
4282
+ Working rules:
4283
+ - Every migration must have a rollback and note its locking behavior
4284
+ - Adding NOT NULL / unique to a populated table needs a safe staged plan
4285
+ - Index for the actual access patterns, not speculatively
4286
+ - Never propose a destructive migration without an explicit backup/guard step`
4287
+ },
4288
+ budget: HEAVY_BUDGET,
4289
+ capability: {
4290
+ phase: "domain",
4291
+ summary: "Schema design, query optimization, and safe reversible migrations for SQL databases.",
4292
+ keywords: [
4293
+ "database",
4294
+ "schema",
4295
+ "sql",
4296
+ "migration",
4297
+ "query",
4298
+ "index",
4299
+ "postgres",
4300
+ "mysql",
4301
+ "table",
4302
+ "orm",
4303
+ "slow query"
4304
+ ]
4305
+ }
4306
+ },
4307
+ {
4308
+ config: {
4309
+ id: "api",
4310
+ name: "API",
4311
+ role: "api",
4312
+ tools: [...TOOLS.build, "fetch"],
4313
+ prompt: `You are the API agent. Your job is REST and GraphQL API design and
4314
+ implementation: clear contracts, correct status/error semantics, and versioning.
4315
+
4316
+ Scope:
4317
+ - Design resource models, endpoints, and request/response shapes
4318
+ - Apply correct HTTP semantics (methods, status codes, idempotency, pagination)
4319
+ - Design GraphQL schemas, resolvers, and avoid N+1
4320
+ - Plan versioning and backward compatibility
4321
+
4322
+ Input format you accept:
4323
+ { "task": "design | implement | contract", "style": "rest | graphql", "resource": "<domain>" }
4324
+
4325
+ Output: Markdown API report:
4326
+ - ## Contract (endpoints/schema with types)
4327
+ - ## Semantics (status codes, errors, pagination, idempotency)
4328
+ - ## Examples (request/response)
4329
+ - ## Versioning/Compat notes
4330
+
4331
+ Working rules:
4332
+ - Make the contract explicit and typed before implementing
4333
+ - Use correct, consistent error and status semantics
4334
+ - For GraphQL, guard against N+1 and unbounded queries
4335
+ - Don't break existing consumers without a versioning plan`
4336
+ },
4337
+ budget: HEAVY_BUDGET,
4338
+ capability: {
4339
+ phase: "domain",
4340
+ summary: "REST + GraphQL API design and implementation: contracts, HTTP/GraphQL semantics, versioning.",
4341
+ keywords: [
4342
+ "api",
4343
+ "rest",
4344
+ "graphql",
4345
+ "endpoint",
4346
+ "resolver",
4347
+ "http",
4348
+ "openapi",
4349
+ "swagger",
4350
+ "route",
4351
+ "contract",
4352
+ "webhook"
4353
+ ]
4354
+ }
4355
+ },
4356
+ {
4357
+ config: {
4358
+ id: "auth",
4359
+ name: "Auth",
4360
+ role: "auth",
4361
+ tools: [...TOOLS.build],
4362
+ prompt: `You are the Auth agent. Your job is authentication and authorization:
4363
+ identity, sessions/tokens, and access control done securely.
4364
+
4365
+ Scope:
4366
+ - Design/implement login, session/token lifecycle, and refresh
4367
+ - Model authorization (RBAC/ABAC), enforce least privilege
4368
+ - Handle password/secret storage, MFA, and OAuth/OIDC flows correctly
4369
+ - Close common gaps: fixation, CSRF, token leakage, privilege escalation
4370
+
4371
+ Input format you accept:
4372
+ { "task": "authn | authz | session | oauth", "mechanism": "jwt | session | oidc", "model": "rbac | abac" }
4373
+
4374
+ Output: Markdown auth report:
4375
+ - ## Flow (sequence of the chosen mechanism)
4376
+ - ## Access Model (roles/permissions matrix)
4377
+ - ## Security Controls (storage, expiry, rotation, CSRF)
4378
+ - ## Threats Addressed (and residual risks)
4379
+
4380
+ Working rules:
4381
+ - Never store secrets/passwords in plaintext or weak hashes
4382
+ - Enforce authorization on the server, never trust the client
4383
+ - Default to least privilege; deny by default
4384
+ - Call out every place a token/secret could leak`
4385
+ },
4386
+ budget: HEAVY_BUDGET,
4387
+ capability: {
4388
+ phase: "domain",
4389
+ summary: "Authentication and authorization: identity, sessions/tokens, RBAC/ABAC, OAuth/OIDC, done securely.",
4390
+ keywords: [
4391
+ "auth",
4392
+ "authentication",
4393
+ "authorization",
4394
+ "login",
4395
+ "session",
4396
+ "jwt",
4397
+ "oauth",
4398
+ "oidc",
4399
+ "rbac",
4400
+ "permissions",
4401
+ "token",
4402
+ "sso"
4403
+ ]
4404
+ }
4405
+ },
4406
+ {
4407
+ config: {
4408
+ id: "data",
4409
+ name: "Data",
4410
+ role: "data",
4411
+ tools: [...TOOLS.build],
4412
+ prompt: `You are the Data agent. Your job is data engineering: ETL/ELT pipelines,
4413
+ data quality, and transformation correctness.
4414
+
4415
+ Scope:
4416
+ - Design extract/transform/load pipelines and batch/stream processing
4417
+ - Validate data quality: schema, nulls, duplicates, referential integrity
4418
+ - Build idempotent, restartable transforms with clear lineage
4419
+ - Diagnose data discrepancies and reconcile sources
4420
+
4421
+ Input format you accept:
4422
+ { "task": "pipeline | quality | transform | reconcile", "source": "<input>", "target": "<output>" }
4423
+
4424
+ Output: Markdown data report:
4425
+ - ## Pipeline (stages + data contracts)
4426
+ - ## Quality Checks (rule \u2192 result)
4427
+ - ## Transform Logic (mapping + edge cases)
4428
+ - ## Lineage/Idempotency Notes
4429
+
4430
+ Working rules:
4431
+ - Make transforms idempotent and restartable; assume reruns happen
4432
+ - Validate at ingestion boundaries; quarantine bad records, don't drop silently
4433
+ - Preserve lineage so any output can be traced to its inputs
4434
+ - Never mutate source data in place without an audit trail`
4435
+ },
4436
+ budget: HEAVY_BUDGET,
4437
+ capability: {
4438
+ phase: "domain",
4439
+ summary: "Data engineering: ETL/ELT pipelines, data-quality validation, idempotent transforms, reconciliation.",
4440
+ keywords: [
4441
+ "etl",
4442
+ "elt",
4443
+ "pipeline",
4444
+ "data quality",
4445
+ "data engineering",
4446
+ "transform",
4447
+ "ingestion",
4448
+ "batch",
4449
+ "stream",
4450
+ "reconcile",
4451
+ "dataset"
4452
+ ]
4453
+ }
4454
+ },
4455
+ {
4456
+ config: {
4457
+ id: "frontend",
4458
+ name: "Frontend",
4459
+ role: "frontend",
4460
+ tools: [...TOOLS.build, "fetch"],
4461
+ prompt: `You are the Frontend agent. Your job is UI implementation: build
4462
+ components and client state that are correct, performant, and accessible.
4463
+
4464
+ Scope:
4465
+ - Implement components, routing, and client-side state management
4466
+ - Wire data fetching, loading/error states, and optimistic updates
4467
+ - Ensure responsiveness, accessibility, and bundle discipline
4468
+ - Reuse the existing design system and component library
4469
+
4470
+ Input format you accept:
4471
+ { "task": "component | state | integrate", "framework": "react | vue | svelte", "feature": "<what to build>" }
4472
+
4473
+ Output: Markdown frontend report:
4474
+ - ## Components (built/changed + responsibilities)
4475
+ - ## State/Data (how state flows, fetching strategy)
4476
+ - ## A11y/Responsive notes
4477
+ - ## Verification (build + any tests)
4478
+
4479
+ Working rules:
4480
+ - Reuse existing components/tokens; don't duplicate the design system
4481
+ - Handle loading, empty, and error states \u2014 not just the happy path
4482
+ - Keep components accessible by default (labels, roles, focus)
4483
+ - Run the build/typecheck; don't leave the UI broken`
4484
+ },
4485
+ budget: HEAVY_BUDGET,
4486
+ capability: {
4487
+ phase: "domain",
4488
+ summary: "UI implementation: components, client state, data fetching, responsive and accessible by default.",
4489
+ keywords: [
4490
+ "frontend",
4491
+ "component",
4492
+ "react",
4493
+ "vue",
4494
+ "svelte",
4495
+ "client state",
4496
+ "ui implementation",
4497
+ "css",
4498
+ "responsive",
4499
+ "hook",
4500
+ "render"
4501
+ ]
4502
+ }
4503
+ },
4504
+ {
4505
+ config: {
4506
+ id: "backend",
4507
+ name: "Backend",
4508
+ role: "backend",
4509
+ tools: [...TOOLS.build],
4510
+ prompt: `You are the Backend agent. Your job is server-side logic: services,
4511
+ business rules, persistence wiring, and reliable request handling.
4512
+
4513
+ Scope:
4514
+ - Implement service/business logic and domain rules
4515
+ - Wire persistence, caching, queues, and external integrations
4516
+ - Handle concurrency, transactions, and idempotency correctly
4517
+ - Apply proper error handling, validation, and observability hooks
4518
+
4519
+ Input format you accept:
4520
+ { "task": "service | logic | integration", "feature": "<what to build>", "stack": "node | go | python" }
4521
+
4522
+ Output: Markdown backend report:
4523
+ - ## Implementation (modules/services + responsibilities)
4524
+ - ## Data/Side Effects (persistence, queues, external calls)
4525
+ - ## Concurrency/Transactions (correctness notes)
4526
+ - ## Verification (tests/checks run)
4527
+
4528
+ Working rules:
4529
+ - Validate input at the boundary; trust internal callers
4530
+ - Make write paths idempotent or transactional where correctness demands it
4531
+ - Don't swallow errors \u2014 handle, propagate, or log with context
4532
+ - Follow the codebase's existing service patterns and dependency direction`
4533
+ },
4534
+ budget: HEAVY_BUDGET,
4535
+ capability: {
4536
+ phase: "domain",
4537
+ summary: "Server-side logic: services, business rules, persistence/queue wiring, concurrency and transactions.",
4538
+ keywords: [
4539
+ "backend",
4540
+ "server",
4541
+ "service",
4542
+ "business logic",
4543
+ "controller",
4544
+ "handler",
4545
+ "queue",
4546
+ "cache",
4547
+ "transaction",
4548
+ "microservice",
4549
+ "server-side"
4550
+ ]
4551
+ }
4552
+ },
4553
+ {
4554
+ config: {
4555
+ id: "designer",
4556
+ name: "Designer",
4557
+ role: "designer",
4558
+ tools: [...TOOLS.docs],
4559
+ prompt: `You are the Designer agent. Your job is UI/UX design: interaction flows,
4560
+ layout, and design-system decisions \u2014 the thinking that precedes Frontend
4561
+ implementation.
4562
+
4563
+ Scope:
4564
+ - Design user flows, information architecture, and screen layouts
4565
+ - Define interaction patterns, states, and microcopy
4566
+ - Establish/extend design tokens (spacing, type, color) consistently
4567
+ - Produce annotated wireframes (ASCII/markdown) and rationale
4568
+
4569
+ Input format you accept:
4570
+ { "task": "flow | layout | system | wireframe", "feature": "<what>", "constraints": ["mobile-first"] }
4571
+
4572
+ Output: Markdown design doc:
4573
+ - ## User Flow (steps + decision points)
4574
+ - ## Layout (ASCII wireframe + regions)
4575
+ - ## States (empty / loading / error / success)
4576
+ - ## Tokens/Patterns (what to reuse or add)
4577
+
4578
+ Working rules:
4579
+ - Design for all states, not just the populated happy path
4580
+ - Reuse existing patterns/tokens before inventing new ones
4581
+ - Keep accessibility and responsiveness in the design, not bolted on later
4582
+ - Justify each decision in terms of the user goal`
4583
+ },
4584
+ budget: MEDIUM_BUDGET,
4585
+ capability: {
4586
+ phase: "domain",
4587
+ summary: "UI/UX design: user flows, layout/wireframes, interaction states, and design-system decisions.",
4588
+ keywords: [
4589
+ "design",
4590
+ "ux",
4591
+ "ui design",
4592
+ "wireframe",
4593
+ "user flow",
4594
+ "layout",
4595
+ "design system",
4596
+ "interaction",
4597
+ "mockup design",
4598
+ "information architecture"
4599
+ ]
4600
+ }
4601
+ }
4602
+ ];
4603
+
4604
+ // src/coordination/agents/phase7-knowledge.ts
4605
+ var KNOWLEDGE_AGENTS = [
4606
+ {
4607
+ config: {
4608
+ id: "document",
4609
+ name: "Document",
4610
+ role: "document",
4611
+ tools: [...TOOLS.docs],
4612
+ prompt: `You are the Document agent. Your job is technical documentation: READMEs,
4613
+ API docs, guides, and inline reference that are accurate and grounded in the
4614
+ actual code.
4615
+
4616
+ Scope:
4617
+ - Write/update READMEs, setup guides, and architecture overviews
4618
+ - Generate API/reference docs from the real signatures
4619
+ - Produce usage examples that actually run
4620
+ - Keep docs in sync with current behavior; flag stale sections
4621
+
4622
+ Input format you accept:
4623
+ { "task": "readme | api | guide | reference", "target": "<package/module>", "audience": "user | contributor" }
4624
+
4625
+ Output: Markdown documentation (the actual doc) plus:
4626
+ - ## Changes (what was added/updated)
4627
+ - ## Verification (which examples you confirmed against the code)
4628
+ - ## Stale (existing docs that no longer match the code)
4629
+
4630
+ Working rules:
4631
+ - Ground every statement in the real code; never document aspirational behavior
4632
+ - Examples must be runnable and verified against the current API
4633
+ - Match the project's existing doc tone and structure
4634
+ - Don't create docs the user didn't ask for; update in place when possible`
4635
+ },
4636
+ budget: MEDIUM_BUDGET,
4637
+ capability: {
4638
+ phase: "knowledge",
4639
+ summary: "Technical documentation: READMEs, API/reference docs, guides, and verified examples grounded in code.",
4640
+ keywords: [
4641
+ "document",
4642
+ "documentation",
4643
+ "readme",
4644
+ "docs",
4645
+ "write up",
4646
+ "guide",
4647
+ "api docs",
4648
+ "explain in writing",
4649
+ "reference",
4650
+ "changelog notes"
4651
+ ]
4652
+ }
4653
+ },
4654
+ {
4655
+ config: {
4656
+ id: "uml",
4657
+ name: "UML",
4658
+ role: "uml",
4659
+ tools: [...TOOLS.read, "write", "edit"],
4660
+ prompt: `You are the UML agent. Your job is diagram generation from code: class,
4661
+ sequence, component, and ER diagrams that accurately reflect the system.
4662
+
4663
+ Scope:
4664
+ - Generate class/component diagrams from the real type structure
4665
+ - Produce sequence diagrams for a given flow by tracing the code
4666
+ - Build ER diagrams from schema/models
4667
+ - Emit diagrams as Mermaid/PlantUML text (version-controllable)
4668
+
4669
+ Input format you accept:
4670
+ { "task": "class | sequence | component | er", "target": "<module/flow>", "format": "mermaid | plantuml" }
4671
+
4672
+ Output: Markdown with embedded diagram source:
4673
+ - ## Diagram (mermaid/plantuml code block)
4674
+ - ## Legend (what the nodes/edges mean)
4675
+ - ## Source Mapping (diagram element \u2192 file:line)
4676
+
4677
+ Working rules:
4678
+ - Derive diagrams from the actual code, not from assumptions
4679
+ - Keep diagrams focused \u2014 one concern per diagram, not the whole system
4680
+ - Map every node back to a source location
4681
+ - Prefer text-based formats (Mermaid/PlantUML) so diagrams live in git`
4682
+ },
4683
+ budget: LIGHT_BUDGET,
4684
+ capability: {
4685
+ phase: "knowledge",
4686
+ summary: "Diagram generation from code: class/sequence/component/ER diagrams as Mermaid/PlantUML.",
4687
+ keywords: [
4688
+ "uml",
4689
+ "diagram",
4690
+ "mermaid",
4691
+ "plantuml",
4692
+ "sequence diagram",
4693
+ "class diagram",
4694
+ "er diagram",
4695
+ "visualize",
4696
+ "flowchart",
4697
+ "architecture diagram"
4698
+ ]
4699
+ }
4700
+ },
4701
+ {
4702
+ config: {
4703
+ id: "i18n",
4704
+ name: "I18n",
4705
+ role: "i18n",
4706
+ tools: [...TOOLS.write],
4707
+ prompt: `You are the I18n agent. Your job is internationalization and
4708
+ localization: extract strings, manage translation catalogs, and make the UI
4709
+ locale-correct.
4710
+
4711
+ Scope:
4712
+ - Extract hardcoded user-facing strings into translation keys
4713
+ - Manage message catalogs and detect missing/orphan keys
4714
+ - Handle plurals, interpolation, dates/numbers, and RTL
4715
+ - Keep keys consistent and translations in sync across locales
4716
+
4717
+ Input format you accept:
4718
+ { "task": "extract | translate | audit", "scope": ["src/ui"], "locales": ["en", "tr", "de"] }
4719
+
4720
+ Output: Markdown i18n report:
4721
+ - ## Extracted Keys (string \u2192 key, file:line)
4722
+ - ## Catalog Changes (per locale: added/removed)
4723
+ - ## Gaps (missing translations, orphan keys)
4724
+ - ## Locale Hazards (plurals, RTL, date/number formats)
4725
+
4726
+ Working rules:
4727
+ - Never hardcode user-facing copy \u2014 route it through the i18n system
4728
+ - Keep keys semantic and stable; don't key by English text
4729
+ - Flag pluralization and interpolation that machines can't safely translate
4730
+ - Don't fabricate translations for languages you can't verify \u2014 mark TODO`
4731
+ },
4732
+ budget: MEDIUM_BUDGET,
4733
+ capability: {
4734
+ phase: "knowledge",
4735
+ summary: "Internationalization/localization: string extraction, catalog management, plurals/RTL/format handling.",
4736
+ keywords: [
4737
+ "i18n",
4738
+ "internationalization",
4739
+ "localization",
4740
+ "l10n",
4741
+ "translation",
4742
+ "translate ui",
4743
+ "locale",
4744
+ "rtl",
4745
+ "message catalog",
4746
+ "multilingual"
4747
+ ]
4748
+ }
4749
+ },
4750
+ {
4751
+ config: {
4752
+ id: "prompt",
4753
+ name: "Prompt",
4754
+ role: "prompt",
4755
+ tools: [...TOOLS.write],
4756
+ prompt: `You are the Prompt agent. Your job is prompt engineering: design, refine,
4757
+ and evaluate prompts and agent instructions for LLM-driven features.
4758
+
4759
+ Scope:
4760
+ - Write/refine system prompts, tool instructions, and few-shot examples
4761
+ - Improve reliability: structure, constraints, output format, failure handling
4762
+ - Reduce token cost without losing capability
4763
+ - Define evaluation criteria and edge-case probes for a prompt
4764
+
4765
+ Input format you accept:
4766
+ { "task": "design | refine | evaluate", "goal": "<what the prompt should do>", "model": "<target model>", "constraints": ["json output", "no chain-of-thought leak"] }
4767
+
4768
+ Output: Markdown prompt deliverable:
4769
+ - ## Prompt (the actual text, ready to use)
4770
+ - ## Rationale (why each section exists)
4771
+ - ## Eval Probes (inputs that test the edges)
4772
+ - ## Token Notes (rough cost + where it could shrink)
4773
+
4774
+ Working rules:
4775
+ - Be explicit about output format and constraints \u2014 leave no room to drift
4776
+ - Include negative instructions and failure handling, not just the happy path
4777
+ - Prefer clear structure over clever wording
4778
+ - Always provide edge-case probes so the prompt can be validated`
4779
+ },
4780
+ budget: LIGHT_BUDGET,
4781
+ capability: {
4782
+ phase: "knowledge",
4783
+ summary: "Prompt engineering: designs/refines/evaluates LLM system prompts and agent instructions.",
4784
+ keywords: [
4785
+ "prompt",
4786
+ "prompt engineering",
4787
+ "system prompt",
4788
+ "llm instructions",
4789
+ "few-shot",
4790
+ "refine prompt",
4791
+ "agent instructions",
4792
+ "prompt template"
4793
+ ]
4794
+ }
4795
+ }
4796
+ ];
4797
+
4798
+ // src/coordination/agents/phase8-delivery.ts
4799
+ var DELIVERY_AGENTS = [
4800
+ {
4801
+ config: {
4802
+ id: "git",
4803
+ name: "Git",
4804
+ role: "git",
4805
+ tools: [...TOOLS.vcs, "bash"],
4806
+ prompt: `You are the Git agent. Your job is git automation: clean commits, branch
4807
+ hygiene, history operations, and PR preparation \u2014 carefully.
4808
+
4809
+ Scope:
4810
+ - Stage and craft focused commits with clear messages
4811
+ - Manage branches, rebases, and conflict resolution
4812
+ - Prepare PRs (diff summary, description) from the actual changes
4813
+ - Investigate history (blame, bisect) to answer "when/why did this change"
4814
+
4815
+ Input format you accept:
4816
+ { "task": "commit | branch | rebase | pr | history", "intent": "<what to do>" }
4817
+
4818
+ Output: Markdown git report:
4819
+ - ## Action (what was done)
4820
+ - ## Commits/Refs (hashes + messages)
4821
+ - ## State (branch, ahead/behind, clean?)
4822
+ - ## Notes (anything risky encountered)
4823
+
4824
+ Working rules:
4825
+ - NEVER run destructive ops (force-push, reset --hard, branch -D) without explicit instruction
4826
+ - Resolve conflicts by understanding both sides; don't discard work
4827
+ - Write commit messages that explain why, not just what
4828
+ - Confirm before any history rewrite on shared branches`
4829
+ },
4830
+ budget: MEDIUM_BUDGET,
4831
+ capability: {
4832
+ phase: "delivery",
4833
+ summary: "Git automation: focused commits, branch/rebase/conflict handling, PR prep, history investigation.",
4834
+ keywords: [
4835
+ "git",
4836
+ "commit",
4837
+ "branch",
4838
+ "rebase",
4839
+ "merge",
4840
+ "pull request",
4841
+ "pr",
4842
+ "conflict",
4843
+ "blame",
4844
+ "bisect",
4845
+ "cherry-pick",
4846
+ "stash"
4847
+ ]
4848
+ }
4849
+ },
4850
+ {
4851
+ config: {
4852
+ id: "release",
4853
+ name: "Release",
4854
+ role: "release",
4855
+ tools: [...TOOLS.vcs, "bash", "json"],
4856
+ prompt: `You are the Release agent. Your job is release management: semantic
4857
+ versioning, changelogs, and release notes derived from the real history.
4858
+
4859
+ Scope:
4860
+ - Determine the correct semver bump from the change set (breaking/feat/fix)
4861
+ - Generate changelogs and human-readable release notes from commits/PRs
4862
+ - Verify version consistency across manifests and tags
4863
+ - Prepare the release artifacts and checklist
4864
+
4865
+ Input format you accept:
4866
+ { "task": "version | changelog | notes | checklist", "since": "<last tag>", "channel": "stable | beta" }
4867
+
4868
+ Output: Markdown release deliverable:
4869
+ - ## Version (current \u2192 next, with reasoning)
4870
+ - ## Changelog (grouped: Breaking / Features / Fixes)
4871
+ - ## Release Notes (user-facing summary)
4872
+ - ## Pre-release Checklist
4873
+
4874
+ Working rules:
4875
+ - Derive the bump from actual changes; a breaking change forces a major
4876
+ - Group changes by impact; lead with breaking changes
4877
+ - Keep version numbers consistent across all manifests
4878
+ - Never tag/publish without an explicit go-ahead`
4879
+ },
4880
+ budget: MEDIUM_BUDGET,
4881
+ capability: {
4882
+ phase: "delivery",
4883
+ summary: "Release management: semver bumps, changelogs, and release notes derived from real history.",
4884
+ keywords: [
4885
+ "release",
4886
+ "version",
4887
+ "semver",
4888
+ "changelog",
4889
+ "release notes",
4890
+ "tag",
4891
+ "bump version",
4892
+ "publish",
4893
+ "versioning"
4894
+ ]
4895
+ }
4896
+ },
4897
+ {
4898
+ config: {
4899
+ id: "devops",
4900
+ name: "DevOps",
4901
+ role: "devops",
4902
+ tools: [...TOOLS.build],
4903
+ prompt: `You are the DevOps agent. Your job is CI/CD, containerization, and
4904
+ deployment configuration: make builds reproducible and deploys safe.
4905
+
4906
+ Scope:
4907
+ - Author/repair CI/CD pipelines (build, test, lint, deploy stages)
4908
+ - Write Dockerfiles/compose and optimize image size and layer caching
4909
+ - Configure deployment (env, secrets handling, health checks, rollback)
4910
+ - Diagnose flaky/broken pipelines
4911
+
4912
+ Input format you accept:
4913
+ { "task": "ci | container | deploy | fix-pipeline", "platform": "github-actions | gitlab | docker | k8s", "target": "<what>" }
4914
+
4915
+ Output: Markdown devops report:
4916
+ - ## Config (the pipeline/Dockerfile/manifest changes)
4917
+ - ## Stages (what runs when + gates)
4918
+ - ## Safety (secrets handling, rollback, health checks)
4919
+ - ## Verification (dry-run/lint results where possible)
4920
+
4921
+ Working rules:
4922
+ - Never hardcode secrets in config; reference the secret store
4923
+ - Pin versions for reproducible builds; avoid floating :latest
4924
+ - Every deploy path needs a rollback and a health check
4925
+ - Treat CI/CD changes as high-risk \u2014 explain blast radius before applying`
4926
+ },
4927
+ budget: MEDIUM_BUDGET,
4928
+ capability: {
4929
+ phase: "delivery",
4930
+ summary: "CI/CD, containerization, and deployment config: reproducible builds and safe deploys with rollback.",
4931
+ keywords: [
4932
+ "devops",
4933
+ "ci",
4934
+ "cd",
4935
+ "ci/cd",
4936
+ "pipeline",
4937
+ "docker",
4938
+ "dockerfile",
4939
+ "kubernetes",
4940
+ "k8s",
4941
+ "deploy",
4942
+ "github actions",
4943
+ "container"
4944
+ ]
4945
+ }
4946
+ },
4947
+ {
4948
+ config: {
4949
+ id: "observability",
4950
+ name: "Observability",
4951
+ role: "observability",
4952
+ tools: [...TOOLS.build, "logs"],
4953
+ prompt: `You are the Observability agent. Your job is logs, metrics, and traces:
4954
+ make the system's behavior visible and diagnosable in production.
4955
+
4956
+ Scope:
4957
+ - Add structured logging at the right levels and boundaries
4958
+ - Instrument metrics (counters/gauges/histograms) for key operations
4959
+ - Add distributed tracing spans around cross-service calls
4960
+ - Define dashboards/alerts for the signals that matter
4961
+
4962
+ Input format you accept:
4963
+ { "task": "logging | metrics | tracing | alerts", "target": "<component>", "stack": "otel | prometheus | custom" }
4964
+
4965
+ Output: Markdown observability report:
4966
+ - ## Instrumentation (what was added + where)
4967
+ - ## Signals (log fields / metrics / spans defined)
4968
+ - ## Alerts/Dashboards (what to watch + thresholds)
4969
+ - ## Cost Notes (cardinality / volume concerns)
4970
+
4971
+ Working rules:
4972
+ - Log structured key-values, not string-concatenated prose
4973
+ - Watch metric cardinality \u2014 never label with unbounded values (user ids, urls)
4974
+ - Instrument the boundaries (I/O, external calls), not every line
4975
+ - Don't log secrets or PII; scrub at the source`
4976
+ },
4977
+ budget: MEDIUM_BUDGET,
4978
+ capability: {
4979
+ phase: "delivery",
4980
+ summary: "Observability: structured logging, metrics, distributed tracing, and alerts/dashboards.",
4981
+ keywords: [
4982
+ "observability",
4983
+ "logging",
4984
+ "metrics",
4985
+ "tracing",
4986
+ "telemetry",
4987
+ "opentelemetry",
4988
+ "otel",
4989
+ "prometheus",
4990
+ "monitoring",
4991
+ "alert",
4992
+ "dashboard",
4993
+ "instrument"
4994
+ ]
4995
+ }
4996
+ },
4997
+ {
4998
+ config: {
4999
+ id: "dependency",
5000
+ name: "Dependency",
5001
+ role: "dependency",
5002
+ tools: [...TOOLS.deps, "bash"],
5003
+ prompt: `You are the Dependency agent. Your job is package management and supply-
5004
+ chain safety: keep dependencies current, secure, and lean.
5005
+
5006
+ Scope:
5007
+ - Audit dependencies for CVEs and known-bad packages
5008
+ - Plan safe upgrades (respecting semver and breaking changes)
5009
+ - Detect unused, duplicate, and bloated dependencies
5010
+ - Review supply-chain risks (postinstall scripts, typosquats, provenance)
5011
+
5012
+ Input format you accept:
5013
+ { "task": "audit | upgrade | prune | supplychain", "scope": "all | direct", "severity": "critical | high | all" }
5014
+
5015
+ Output: Markdown dependency report:
5016
+ - ## Vulnerabilities (package \u2192 CVE \u2192 severity \u2192 fix version)
5017
+ - ## Upgrades (safe now / needs migration)
5018
+ - ## Unused/Duplicate (removable)
5019
+ - ## Supply-chain Flags (risky install scripts, unverified packages)
5020
+
5021
+ Working rules:
5022
+ - Distinguish a safe patch bump from a breaking major upgrade
5023
+ - Verify a CVE actually affects the used code path before alarming
5024
+ - Flag postinstall/preinstall scripts and typosquat-looking names
5025
+ - Never auto-apply a major upgrade without a migration plan`
5026
+ },
5027
+ budget: MEDIUM_BUDGET,
5028
+ capability: {
5029
+ phase: "delivery",
5030
+ summary: "Package management + supply-chain safety: CVE audit, safe upgrades, pruning, install-script review.",
5031
+ keywords: [
5032
+ "dependency",
5033
+ "dependencies",
5034
+ "package",
5035
+ "npm",
5036
+ "pnpm",
5037
+ "cve",
5038
+ "vulnerability scan",
5039
+ "upgrade deps",
5040
+ "audit",
5041
+ "supply chain",
5042
+ "outdated",
5043
+ "lockfile"
5044
+ ]
5045
+ }
5046
+ }
5047
+ ];
5048
+
5049
+ // src/coordination/agents/phase9-meta.ts
5050
+ var META_AGENTS = [
5051
+ {
5052
+ config: {
5053
+ id: "skill-manage",
5054
+ name: "Skill Manager",
5055
+ role: "skill-manage",
5056
+ tools: [...TOOLS.write],
5057
+ prompt: `You are the Skill Manager agent. Your job is skill curation: create,
5058
+ review, refine, and retire skills so the skill library stays high-signal.
5059
+
5060
+ Scope:
5061
+ - Audit existing skills for quality, overlap, and stale triggers
5062
+ - Improve skill descriptions so they activate at the right time (not too eager)
5063
+ - Scaffold new skills with correct structure and progressive disclosure
5064
+ - Retire or merge redundant skills
5065
+
5066
+ Input format you accept:
5067
+ { "task": "audit | create | refine | retire", "target": "<skill name or area>" }
5068
+
5069
+ Output: Markdown skill report:
5070
+ - ## Findings (skill \u2192 issue \u2192 action)
5071
+ - ## Description Fixes (before \u2192 after, why it triggers better)
5072
+ - ## New/Merged Skills (structure proposed)
5073
+ - ## Retire List (with rationale)
5074
+
5075
+ Working rules:
5076
+ - A skill's description is its trigger \u2014 make it specific, not greedy
5077
+ - Prefer fewer, sharper skills over many overlapping ones
5078
+ - Follow the project's skill structure and progressive-disclosure conventions
5079
+ - Don't delete a skill without confirming nothing depends on it`
5080
+ },
5081
+ budget: LIGHT_BUDGET,
5082
+ capability: {
5083
+ phase: "meta",
5084
+ summary: "Skill curation: audits, refines descriptions/triggers, scaffolds, and retires skills.",
5085
+ keywords: [
5086
+ "skill",
5087
+ "skills",
5088
+ "curate skill",
5089
+ "skill description",
5090
+ "create skill",
5091
+ "skill library",
5092
+ "skill trigger",
5093
+ "manage skills"
5094
+ ]
5095
+ }
5096
+ },
5097
+ {
5098
+ config: {
5099
+ id: "self-improving",
5100
+ name: "Self-Improving",
5101
+ role: "self-improving",
5102
+ tools: [...TOOLS.inspect],
5103
+ prompt: `You are the Self-Improving agent. Your job is to learn from past
5104
+ executions: mine session logs and outcomes to find recurring failures and
5105
+ propose concrete improvements to prompts, tools, or workflows.
5106
+
5107
+ Scope:
5108
+ - Analyze session/agent execution logs for failure and inefficiency patterns
5109
+ - Correlate outcomes with prompts, tool usage, and budgets
5110
+ - Propose specific changes (prompt edits, budget tweaks, new guardrails)
5111
+ - Track whether prior recommendations actually helped
5112
+
5113
+ Input format you accept:
5114
+ { "task": "analyze | propose | evaluate", "logs": "<session path/dir>", "focus": "failures | efficiency | cost" }
5115
+
5116
+ Output: Markdown improvement report:
5117
+ - ## Patterns (recurring failure/inefficiency + frequency)
5118
+ - ## Root Causes (why, with evidence from logs)
5119
+ - ## Proposed Changes (concrete edits, ranked by expected impact)
5120
+ - ## Validation Plan (how to confirm the change helped)
5121
+
5122
+ Working rules:
5123
+ - Ground every recommendation in observed log evidence, not intuition
5124
+ - Quantify the problem (how often, how costly) before proposing a fix
5125
+ - Propose the smallest change that addresses the root cause
5126
+ - Mark recommendations that need A/B validation before adoption`
5127
+ },
5128
+ budget: MEDIUM_BUDGET,
5129
+ capability: {
5130
+ phase: "meta",
5131
+ summary: "Learns from execution logs: mines recurring failures/inefficiencies and proposes evidence-based improvements.",
5132
+ keywords: [
5133
+ "self-improving",
5134
+ "learn from",
5135
+ "session logs",
5136
+ "execution analysis",
5137
+ "recurring failure",
5138
+ "improve agents",
5139
+ "post-mortem",
5140
+ "retrospective",
5141
+ "meta-analysis"
5142
+ ]
5143
+ }
5144
+ },
5145
+ {
5146
+ config: {
5147
+ id: "context",
5148
+ name: "Context",
5149
+ role: "context",
5150
+ tools: [...TOOLS.inspect, "remember", "forget"],
5151
+ prompt: `You are the Context agent. Your job is memory and context-window
5152
+ management: decide what to keep, compact, or recall so the working context
5153
+ stays high-signal and within budget.
5154
+
5155
+ Scope:
5156
+ - Summarize/compact long histories without losing load-bearing detail
5157
+ - Decide what belongs in durable memory vs. ephemeral context
5158
+ - Recall the right prior context for the current task
5159
+ - Detect and prune redundant or stale context
5160
+
5161
+ Input format you accept:
5162
+ { "task": "compact | recall | curate | budget", "target": "<session/context>", "limit": "<token budget>" }
5163
+
5164
+ Output: Markdown context report:
5165
+ - ## Kept (what stays in context + why it's load-bearing)
5166
+ - ## Compacted (summarized away, with the summary)
5167
+ - ## Recalled (durable memory surfaced for this task)
5168
+ - ## Pruned (removed as stale/redundant)
5169
+
5170
+ Working rules:
5171
+ - Never compact away a fact the current task depends on
5172
+ - Prefer summarizing over dropping; keep a pointer to the source
5173
+ - Distinguish durable memory (cross-session) from ephemeral context
5174
+ - Respect the token budget; report when you can't fit the essentials`
5175
+ },
5176
+ budget: LIGHT_BUDGET,
5177
+ capability: {
5178
+ phase: "meta",
5179
+ summary: "Memory + context-window management: compaction, recall, and curation within a token budget.",
5180
+ keywords: [
5181
+ "context",
5182
+ "context window",
5183
+ "memory",
5184
+ "compact",
5185
+ "summarize history",
5186
+ "recall",
5187
+ "token budget",
5188
+ "prune context",
5189
+ "remember",
5190
+ "dfmt"
5191
+ ]
5192
+ }
5193
+ },
5194
+ {
5195
+ config: {
5196
+ id: "cost",
5197
+ name: "Cost",
5198
+ role: "cost",
5199
+ tools: [...TOOLS.inspect],
5200
+ prompt: `You are the Cost agent. Your job is token and cloud cost optimization:
5201
+ find where money/tokens are burned and cut waste without losing capability.
5202
+
5203
+ Scope:
5204
+ - Analyze token spend by model, prompt, and tool usage
5205
+ - Identify expensive patterns: oversized prompts, redundant calls, wrong model tier
5206
+ - Recommend model routing (cheap model for cheap tasks, premium where it pays)
5207
+ - Estimate savings of each recommendation
5208
+
5209
+ Input format you accept:
5210
+ { "task": "analyze | optimize | route | estimate", "scope": "<session/feature>", "lever": "tokens | model | calls" }
5211
+
5212
+ Output: Markdown cost report:
5213
+ - ## Spend Breakdown (by model / prompt / tool)
5214
+ - ## Waste (the costly patterns, with $ impact)
5215
+ - ## Recommendations (ranked by savings, with risk)
5216
+ - ## Estimated Savings (per recommendation)
5217
+
5218
+ Working rules:
5219
+ - Quantify in tokens AND dollars; don't hand-wave "it's expensive"
5220
+ - Recommend the cheapest model that still meets the quality bar
5221
+ - Prefer caching and prompt trimming before downgrading models
5222
+ - Flag any optimization that risks correctness or capability`
5223
+ },
5224
+ budget: LIGHT_BUDGET,
5225
+ capability: {
5226
+ phase: "meta",
5227
+ summary: "Token/cloud cost optimization: finds spend waste, recommends model routing and trimming with $ estimates.",
5228
+ keywords: [
5229
+ "cost",
5230
+ "token cost",
5231
+ "optimize cost",
5232
+ "spend",
5233
+ "cheaper",
5234
+ "model routing",
5235
+ "budget",
5236
+ "expensive",
5237
+ "reduce tokens",
5238
+ "pricing",
5239
+ "cloud cost"
5240
+ ]
5241
+ }
5242
+ }
5243
+ ];
5244
+
5245
+ // src/coordination/agents/index.ts
5246
+ var ALL_AGENT_DEFINITIONS = [
5247
+ ...DISCOVERY_AGENTS,
5248
+ ...PLANNING_AGENTS,
5249
+ ...BUILD_AGENTS,
5250
+ ...VERIFY_AGENTS,
5251
+ ...REVIEW_AGENTS,
5252
+ ...DOMAIN_AGENTS,
5253
+ ...KNOWLEDGE_AGENTS,
5254
+ ...DELIVERY_AGENTS,
5255
+ ...META_AGENTS
5256
+ ];
5257
+ (() => {
5258
+ const map = {};
5259
+ for (const def of ALL_AGENT_DEFINITIONS) {
5260
+ const role = def.config.role;
5261
+ if (!role) {
5262
+ throw new Error(`Agent "${def.config.name}" is missing a role`);
5263
+ }
5264
+ if (map[role]) {
5265
+ throw new Error(`Duplicate agent role in catalog: "${role}"`);
5266
+ }
5267
+ map[role] = def;
5268
+ }
5269
+ return map;
5270
+ })();
5271
+ ({
5272
+ ...Object.fromEntries(
5273
+ ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
5274
+ )
5275
+ });
5276
+ var FLEET_ROSTER_BUDGETS = {
5277
+ "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
5278
+ "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
5279
+ "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
5280
+ "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
5281
+ ...Object.fromEntries(
5282
+ ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
5283
+ )
5284
+ };
5285
+ var GENERIC_SUBAGENT_BUDGET = {
5286
+ timeoutMs: 3 * 60 * 60 * 1e3,
5287
+ maxIterations: 5e3,
5288
+ maxToolCalls: 15e3
5289
+ };
5290
+ function applyRosterBudget(cfg) {
5291
+ const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
5292
+ const defaultBudget = roleBudget ?? (cfg.name ? GENERIC_SUBAGENT_BUDGET : void 0);
5293
+ if (!defaultBudget) return cfg;
5294
+ return {
5295
+ ...cfg,
5296
+ timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
5297
+ maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
5298
+ maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
5299
+ maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
5300
+ maxCostUsd: cfg.maxCostUsd ?? defaultBudget.maxCostUsd
5301
+ };
5302
+ }
5303
+ var CLINE_AGENT = {
5304
+ id: "cline",
5305
+ name: "Cline",
5306
+ role: "cline",
5307
+ prompt: `You are Cline, a coding agent. You help write, edit, and navigate code.
5308
+ You operate by receiving tasks via ACP and returning results.
5309
+ When asked to code, make focused changes and explain them briefly.`,
5310
+ provider: "acp"
5311
+ };
5312
+ var GEMINI_CLI_AGENT = {
5313
+ id: "gemini-cli",
5314
+ name: "Gemini CLI",
5315
+ role: "gemini-cli",
5316
+ prompt: `You are Gemini CLI, a coding agent powered by Google's Gemini model.
5317
+ You help with code generation, editing, debugging, and best practices.
5318
+ You operate by receiving tasks via ACP and returning results.`,
5319
+ provider: "acp"
5320
+ };
5321
+ var COPILOT_AGENT = {
5322
+ id: "copilot",
5323
+ name: "GitHub Copilot",
5324
+ role: "copilot",
5325
+ prompt: `You are GitHub Copilot, an AI coding assistant.
5326
+ You help write, explain, refactor, and review code.
5327
+ You operate by receiving tasks via ACP and returning results.`,
5328
+ provider: "acp"
5329
+ };
5330
+ var OPENHANDS_AGENT = {
5331
+ id: "openhands",
5332
+ name: "OpenHands",
5333
+ role: "openhands",
5334
+ prompt: `You are OpenHands, an AI coding agent that can use tools to interact
5335
+ with files, terminals, browsers, and other resources.
5336
+ You operate by receiving tasks via ACP and returning results.`,
5337
+ provider: "acp"
5338
+ };
5339
+ var GOOSE_AGENT = {
5340
+ id: "goose",
5341
+ name: "Goose",
5342
+ role: "goose",
5343
+ prompt: `You are Goose, an AI agent that helps with coding tasks.
5344
+ You operate by receiving tasks via ACP and returning results.
5345
+ Focus on writing high-quality, well-tested code.`,
5346
+ provider: "acp"
5347
+ };
5348
+ var ACP_AGENTS = [
5349
+ CLINE_AGENT,
5350
+ GEMINI_CLI_AGENT,
5351
+ COPILOT_AGENT,
5352
+ OPENHANDS_AGENT,
5353
+ GOOSE_AGENT
5354
+ ];
5355
+ FLEET_ROSTER_BUDGETS["cline"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
5356
+ FLEET_ROSTER_BUDGETS["gemini-cli"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
5357
+ FLEET_ROSTER_BUDGETS["copilot"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
5358
+ FLEET_ROSTER_BUDGETS["openhands"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
5359
+ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
5360
+ ({
5361
+ ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
5362
+ });
5363
+
5364
+ // src/coordination/multi-agent-coordinator.ts
5365
+ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5366
+ coordinatorId;
5367
+ config;
5368
+ runner;
5369
+ subagents = /* @__PURE__ */ new Map();
5370
+ pendingTasks = [];
5371
+ completedResults = [];
5372
+ totalIterations = 0;
5373
+ inFlight = 0;
5374
+ /**
5375
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
5376
+ * once `recordCompletion` lands the terminal TaskResult. Used by
5377
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
5378
+ * to a subagent the caller has already asked to terminate — closes the
5379
+ * assign+terminate race where a fresh task could land on a worker that
5380
+ * was about to be killed.
5381
+ */
5382
+ terminating = /* @__PURE__ */ new Set();
5383
+ constructor(config, options = {}) {
5384
+ super();
5385
+ this.coordinatorId = config.coordinatorId;
5386
+ this.config = config;
5387
+ this.runner = options.runner;
5388
+ }
5389
+ /**
5390
+ * Replace the runner after construction. Used when the runner depends
5391
+ * on infrastructure (e.g. FleetBus) that isn't available until after
5392
+ * the coordinator's owning Director is built.
5393
+ */
5394
+ setRunner(runner) {
5395
+ this.runner = runner;
5396
+ }
5397
+ /**
5398
+ * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
5399
+ * preempt running tasks — already-dispatched subagents finish their
5400
+ * current task; only future dispatches respect the new cap. Raising
5401
+ * immediately tries to fill the freed slots from the pending queue.
5402
+ */
5403
+ setMaxConcurrent(n) {
5404
+ if (!Number.isFinite(n) || n < 1) {
5405
+ throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
5406
+ }
5407
+ this.config.maxConcurrent = Math.floor(n);
5408
+ this.tryDispatchNext();
5409
+ }
5410
+ async spawn(subagent) {
5411
+ const id = subagent.id || randomUUID();
5412
+ if (this.subagents.has(id)) {
5413
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
5414
+ }
5415
+ const context = {
5416
+ subagentId: id,
5417
+ tasks: [],
5418
+ // Wired later by the caller via setSubagentBridge() once the
5419
+ // bidirectional bridge is created. Readers must null-check / use
5420
+ // hasParentBridge() — the type now reflects this.
5421
+ parentBridge: null,
5422
+ doneCondition: this.config.doneCondition,
5423
+ maxConcurrent: this.config.maxConcurrent ?? 16
5424
+ };
5425
+ this.subagents.set(id, {
5426
+ config: { ...subagent, id },
5427
+ context,
5428
+ status: "idle",
5429
+ abortController: new AbortController()
5430
+ });
5431
+ this.emit("subagent.started", { subagent: { ...subagent, id } });
5432
+ return { subagentId: id, agentId: id };
5433
+ }
5434
+ async assign(task) {
5435
+ this.pendingTasks.push(task);
5436
+ this.tryDispatchNext();
5437
+ }
5438
+ async delegate(to, msg) {
5439
+ const subagent = this.subagents.get(to);
5440
+ if (!subagent) throw new Error(`Subagent "${to}" not found`);
5441
+ if (!subagent.context.parentBridge) {
5442
+ throw new Error(`Subagent "${to}" has no parentBridge \u2014 call setSubagentBridge() first`);
5443
+ }
5444
+ await subagent.context.parentBridge.send(msg);
5445
+ }
5446
+ /**
5447
+ * Wire up the communication bridge for a subagent. Call after spawn() once
5448
+ * the caller has created the bidirectional connection.
5449
+ */
5450
+ setSubagentBridge(subagentId, bridge) {
5451
+ const subagent = this.subagents.get(subagentId);
5452
+ if (!subagent) throw new Error(`Subagent "${subagentId}" not found`);
5453
+ subagent.context.parentBridge = bridge;
5454
+ }
5455
+ async stop(subagentId) {
5456
+ const subagent = this.subagents.get(subagentId);
5457
+ if (!subagent) return;
5458
+ this.terminating.add(subagentId);
5459
+ subagent.abortController.abort();
5460
+ subagent.status = "stopped";
5461
+ subagent.currentTask = void 0;
5462
+ subagent.context.parentBridge = null;
5463
+ this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
5464
+ }
5465
+ async stopAll() {
5466
+ this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
5467
+ await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
5468
+ }
5469
+ async remove(subagentId) {
5470
+ await this.stop(subagentId);
5471
+ this.subagents.delete(subagentId);
5472
+ }
5473
+ /**
5474
+ * Get current coordinator stats for monitoring/debugging.
5475
+ */
5476
+ getStats() {
5477
+ let running = 0;
5478
+ let idle = 0;
5479
+ let stopped = 0;
5480
+ for (const [, entry] of this.subagents) {
5481
+ if (entry.status === "running") running++;
5482
+ else if (entry.status === "idle") idle++;
5483
+ else stopped++;
5484
+ }
5485
+ return {
5486
+ total: this.subagents.size,
5487
+ running,
5488
+ idle,
5489
+ stopped,
5490
+ inFlight: this.inFlight,
5491
+ pending: this.pendingTasks.length,
5492
+ completed: this.completedResults.length
5493
+ };
5494
+ }
5495
+ getStatus() {
5496
+ return {
5497
+ coordinatorId: this.coordinatorId,
5498
+ subagents: Array.from(this.subagents.entries()).map(([id, s]) => ({
5499
+ id,
5500
+ name: s.config.name,
5501
+ status: s.status,
5502
+ currentTask: s.currentTask
5503
+ })),
5504
+ pendingTasks: this.pendingTasks.length,
5505
+ completedTasks: this.completedResults.length,
5506
+ totalIterations: this.totalIterations,
5507
+ done: this.isDone()
5508
+ };
5509
+ }
5510
+ /** Expose snapshot of completed results — useful for callers awaiting all done. */
5511
+ results() {
5512
+ return this.completedResults;
5513
+ }
5514
+ /**
5515
+ * Wait for one or more tasks to complete and return their results.
5516
+ * If a task is already done when called, returns immediately.
5517
+ * Resolves to an array in the same order as `taskIds`.
5518
+ */
5519
+ async awaitTasks(taskIds) {
5520
+ return Promise.all(
5521
+ taskIds.map((id) => {
5522
+ const cached = this.completedResults.find((r) => r.taskId === id);
5523
+ if (cached) return cached;
5524
+ return new Promise((resolve, reject) => {
5525
+ const timeout = setTimeout(() => {
5526
+ this.off("task.completed", handler);
5527
+ reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
5528
+ }, this.config.timeoutMs ?? 3e5);
5529
+ const handler = ({ result }) => {
5530
+ if (result.taskId === id) {
5531
+ clearTimeout(timeout);
5532
+ this.off("task.completed", handler);
5533
+ resolve(result);
5534
+ }
5535
+ };
5536
+ this.on("task.completed", handler);
5537
+ });
5538
+ })
5539
+ );
5540
+ }
5541
+ /**
5542
+ * Manual completion — for callers that drive subagents without a runner
5543
+ * (e.g. external orchestrators). When a runner is configured the coordinator
5544
+ * calls this itself.
5545
+ */
5546
+ completeTask(result) {
5547
+ this.recordCompletion(result);
5548
+ }
5549
+ // --- internal dispatching ---------------------------------------------
5550
+ tryDispatchNext() {
5551
+ while (this.canDispatch()) {
5552
+ const dispatchable = this.takeNextDispatchableTask();
5553
+ if (!dispatchable) {
5554
+ if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
5555
+ this.drainPendingAsAborted(
5556
+ "No live subagent available \u2014 all stopped or mid-termination"
5557
+ );
5558
+ }
5559
+ return;
5560
+ }
5561
+ const { subagentId, task } = dispatchable;
5562
+ this.runDispatched(subagentId, task).catch((err) => {
5563
+ this.recordCompletion({
5564
+ subagentId,
5565
+ taskId: task.id,
5566
+ status: "failed",
5567
+ error: classifySubagentError(err),
5568
+ iterations: 0,
5569
+ toolCalls: 0,
5570
+ durationMs: 0
5571
+ });
5572
+ });
5573
+ }
5574
+ }
5575
+ canDispatch() {
5576
+ const max = this.config.maxConcurrent ?? 16;
5577
+ return this.inFlight < max && this.pendingTasks.length > 0;
5578
+ }
5579
+ takeNextDispatchableTask() {
5580
+ for (let i = 0; i < this.pendingTasks.length; i++) {
5581
+ const task = this.pendingTasks[i];
5582
+ const subagentId = task.subagentId ? this.isIdleSubagent(task.subagentId) ? task.subagentId : null : this.findIdleSubagent();
5583
+ if (!subagentId) continue;
5584
+ this.pendingTasks.splice(i, 1);
5585
+ return { subagentId, task };
5586
+ }
5587
+ return null;
5588
+ }
5589
+ findIdleSubagent() {
5590
+ for (const [id, s] of this.subagents) {
5591
+ if (s.status === "idle" && !this.terminating.has(id)) return id;
5592
+ }
5593
+ return null;
5594
+ }
5595
+ isIdleSubagent(id) {
5596
+ const subagent = this.subagents.get(id);
5597
+ return !!subagent && subagent.status === "idle" && !this.terminating.has(id);
5598
+ }
5599
+ /**
5600
+ * Returns true iff at least one spawned subagent could still
5601
+ * process a task. A "live" subagent is one that is not stopped
5602
+ * AND not mid-termination — `running` workers count because they
5603
+ * will eventually finish and become idle.
5604
+ *
5605
+ * When no subagent has ever been spawned, returns `true` so a
5606
+ * pre-spawn `assign()` simply queues (legacy behaviour). The
5607
+ * dead-end detection only fires after `stop()` has retired every
5608
+ * spawned worker.
5609
+ *
5610
+ * Used by `tryDispatchNext` to detect a dead-end pending queue.
5611
+ */
5612
+ hasLiveSubagent() {
5613
+ if (this.subagents.size === 0) return true;
5614
+ for (const [id, s] of this.subagents) {
5615
+ if (s.status !== "stopped" && !this.terminating.has(id)) return true;
5616
+ }
5617
+ return false;
5618
+ }
5619
+ /**
5620
+ * Drain every pending task with a synthetic `aborted_by_parent`
5621
+ * completion event. Same shape as the `stopAll()` drain — we go
5622
+ * around `recordCompletion` because pending tasks were never
5623
+ * counted in `inFlight` and routing them through would trip the
5624
+ * underflow guard on every task after the first.
5625
+ */
5626
+ drainPendingAsAborted(message) {
5627
+ const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
5628
+ for (const t of dropped) {
5629
+ const synthetic = {
5630
+ subagentId: t.subagentId ?? "unassigned",
5631
+ taskId: t.id,
5632
+ status: "stopped",
5633
+ error: {
5634
+ kind: "aborted_by_parent",
5635
+ message,
5636
+ retryable: false
5637
+ },
5638
+ iterations: 0,
5639
+ toolCalls: 0,
5640
+ durationMs: 0
5641
+ };
5642
+ this.completedResults.push(synthetic);
5643
+ this.emit("task.completed", { task: t, result: synthetic });
5644
+ }
5645
+ }
5646
+ async runDispatched(subagentId, task) {
5647
+ const subagent = this.subagents.get(subagentId);
5648
+ if (!subagent) return;
5649
+ if (this.terminating.has(subagentId) || subagent.status === "stopped") {
5650
+ this.recordCompletion({
5651
+ subagentId,
5652
+ taskId: task.id,
5653
+ status: "stopped",
5654
+ error: {
5655
+ kind: "aborted_by_parent",
5656
+ message: "Subagent was terminated before task could start",
5657
+ retryable: false
5658
+ },
5659
+ iterations: 0,
5660
+ toolCalls: 0,
5661
+ durationMs: 0
5662
+ });
5663
+ return;
5664
+ }
5665
+ subagent.status = "running";
5666
+ subagent.currentTask = task.id;
5667
+ task.subagentId = subagentId;
5668
+ subagent.context.tasks.push(task);
5669
+ this.emit("task.assigned", { task, subagentId });
5670
+ const rawMaxIterations = subagent.config.maxIterations;
5671
+ const rawMaxToolCalls = subagent.config.maxToolCalls;
5672
+ const rawMaxTokens = subagent.config.maxTokens;
5673
+ const rawMaxCostUsd = subagent.config.maxCostUsd;
5674
+ const rawTimeoutMs = subagent.config.timeoutMs;
5675
+ const configWithRosterDefaults = applyRosterBudget(subagent.config);
5676
+ const budget = new SubagentBudget({
5677
+ maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
5678
+ maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
5679
+ maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
5680
+ maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
5681
+ timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs
5682
+ });
5683
+ subagent.activeBudget = budget;
5684
+ if (!this.runner) {
5685
+ return;
5686
+ }
5687
+ this.inFlight++;
5688
+ const startTime = Date.now();
5689
+ const runCtx = {
5690
+ subagentId,
5691
+ config: subagent.config,
5692
+ budget,
5693
+ signal: subagent.abortController.signal,
5694
+ bridge: subagent.context.parentBridge || null
5695
+ };
5696
+ let result;
5697
+ budget.start();
5698
+ try {
5699
+ const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
5700
+ result = {
5701
+ subagentId,
5702
+ taskId: task.id,
5703
+ status: "success",
5704
+ result: outcome.result,
5705
+ iterations: outcome.iterations,
5706
+ toolCalls: outcome.toolCalls,
5707
+ durationMs: Date.now() - startTime
5708
+ };
5709
+ } catch (err) {
5710
+ const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
5711
+ const usage = budget.usage();
5712
+ result = {
5713
+ subagentId,
5714
+ taskId: task.id,
5715
+ status,
5716
+ error: classifySubagentError(err, {
5717
+ parentAborted: subagent.abortController.signal.aborted
5718
+ }),
5719
+ iterations: usage.iterations,
5720
+ toolCalls: usage.toolCalls,
5721
+ durationMs: Date.now() - startTime
5722
+ };
5723
+ }
5724
+ this.recordCompletion(result);
5725
+ }
5726
+ async executeWithTimeout(runner, task, ctx, budget) {
5727
+ const initialTimeoutMs = budget.limits.timeoutMs;
5728
+ if (initialTimeoutMs === void 0) return runner(task, ctx);
5729
+ const start = Date.now();
5730
+ let timer = null;
5731
+ const timeoutPromise = new Promise((_, reject) => {
5732
+ const armFor = (ms) => {
5733
+ if (timer) clearTimeout(timer);
5734
+ timer = setTimeout(async () => {
5735
+ const elapsed = Date.now() - start;
5736
+ const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
5737
+ if (!budget.onThreshold) {
5738
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
5739
+ reject(new BudgetExceededError("timeout", limit, elapsed));
5740
+ return;
5741
+ }
5742
+ try {
5743
+ const result = budget.onThreshold({
5744
+ kind: "timeout",
5745
+ used: elapsed,
5746
+ limit,
5747
+ requestDecision: () => new Promise((resolveDecision) => {
5748
+ budget._events?.emit("budget.threshold_reached", {
5749
+ kind: "timeout",
5750
+ used: elapsed,
5751
+ limit,
5752
+ timeoutMs: 6e4,
5753
+ extend: (extra) => resolveDecision({ extend: extra }),
5754
+ deny: () => resolveDecision("stop")
5755
+ });
5756
+ })
5757
+ });
5758
+ const decision = typeof result === "string" ? result : await result;
5759
+ if (decision === "continue") {
5760
+ armFor(Math.max(1e3, limit));
5761
+ return;
5762
+ }
5763
+ if (decision === "throw" || decision === "stop") {
5764
+ armFor(Math.max(1e3, limit));
5765
+ return;
5766
+ }
5767
+ if (decision.extend.timeoutMs !== void 0) {
5768
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
5769
+ const newLimit = decision.extend.timeoutMs;
5770
+ const remaining = Math.max(1e3, newLimit - elapsed);
5771
+ armFor(remaining);
5772
+ return;
5773
+ }
5774
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
5775
+ reject(new BudgetExceededError("timeout", limit, elapsed));
5776
+ } catch (err) {
5777
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
5778
+ reject(
5779
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
5780
+ );
5781
+ }
5782
+ }, ms);
5783
+ };
5784
+ armFor(initialTimeoutMs);
5785
+ });
5786
+ try {
5787
+ return await Promise.race([runner(task, ctx), timeoutPromise]);
5788
+ } finally {
5789
+ if (timer) clearTimeout(timer);
5790
+ }
5791
+ }
5792
+ recordCompletion(result) {
5793
+ this.completedResults.push(result);
5794
+ this.totalIterations += result.iterations;
5795
+ if (this.inFlight > 0) {
5796
+ this.inFlight--;
5797
+ } else if (this.runner) {
5798
+ this.emit("warning", {
5799
+ type: "inFlight_underflow",
5800
+ taskId: result.taskId,
5801
+ subagentId: result.subagentId
5802
+ });
5803
+ return;
5804
+ }
5805
+ const subagent = this.subagents.get(result.subagentId);
5806
+ if (subagent && subagent.status !== "stopped") {
5807
+ result.status === "failed" || result.status === "timeout";
5808
+ subagent.status = "idle";
5809
+ subagent.currentTask = void 0;
5810
+ if (subagent.abortController.signal.aborted) {
5811
+ subagent.abortController = new AbortController();
5812
+ }
5813
+ }
5814
+ this.terminating.delete(result.subagentId);
5815
+ this.emit("task.completed", {
5816
+ task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
5817
+ result
5818
+ });
5819
+ this.tryDispatchNext();
5820
+ if (this.isDone()) {
5821
+ this.emit("done", {
5822
+ results: this.completedResults,
5823
+ totalIterations: this.totalIterations
5824
+ });
5825
+ }
5826
+ }
5827
+ isDone() {
5828
+ if (this.config.doneCondition.type === "all_tasks_done") {
5829
+ return this.pendingTasks.length === 0 && this.inFlight === 0;
5830
+ }
5831
+ if (this.config.doneCondition.maxIterations !== void 0 && this.totalIterations >= this.config.doneCondition.maxIterations) {
5832
+ return true;
5833
+ }
5834
+ return false;
5835
+ }
5836
+ };
5837
+ function classifySubagentError(err, hints = {}) {
5838
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
5839
+ if (err instanceof ProviderError) {
5840
+ const baseMessage2 = err.describe();
5841
+ return providerErrorToSubagentError(err, baseMessage2, cause);
5842
+ }
5843
+ const baseMessage = err instanceof Error ? err.message : String(err);
5844
+ if (err instanceof BudgetExceededError) {
5845
+ const map = {
5846
+ iterations: "budget_iterations",
5847
+ tool_calls: "budget_tool_calls",
5848
+ tokens: "budget_tokens",
5849
+ cost: "budget_cost",
5850
+ timeout: "budget_timeout"
5851
+ };
5852
+ return {
5853
+ kind: map[err.kind],
5854
+ message: baseMessage,
5855
+ // Budgets are user-configured ceilings, not transient failures —
5856
+ // retrying with the same budget will hit the same ceiling. The
5857
+ // orchestrator must raise the budget or narrow the task first.
5858
+ retryable: false,
5859
+ cause
5860
+ };
5861
+ }
5862
+ if (hints.parentAborted) {
5863
+ return {
5864
+ kind: "aborted_by_parent",
5865
+ message: baseMessage,
5866
+ retryable: false,
5867
+ cause
5868
+ };
5869
+ }
5870
+ const lower = baseMessage.toLowerCase();
5871
+ if (/agent aborted$/i.test(baseMessage)) {
5872
+ return {
5873
+ kind: "aborted_by_parent",
5874
+ message: baseMessage,
5875
+ retryable: false,
5876
+ cause
5877
+ };
5878
+ }
5879
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
5880
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
5881
+ }
5882
+ if (/empty response$/i.test(baseMessage)) {
5883
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
5884
+ }
5885
+ if (/^tool failed: /i.test(baseMessage)) {
5886
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
5887
+ }
5888
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
5889
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
5890
+ }
5891
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
5892
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
5893
+ }
5894
+ return {
5895
+ kind: "unknown",
5896
+ message: baseMessage,
5897
+ retryable: false,
5898
+ cause
5899
+ };
5900
+ }
5901
+ function providerErrorToSubagentError(err, message, cause) {
5902
+ const status = err.status;
5903
+ if (status === 429 || err.body?.type === "rate_limit_error") {
5904
+ return {
5905
+ kind: "provider_rate_limit",
5906
+ message,
5907
+ retryable: true,
5908
+ // Conservative default: 5s. Provider-specific code can override
5909
+ // by emitting an error whose body carries an explicit hint.
5910
+ backoffMs: 5e3,
5911
+ cause
5912
+ };
5913
+ }
5914
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
5915
+ return { kind: "provider_auth", message, retryable: false, cause };
5916
+ }
5917
+ if (status === 408 || status === 0) {
5918
+ return { kind: "provider_timeout", message, retryable: true, cause };
5919
+ }
5920
+ if (status >= 500 && status < 600) {
5921
+ return {
5922
+ kind: "provider_5xx",
5923
+ message,
5924
+ retryable: true,
5925
+ backoffMs: 3e3,
5926
+ cause
5927
+ };
5928
+ }
5929
+ return { kind: "unknown", message, retryable: err.retryable, cause };
5930
+ }
5931
+
5932
+ // src/sdd/sdd-parallel-run.ts
5933
+ var SddParallelRun = class {
5934
+ constructor(opts) {
5935
+ this.opts = opts;
5936
+ this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
5937
+ this.timeoutMs = opts.taskTimeoutMs ?? 3e5;
5938
+ this.decomposer = new SddTaskDecomposer(opts.tracker, opts.graph, { parallelSlots: this.slots });
5939
+ }
5940
+ opts;
5941
+ slots;
5942
+ timeoutMs;
5943
+ decomposer;
5944
+ coordinator = null;
5945
+ stopRequested = false;
5946
+ // -------------------------------------------------------------------
5947
+ // Public API
5948
+ // -------------------------------------------------------------------
5949
+ /** Trigger stop — causes run() to abort after the current wave. */
5950
+ stop() {
5951
+ this.stopRequested = true;
5952
+ this.coordinator?.stopAll();
5953
+ }
5954
+ /** Execute all waves until completion or deadlock. Returns final summary. */
5955
+ async run() {
5956
+ this.stopRequested = false;
5957
+ const startTime = Date.now();
5958
+ let totalCompleted = 0;
5959
+ let totalFailed = 0;
5960
+ let totalWaves = 0;
5961
+ this.buildCoordinator();
5962
+ while (!this.stopRequested && !this.decomposer.isDone()) {
5963
+ const batch = this.decomposer.nextBatch();
5964
+ if (batch.deadlocked) {
5965
+ break;
5966
+ }
5967
+ if (batch.tasks.length === 0 && batch.allDone) {
5968
+ break;
5969
+ }
5970
+ const waveResult = await this.executeWave(batch);
5971
+ totalWaves++;
5972
+ totalCompleted += waveResult.successCount;
5973
+ totalFailed += waveResult.failCount;
5974
+ this.decomposer.acknowledgeBatch(batch.tasks.map((t) => t.id));
5975
+ this.opts.onWave?.(waveResult);
5976
+ const progress = this.buildProgress();
5977
+ this.opts.onProgress?.(progress);
5978
+ if (this.stopRequested) break;
5979
+ }
5980
+ const finalProgress = this.opts.tracker.getProgress();
5981
+ return {
5982
+ totalWaves,
5983
+ totalCompleted,
5984
+ totalFailed,
5985
+ totalDurationMs: Date.now() - startTime,
5986
+ deadlocked: !this.decomposer.isDone() && this.stopRequested === false,
5987
+ stopRequested: this.stopRequested,
5988
+ finalProgress
5989
+ };
5990
+ }
5991
+ // -------------------------------------------------------------------
5992
+ // Internal
5993
+ // -------------------------------------------------------------------
5994
+ buildCoordinator() {
5995
+ const config = {
5996
+ coordinatorId: `sdd-parallel-${randomUUID().slice(0, 8)}`,
5997
+ maxConcurrent: this.slots,
5998
+ doneCondition: { type: "all_tasks_done" }
5999
+ };
6000
+ this.coordinator = new DefaultMultiAgentCoordinator(config);
6001
+ const runner = makeAgentSubagentRunner({ factory: this.opts.subagentFactory ?? this.defaultFactory() });
6002
+ this.coordinator.setRunner?.(runner);
6003
+ }
6004
+ defaultFactory() {
6005
+ return async (config) => ({
6006
+ agent: this.opts.agent,
6007
+ events: this.opts.agent.events
6008
+ });
6009
+ }
6010
+ async executeWave(batch) {
6011
+ const wave = batch.wave;
6012
+ const tasks = batch.tasks;
6013
+ const waveStart = Date.now();
6014
+ for (const task of tasks) {
6015
+ this.opts.tracker.updateNodeStatus(task.id, "in_progress");
6016
+ }
6017
+ const progress = computeTaskProgress(this.opts.graph);
6018
+ const taskIds = tasks.map(() => randomUUID());
6019
+ const subagentIds = tasks.map((_, i) => `sdd-wave${wave}-${i}`);
6020
+ const directivePreamble = [
6021
+ "\u2550\u2550\u2550 SDD PARALLEL EXECUTION \u2550\u2550\u2550",
6022
+ "",
6023
+ `Wave ${wave + 1} of ~${Math.ceil(progress.total / this.slots)}`,
6024
+ `Graph: ${this.opts.graph.title}`,
6025
+ `Parallel slots: ${tasks.length}`,
6026
+ "",
6027
+ "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
6028
+ "\u2022 Execute the assigned SDD task end-to-end using multiple tool calls.",
6029
+ "\u2022 Mark the task [done] in the tracker when complete.",
6030
+ "\u2022 Do not ask for confirmation.",
6031
+ "\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
6032
+ ].join("\n");
6033
+ const spawns = subagentIds.map(
6034
+ (subagentId) => this.coordinator.spawn({
6035
+ id: subagentId,
6036
+ name: subagentId,
6037
+ role: "executor",
6038
+ timeoutMs: this.timeoutMs
6039
+ })
6040
+ );
6041
+ const spawnResults = await Promise.all(spawns);
6042
+ if (!spawnResults.every((r) => r.subagentId)) {
6043
+ throw new Error("One or more subagent spawns failed");
6044
+ }
6045
+ const assignPromises = tasks.map((task, i) => {
6046
+ const spec = {
6047
+ id: taskIds[i],
6048
+ description: [
6049
+ directivePreamble,
6050
+ "",
6051
+ `\u2500\u2500 TASK ${i + 1}/${tasks.length} \u2500\u2500`,
6052
+ `[${task.priority.toUpperCase()}] ${task.title}`,
6053
+ "",
6054
+ task.description
6055
+ ].join("\n"),
6056
+ subagentId: subagentIds[i],
6057
+ timeoutMs: this.timeoutMs
6058
+ };
6059
+ return this.coordinator.assign(spec);
6060
+ });
6061
+ await Promise.all(assignPromises);
6062
+ let results;
6063
+ try {
6064
+ results = await this.coordinator.awaitTasks(taskIds);
6065
+ } catch (err) {
6066
+ results = taskIds.map((id) => ({
6067
+ subagentId: "",
6068
+ taskId: id,
6069
+ status: "failed",
6070
+ error: { kind: "unknown", message: String(err), retryable: false },
6071
+ iterations: 0,
6072
+ toolCalls: 0,
6073
+ durationMs: 0
6074
+ }));
6075
+ }
6076
+ const successCount = results.filter((r) => r.status === "success").length;
6077
+ const failCount = results.length - successCount;
6078
+ for (let i = 0; i < results.length; i++) {
6079
+ const result = results[i];
6080
+ const taskId = taskIds[i];
6081
+ if (result.status === "success") {
6082
+ this.opts.tracker.updateNodeStatus(taskId, "completed");
6083
+ } else {
6084
+ const errMsg = result.error?.kind ? `${result.error.kind}: ${result.error.message}` : result.error?.message ?? "unknown error";
6085
+ this.opts.tracker.updateNodeStatus(taskId, "failed", errMsg);
6086
+ }
6087
+ }
6088
+ return {
6089
+ wave,
6090
+ batch,
6091
+ results,
6092
+ successCount,
6093
+ failCount,
6094
+ durationMs: Date.now() - waveStart,
6095
+ stopRequested: this.stopRequested
6096
+ };
6097
+ }
6098
+ buildProgress() {
6099
+ const gp = this.opts.tracker.getProgress();
6100
+ return {
6101
+ wave: this.decomposer.getWaveCount(),
6102
+ total: gp.total,
6103
+ completed: gp.completed,
6104
+ inProgress: gp.inProgress,
6105
+ failed: gp.failed,
6106
+ blocked: gp.blocked,
6107
+ pending: gp.pending,
6108
+ percent: gp.percentComplete,
6109
+ deadlocked: false
6110
+ };
6111
+ }
6112
+ };
6113
+
6114
+ export { AISpecBuilder, AutoExecutor, DefaultTaskStore, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, analyzeCriticalPath, createAutoExecutor, getTemplate, listTemplates, renderProgress, renderSpecAnalysis, renderTaskGraph, renderTaskList, templateToMarkdown };
2497
6115
  //# sourceMappingURL=index.js.map
2498
6116
  //# sourceMappingURL=index.js.map