@wrongstack/cli 0.87.0 → 0.89.3

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
2
+ import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
3
3
  import * as path8 from 'path';
4
4
  import { join } from 'path';
5
5
  import * as fsp4 from 'fs/promises';
@@ -11,10 +11,11 @@ import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
14
+ import { spawn } from 'child_process';
14
15
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
15
16
  import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
16
17
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
17
- import { builtinToolsPack, rememberTool, forgetTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
18
+ import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
18
19
  import { fileURLToPath } from 'url';
19
20
  import * as readline from 'readline';
20
21
  import * as fs12 from 'fs';
@@ -22,15 +23,12 @@ import { writeFileSync, existsSync, readFileSync } from 'fs';
22
23
  import { WrongStackACPServer } from '@wrongstack/acp/agent';
23
24
  import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
24
25
  import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
25
- import { spawn } from 'child_process';
26
26
  import { allServers } from '@wrongstack/core/infrastructure';
27
27
  import { ToolExecutor } from '@wrongstack/core/execution';
28
28
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
29
29
 
30
30
  var __defProp = Object.defineProperty;
31
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
32
31
  var __getOwnPropNames = Object.getOwnPropertyNames;
33
- var __hasOwnProp = Object.prototype.hasOwnProperty;
34
32
  var __esm = (fn, res) => function __init() {
35
33
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
36
34
  };
@@ -38,15 +36,6 @@ var __export = (target, all) => {
38
36
  for (var name in all)
39
37
  __defProp(target, name, { get: all[name], enumerable: true });
40
38
  };
41
- var __copyProps = (to, from, except, desc) => {
42
- if (from && typeof from === "object" || typeof from === "function") {
43
- for (let key of __getOwnPropNames(from))
44
- if (!__hasOwnProp.call(to, key) && key !== except)
45
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
- }
47
- return to;
48
- };
49
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
50
39
  function getSessionState(ctx) {
51
40
  if (!ctx) return sddState;
52
41
  let state = ctx.meta[SDD_META_KEY];
@@ -132,12 +121,6 @@ var init_state = __esm({
132
121
  sddState = new SDDState();
133
122
  }
134
123
  });
135
- function expectDefined3(value) {
136
- if (value === null || value === void 0) {
137
- throw new Error("Expected value to be defined");
138
- }
139
- return value;
140
- }
141
124
  function formatElapsed(ms) {
142
125
  if (ms < 1e3) return `${ms}ms`;
143
126
  const s = Math.floor(ms / 1e3);
@@ -201,7 +184,7 @@ function getCurrentTask() {
201
184
  if (!tracker) return null;
202
185
  const nodes = tracker.getAllNodes({ status: ["in_progress"] });
203
186
  if (nodes.length === 0) return null;
204
- const n = expectDefined3(nodes[0]);
187
+ const n = expectDefined(nodes[0]);
205
188
  return { id: n.id, title: n.title, description: n.description, priority: n.priority, estimateHours: n.estimateHours ?? 0, tags: n.tags ?? [], startedAt: n.startedAt };
206
189
  }
207
190
  function advanceToNextTask() {
@@ -240,7 +223,7 @@ function renderTaskListWithProgress() {
240
223
  return (order[a.status] ?? 6) - (order[b.status] ?? 6);
241
224
  });
242
225
  for (let i = 0; i < sorted.length; i++) {
243
- const n = expectDefined3(sorted[i]);
226
+ const n = expectDefined(sorted[i]);
244
227
  const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
245
228
  const title = n.title.length > 50 ? n.title.slice(0, 49) + "\u2026" : n.title;
246
229
  let elapsed = "";
@@ -254,7 +237,7 @@ function getCurrentExecutingContext() {
254
237
  if (!tracker) return null;
255
238
  const nodes = tracker.getAllNodes({ status: ["in_progress"] });
256
239
  if (nodes.length === 0) return null;
257
- const n = expectDefined3(nodes[0]);
240
+ const n = expectDefined(nodes[0]);
258
241
  const elapsed = n.startedAt ? ` \xB7 elapsed: ${formatElapsed(Date.now() - n.startedAt)}` : "";
259
242
  const progress = tracker.getProgress();
260
243
  return [
@@ -562,12 +545,6 @@ __export(sdd_exports, {
562
545
  trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
563
546
  trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
564
547
  });
565
- function expectDefined4(value) {
566
- if (value === null || value === void 0) {
567
- throw new Error("Expected value to be defined");
568
- }
569
- return value;
570
- }
571
548
  function getTaskTracker() {
572
549
  return getTaskTrackerExport();
573
550
  }
@@ -633,7 +610,7 @@ function buildSddCommand(opts) {
633
610
  }));
634
611
  sddState.setSessionStartTime(Date.now());
635
612
  sddState.setPhaseStartTime(Date.now());
636
- const builder = expectDefined4(sddState.getBuilder());
613
+ const builder = expectDefined(sddState.getBuilder());
637
614
  builder.startSession(title);
638
615
  const aiPrompt = builder.getAIPrompt();
639
616
  return {
@@ -875,7 +852,7 @@ Start executing the tasks one by one.`
875
852
  return (order[a.status] ?? 6) - (order[b.status] ?? 6);
876
853
  });
877
854
  for (let i = 0; i < sorted.length; i++) {
878
- const n = expectDefined4(sorted[i]);
855
+ const n = expectDefined(sorted[i]);
879
856
  const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
880
857
  const num = `${i + 1}`.padStart(3);
881
858
  const prio = n.priority.slice(0, 4).padEnd(5);
@@ -883,7 +860,7 @@ Start executing the tasks one by one.`
883
860
  const elapsed = n.status === "in_progress" && n.startedAt ? ` (${formatElapsed(Date.now() - n.startedAt)})` : "";
884
861
  lines.push(` ${num} ${status} ${prio} ${title}${elapsed}`);
885
862
  if (n.description && n.status !== "completed") {
886
- const first = expectDefined4(n.description.split("\n")[0]);
863
+ const first = expectDefined(n.description.split("\n")[0]);
887
864
  const truncated = first.length > 42 ? first.slice(0, 41) + "\u2026" : first;
888
865
  lines.push(` \u21B3 ${truncated}`);
889
866
  }
@@ -1050,7 +1027,7 @@ Start executing the tasks one by one.`
1050
1027
  if (completed.length === 0) {
1051
1028
  return { message: "No completed tasks to undo." };
1052
1029
  }
1053
- const last = expectDefined4(completed[completed.length - 1]);
1030
+ const last = expectDefined(completed[completed.length - 1]);
1054
1031
  undoTracker.updateNodeStatus(last.id, "pending");
1055
1032
  const progress = undoTracker.getProgress();
1056
1033
  return {
@@ -1100,7 +1077,7 @@ Start executing the tasks one by one.`
1100
1077
  ` \u{1F504} ${next.title}`
1101
1078
  ];
1102
1079
  if (next.description) {
1103
- const first = expectDefined4(next.description.split("\n")[0]);
1080
+ const first = expectDefined(next.description.split("\n")[0]);
1104
1081
  lines.push(` \u21B3 ${first}`);
1105
1082
  }
1106
1083
  const taskElapsed = next.startedAt ? ` \u23F1 ${formatElapsed(Date.now() - next.startedAt)}` : "";
@@ -1212,7 +1189,7 @@ Start executing the tasks one by one.`
1212
1189
  return (order[a.status] ?? 6) - (order[b.status] ?? 6);
1213
1190
  });
1214
1191
  for (let i = 0; i < sorted2.length; i++) {
1215
- const n = expectDefined4(sorted2[i]);
1192
+ const n = expectDefined(sorted2[i]);
1216
1193
  const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
1217
1194
  lines2.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
1218
1195
  }
@@ -1237,7 +1214,7 @@ Start executing the tasks one by one.`
1237
1214
  return (order[a.status] ?? 6) - (order[b.status] ?? 6);
1238
1215
  });
1239
1216
  for (let i = 0; i < sorted.length; i++) {
1240
- const n = expectDefined4(sorted[i]);
1217
+ const n = expectDefined(sorted[i]);
1241
1218
  const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : n.status === "blocked" ? "\u{1F6AB}" : n.status === "review" ? "\u{1F441}" : "\u23F3";
1242
1219
  lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
1243
1220
  }
@@ -1285,7 +1262,7 @@ Start executing the tasks one by one.`
1285
1262
  maxQuestions: 10,
1286
1263
  sessionPath
1287
1264
  }));
1288
- const resumeBuilder = expectDefined4(sddState.getBuilder());
1265
+ const resumeBuilder = expectDefined(sddState.getBuilder());
1289
1266
  const loaded = await resumeBuilder.loadSession();
1290
1267
  if (!loaded) {
1291
1268
  sddState.setBuilder(null);
@@ -1519,12 +1496,6 @@ var init_sdd = __esm({
1519
1496
  init_rendering();
1520
1497
  }
1521
1498
  });
1522
- function expectDefined7(value) {
1523
- if (value === null || value === void 0) {
1524
- throw new Error("Expected value to be defined");
1525
- }
1526
- return value;
1527
- }
1528
1499
  function normalizeKeys(cfg) {
1529
1500
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
1530
1501
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -1542,7 +1513,7 @@ function writeKeysBack(cfg, keys) {
1542
1513
  return;
1543
1514
  }
1544
1515
  cfg.apiKeys = keys;
1545
- const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined7(keys[0]);
1516
+ const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
1546
1517
  cfg.apiKey = active.apiKey;
1547
1518
  if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
1548
1519
  cfg.activeKey = active.label;
@@ -2337,7 +2308,7 @@ async function runWebUI(opts) {
2337
2308
  const keys = normalizeKeys(existing);
2338
2309
  const existingIdx = keys.findIndex((k) => k.label === label);
2339
2310
  if (existingIdx >= 0) {
2340
- keys[existingIdx] = { ...expectDefined7(keys[existingIdx]), apiKey, createdAt: nowIso() };
2311
+ keys[existingIdx] = { ...expectDefined(keys[existingIdx]), apiKey, createdAt: nowIso() };
2341
2312
  } else {
2342
2313
  keys.push({ label, apiKey, createdAt: nowIso() });
2343
2314
  }
@@ -2471,14 +2442,6 @@ try {
2471
2442
  if (corePkg.wrongstackApiVersion) API_VERSION = corePkg.wrongstackApiVersion;
2472
2443
  } catch {
2473
2444
  }
2474
-
2475
- // src/slash-commands/commit-llm.ts
2476
- function expectDefined(value) {
2477
- if (value === null || value === void 0) {
2478
- throw new Error("Expected value to be defined");
2479
- }
2480
- return value;
2481
- }
2482
2445
  async function generateCommitMessageWithLLM(diff, opts) {
2483
2446
  const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
2484
2447
  const userPrompt = `Here is the git diff:
@@ -5424,17 +5387,11 @@ No project type auto-detected. Edit the file with project context and instructio
5424
5387
  }
5425
5388
  };
5426
5389
  }
5427
- function expectDefined2(value) {
5428
- if (value === null || value === void 0) {
5429
- throw new Error("Expected value to be defined");
5430
- }
5431
- return value;
5432
- }
5433
5390
  function parseMcpArgs(args) {
5434
5391
  const trimmed = args.trim();
5435
5392
  if (!trimmed || trimmed === "list") return { action: "list", name: "" };
5436
5393
  const parts = trimmed.split(/\s+/);
5437
- const action = expectDefined2(parts[0]);
5394
+ const action = expectDefined(parts[0]);
5438
5395
  const name = parts[1] ?? "";
5439
5396
  const enable = parts.includes("--enable") || parts.includes("-e");
5440
5397
  switch (action) {
@@ -5681,7 +5638,7 @@ function buildMemoryCommand(opts) {
5681
5638
  return {
5682
5639
  name: "memory",
5683
5640
  category: "Inspect",
5684
- description: "Inspect or edit persistent memory: /memory [show|remember <text>|forget <query>|clear]",
5641
+ description: "Inspect or edit persistent memory: /memory [show|remember <text>|forget <query>|clear|compact|stats]",
5685
5642
  async run(args) {
5686
5643
  const store = opts.memoryStore;
5687
5644
  if (!store) return { message: "No memory store configured." };
@@ -5714,14 +5671,350 @@ function buildMemoryCommand(opts) {
5714
5671
  await store.clear();
5715
5672
  return { message: "Cleared all memory scopes." };
5716
5673
  }
5674
+ case "compact": {
5675
+ return runCompact(opts);
5676
+ }
5677
+ case "stats": {
5678
+ return runStats(opts);
5679
+ }
5717
5680
  default:
5718
5681
  return {
5719
- message: `Unknown subcommand "${verb}". Try: show | remember <text> | forget <query> | clear`
5682
+ message: `Unknown subcommand "${verb}". Try: show | remember <text> | forget <query> | clear | compact | stats`
5720
5683
  };
5721
5684
  }
5722
5685
  }
5723
5686
  };
5724
5687
  }
5688
+ function buildCompactPrompt(entries) {
5689
+ const entriesBlock = entries.map(
5690
+ (e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
5691
+ ${e.text}${e.tags ? `
5692
+ tags: ${e.tags.join(", ")}` : ""}${e.type ? `
5693
+ type: ${e.type}` : ""}${e.priority ? `
5694
+ priority: ${e.priority}` : ""}`
5695
+ ).join("\n\n");
5696
+ return `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
5697
+
5698
+ These entries are injected into the context of an AI coding agent. Every token counts. The memory must be concise, accurate, and free of noise.
5699
+
5700
+ ## Current Memory Entries
5701
+
5702
+ ${entriesBlock}
5703
+
5704
+ ## Your Task
5705
+
5706
+ Review each entry and return a JSON object with an "operations" array. Each operation targets one or more entries:
5707
+
5708
+ ### Actions
5709
+
5710
+ - **"keep"** \u2014 The entry is valuable as-is. Include it in the operations so I know you reviewed it.
5711
+ - **"rewrite"** \u2014 The entry has value but needs better wording. Provide improved "newText". Target a single entry.
5712
+ - **"merge"** \u2014 Two or more entries say essentially the same thing. Combine them into one concise entry. The "targets" should list all entries being merged. Provide the combined "newText".
5713
+ - **"delete"** \u2014 The entry is obsolete, redundant, too vague, or not useful for future sessions. Target one or more entries.
5714
+
5715
+ ### Rules
5716
+
5717
+ 1. **Be ruthless about noise.** If an entry won't help a future AI agent do its job better, delete it.
5718
+ 2. **Deduplicate aggressively.** Similar entries should be merged. Identical entries MUST be merged.
5719
+ 3. **Keep entries concise.** Each entry should be one clear sentence. Remove filler words.
5720
+ 4. **Preserve factual accuracy.** Don't change the meaning of entries unless they're wrong.
5721
+ 5. **Handle every entry.** Every entry must appear in at least one operation (keep, rewrite, merge, or delete).
5722
+ 6. **Prefer quality over quantity.** 10 excellent entries > 30 mediocre ones.
5723
+ 7. **Tag entries appropriately.** If an entry mentions a technology or concept that could be tagged, suggest tags in the newText using #hashtag syntax.
5724
+
5725
+ ### Response Format
5726
+
5727
+ Return ONLY valid JSON with this structure:
5728
+
5729
+ {
5730
+ "operations": [
5731
+ { "action": "keep", "targets": ["mem_1234_abcd"], "reason": "Clear and useful" },
5732
+ { "action": "rewrite", "targets": ["mem_5678_ef01"], "newText": "Project uses pnpm v9 with ESM-only modules #pnpm #esm", "reason": "Added version and ESM detail" },
5733
+ { "action": "merge", "targets": ["mem_aaaa_1111", "mem_bbbb_2222"], "newText": "All packages use TypeScript strict mode with noUncheckedIndexedAccess #typescript", "reason": "Two entries about TS config, merged" },
5734
+ { "action": "delete", "targets": ["mem_cccc_3333"], "reason": "Obsolete \u2014 was a temporary debug note" }
5735
+ ],
5736
+ "summary": "Merged 2 TS entries, rewrote 1 for clarity, deleted 1 obsolete note. 12 entries \u2192 10 entries."
5737
+ }
5738
+
5739
+ Use the EXACT entry IDs from the list above for "targets". No markdown, no explanation outside the JSON.`;
5740
+ }
5741
+ async function runCompact(opts) {
5742
+ const store = opts.memoryStore;
5743
+ if (!store) return { message: "No memory store configured." };
5744
+ const entries = await store.list("project-memory");
5745
+ if (entries.length === 0) {
5746
+ return { message: "Memory is empty \u2014 nothing to compact." };
5747
+ }
5748
+ const raw = await store.read("project-memory");
5749
+ const compactEntries = parseCompactEntries(raw);
5750
+ if (compactEntries.length === 0) {
5751
+ return { message: "No parseable entries found." };
5752
+ }
5753
+ const provider = opts.llmProvider;
5754
+ if (!provider || !provider.complete) {
5755
+ return { message: "No LLM provider available. /memory compact requires an active session with a configured provider." };
5756
+ }
5757
+ const prompt = buildCompactPrompt(compactEntries);
5758
+ let responseText;
5759
+ try {
5760
+ const signal = AbortSignal.timeout(3e4);
5761
+ const response = await provider.complete(
5762
+ {
5763
+ model: opts.llmModel ?? "",
5764
+ system: [{ type: "text", text: prompt }],
5765
+ messages: [
5766
+ {
5767
+ role: "user",
5768
+ content: `Review the ${compactEntries.length} memory entries above and return operations as JSON.`
5769
+ }
5770
+ ],
5771
+ maxTokens: 2e3,
5772
+ temperature: 0.1
5773
+ // low temperature for deterministic curation
5774
+ },
5775
+ { signal }
5776
+ );
5777
+ responseText = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
5778
+ } catch (err) {
5779
+ return {
5780
+ message: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`
5781
+ };
5782
+ }
5783
+ if (!responseText) {
5784
+ return { message: "LLM returned empty response." };
5785
+ }
5786
+ let parsed;
5787
+ try {
5788
+ const jsonMatch = responseText.match(/\{[\s\S]*\}/);
5789
+ if (!jsonMatch) {
5790
+ return { message: `LLM response is not valid JSON:
5791
+ ${responseText.slice(0, 500)}` };
5792
+ }
5793
+ parsed = JSON.parse(jsonMatch[0]);
5794
+ } catch (err) {
5795
+ return {
5796
+ message: `Failed to parse LLM response: ${err instanceof Error ? err.message : String(err)}
5797
+
5798
+ Raw response:
5799
+ ${responseText.slice(0, 500)}`
5800
+ };
5801
+ }
5802
+ if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) {
5803
+ return { message: "LLM returned no operations." };
5804
+ }
5805
+ let kept = 0;
5806
+ let rewritten = 0;
5807
+ let merged = 0;
5808
+ let deleted = 0;
5809
+ const errors = [];
5810
+ for (const op of parsed.operations) {
5811
+ try {
5812
+ switch (op.action) {
5813
+ case "keep": {
5814
+ kept += op.targets.length;
5815
+ break;
5816
+ }
5817
+ case "rewrite": {
5818
+ if (!op.newText) {
5819
+ errors.push(`rewrite missing newText for targets: ${op.targets.join(", ")}`);
5820
+ continue;
5821
+ }
5822
+ for (const target of op.targets) {
5823
+ await store.forget(target);
5824
+ }
5825
+ await store.remember(op.newText);
5826
+ rewritten++;
5827
+ break;
5828
+ }
5829
+ case "merge": {
5830
+ if (!op.newText) {
5831
+ errors.push(`merge missing newText for targets: ${op.targets.join(", ")}`);
5832
+ continue;
5833
+ }
5834
+ for (const target of op.targets) {
5835
+ await store.forget(target);
5836
+ }
5837
+ await store.remember(op.newText);
5838
+ merged++;
5839
+ break;
5840
+ }
5841
+ case "delete": {
5842
+ for (const target of op.targets) {
5843
+ await store.forget(target);
5844
+ }
5845
+ deleted += op.targets.length;
5846
+ break;
5847
+ }
5848
+ default: {
5849
+ errors.push(`unknown action "${op.action}"`);
5850
+ }
5851
+ }
5852
+ } catch (err) {
5853
+ errors.push(
5854
+ `${op.action} failed for ${op.targets.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
5855
+ );
5856
+ }
5857
+ }
5858
+ const lines = ["## Memory Compact \u2014 Complete"];
5859
+ const stats = [];
5860
+ if (kept > 0) stats.push(`${kept} kept`);
5861
+ if (rewritten > 0) stats.push(`${rewritten} rewritten`);
5862
+ if (merged > 0) stats.push(`${merged} merged`);
5863
+ if (deleted > 0) stats.push(`${deleted} deleted`);
5864
+ lines.push(`**Result:** ${stats.join(", ")}`);
5865
+ lines.push(`**Before:** ${compactEntries.length} entries \u2192 **After:** ${kept + rewritten + merged} entries`);
5866
+ if (parsed.summary) {
5867
+ lines.push("");
5868
+ lines.push(parsed.summary);
5869
+ }
5870
+ lines.push("");
5871
+ lines.push("### Operations");
5872
+ for (const op of parsed.operations) {
5873
+ const icon = op.action === "keep" ? "\u2713" : op.action === "rewrite" ? "\u270F\uFE0F" : op.action === "merge" ? "\u{1F500}" : op.action === "delete" ? "\u2717" : "?";
5874
+ const detail = op.newText ? ` \u2192 "${op.newText}"` : "";
5875
+ lines.push(`- ${icon} **${op.action}** ${op.targets.join(", ")}${detail}`);
5876
+ if (op.reason) lines.push(` _${op.reason}_`);
5877
+ }
5878
+ if (errors.length > 0) {
5879
+ lines.push("");
5880
+ lines.push("### Errors");
5881
+ for (const err of errors) {
5882
+ lines.push(`- \u26A0\uFE0F ${err}`);
5883
+ }
5884
+ }
5885
+ return { message: lines.join("\n") };
5886
+ }
5887
+ function parseCompactEntries(raw) {
5888
+ const entries = [];
5889
+ for (const line of raw.split("\n")) {
5890
+ const trimmed = line.trim();
5891
+ if (!trimmed.startsWith("- [")) continue;
5892
+ const idMatch = trimmed.match(/mem_(\d+_\w+)/);
5893
+ if (!idMatch) continue;
5894
+ const id = idMatch[0] ?? "";
5895
+ const afterId = trimmed.slice((idMatch.index ?? 0) + id.length).trim();
5896
+ const tsMatch = trimmed.match(/^-\s*\[([^\]]+)\]/);
5897
+ const ts = tsMatch?.[1] ?? "";
5898
+ const tags = [];
5899
+ const tagRe = /#([\w-]+)/g;
5900
+ let tm;
5901
+ while ((tm = tagRe.exec(afterId)) !== null) {
5902
+ tags.push(tm[1] ?? "");
5903
+ }
5904
+ const text = afterId.replace(tagRe, "").replace(/\s{2,}/g, " ").trim();
5905
+ if (!text) continue;
5906
+ entries.push({ id, text, ts, tags: tags.length > 0 ? tags : void 0 });
5907
+ }
5908
+ return entries;
5909
+ }
5910
+ async function runStats(opts) {
5911
+ const store = opts.memoryStore;
5912
+ if (!store) return { message: "No memory store configured." };
5913
+ const entries = await store.list("project-memory");
5914
+ if (entries.length === 0) {
5915
+ return { message: "\u{1F4CA} Memory is empty. Start adding entries with `/memory remember <text>`." };
5916
+ }
5917
+ const now = Date.now();
5918
+ const lines = ["## \u{1F4CA} Memory Stats"];
5919
+ const raw = await store.read("project-memory");
5920
+ const byteSize = Buffer.byteLength(raw, "utf8");
5921
+ const kbSize = (byteSize / 1024).toFixed(1);
5922
+ const maxKb = (32e3 / 1024).toFixed(1);
5923
+ const pctFull = (byteSize / 32e3 * 100).toFixed(0);
5924
+ lines.push(`**Total:** ${entries.length} entries \xB7 ${kbSize} KB / ${maxKb} KB (${pctFull}%)`);
5925
+ const byType = /* @__PURE__ */ new Map();
5926
+ for (const e of entries) {
5927
+ const t = e.type ?? "untyped";
5928
+ byType.set(t, (byType.get(t) ?? 0) + 1);
5929
+ }
5930
+ if (byType.size > 0) {
5931
+ lines.push("");
5932
+ lines.push("### By Type");
5933
+ const typeOrder = ["convention", "decision", "fact", "preference", "reference", "anti_pattern", "untyped"];
5934
+ for (const t of typeOrder) {
5935
+ const count = byType.get(t);
5936
+ if (count) {
5937
+ const bar = "\u2588".repeat(Math.min(count, 20));
5938
+ lines.push(`- \`${t}\` ${bar} ${count}`);
5939
+ }
5940
+ }
5941
+ }
5942
+ const byPriority = /* @__PURE__ */ new Map();
5943
+ for (const e of entries) {
5944
+ const p = e.priority ?? "unset";
5945
+ byPriority.set(p, (byPriority.get(p) ?? 0) + 1);
5946
+ }
5947
+ if (byPriority.size > 0) {
5948
+ lines.push("");
5949
+ lines.push("### By Priority");
5950
+ const icon = { critical: "\u26A1", high: "\u25B2", medium: "\u25CF", low: "\u25CB", unset: "\xB7" };
5951
+ for (const [p, count] of [...byPriority.entries()].sort((a, b) => b[1] - a[1])) {
5952
+ lines.push(`- ${icon[p] ?? "\xB7"} \`${p}\`: ${count}`);
5953
+ }
5954
+ }
5955
+ const ages = entries.map((e) => {
5956
+ const ageDays = (now - new Date(e.ts).getTime()) / (1e3 * 60 * 60 * 24);
5957
+ if (ageDays < 1) return "<1d";
5958
+ if (ageDays < 7) return "<7d";
5959
+ if (ageDays < 30) return "<30d";
5960
+ return ">30d";
5961
+ });
5962
+ const byAge = /* @__PURE__ */ new Map();
5963
+ for (const a of ages) byAge.set(a, (byAge.get(a) ?? 0) + 1);
5964
+ lines.push("");
5965
+ lines.push("### By Age");
5966
+ for (const age of ["<1d", "<7d", "<30d", ">30d"]) {
5967
+ const actual = byAge.get(age) ?? 0;
5968
+ if (actual > 0 || age === "<7d") {
5969
+ lines.push(`- ${age}: ${actual}`);
5970
+ }
5971
+ }
5972
+ const tagCounts = /* @__PURE__ */ new Map();
5973
+ for (const e of entries) {
5974
+ for (const t of e.tags ?? []) {
5975
+ tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
5976
+ }
5977
+ }
5978
+ if (tagCounts.size > 0) {
5979
+ lines.push("");
5980
+ lines.push("### Top Tags");
5981
+ const sorted = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
5982
+ for (const [tag, count] of sorted) {
5983
+ lines.push(`- \`#${tag}\`: ${count}`);
5984
+ }
5985
+ }
5986
+ lines.push("");
5987
+ lines.push("### Health");
5988
+ const untyped = byType.get("untyped") ?? 0;
5989
+ const unsetPriority = byPriority.get("unset") ?? 0;
5990
+ const old = byAge.get(">30d") ?? 0;
5991
+ if (untyped > entries.length * 0.5) {
5992
+ lines.push(`- \u26A0\uFE0F ${untyped}/${entries.length} entries have no type \u2014 run \`/memory compact\` to categorize`);
5993
+ } else if (untyped > 0) {
5994
+ lines.push(`- \u2139\uFE0F ${untyped} entries untyped \u2014 consider categorizing`);
5995
+ } else {
5996
+ lines.push("- \u2705 All entries have types");
5997
+ }
5998
+ if (unsetPriority > entries.length * 0.5) {
5999
+ lines.push(`- \u26A0\uFE0F ${unsetPriority}/${entries.length} entries have no priority`);
6000
+ } else if (unsetPriority > 0) {
6001
+ lines.push(`- \u2139\uFE0F ${unsetPriority} entries have no priority set`);
6002
+ } else {
6003
+ lines.push("- \u2705 All entries have priorities");
6004
+ }
6005
+ if (old > 5) {
6006
+ lines.push(`- \u26A0\uFE0F ${old} entries older than 30 days \u2014 run \`/memory compact\` to review`);
6007
+ }
6008
+ const pct2 = Number.parseInt(pctFull);
6009
+ if (pct2 > 80) {
6010
+ lines.push(`- \u26A0\uFE0F Storage ${pct2}% full \u2014 run \`/memory compact\` to free space`);
6011
+ } else {
6012
+ lines.push(`- \u2705 Storage ${pct2}% full \u2014 healthy`);
6013
+ }
6014
+ lines.push("");
6015
+ lines.push("**Commands:** `/memory show` \xB7 `/memory compact` \xB7 `/memory remember <text>`");
6016
+ return { message: lines.join("\n") };
6017
+ }
5725
6018
  async function runModePicker(modeStore, reader) {
5726
6019
  const modes = await modeStore.listModes();
5727
6020
  const active = await modeStore.getActiveMode();
@@ -6397,12 +6690,6 @@ function summariseEvent(ev) {
6397
6690
  return color.dim("\u2026");
6398
6691
  }
6399
6692
  }
6400
- function expectDefined5(value) {
6401
- if (value === null || value === void 0) {
6402
- throw new Error("Expected value to be defined");
6403
- }
6404
- return value;
6405
- }
6406
6693
  var noOpVault3 = {
6407
6694
  encrypt: (v) => v,
6408
6695
  decrypt: (v) => v,
@@ -6495,7 +6782,7 @@ function buildSetModelCommand(opts) {
6495
6782
  for (const k of keys.sort()) {
6496
6783
  const kind = matrixKeyKind(k);
6497
6784
  const tag = kind === "unknown" ? color.red("?") : color.dim(kind);
6498
- lines.push(` ${color.amber(k.padEnd(22))} \u2192 ${fmtEntry(expectDefined5(matrix[k]))} ${tag}`);
6785
+ lines.push(` ${color.amber(k.padEnd(22))} \u2192 ${fmtEntry(expectDefined(matrix[k]))} ${tag}`);
6499
6786
  }
6500
6787
  }
6501
6788
  lines.push("", color.dim(" /setmodel list for valid keys \xB7 /setmodel help for usage"));
@@ -7431,9 +7718,9 @@ async function runProjectCheck(opts) {
7431
7718
  }
7432
7719
  if (answer2 === "y" || answer2 === "yes") {
7433
7720
  try {
7434
- const { spawn: spawn3 } = await import('child_process');
7721
+ const { spawn: spawn4 } = await import('child_process');
7435
7722
  await new Promise((resolve5, reject) => {
7436
- const child = spawn3("git", ["init"], { cwd });
7723
+ const child = spawn4("git", ["init"], { cwd, signal: AbortSignal.timeout(1e4) });
7437
7724
  child.on("error", reject);
7438
7725
  child.on("close", (code) => code === 0 ? resolve5() : reject(new Error(`git init failed with ${code}`)));
7439
7726
  });
@@ -8139,20 +8426,14 @@ async function restoreLast(homeFn = defaultHomeDir) {
8139
8426
  }
8140
8427
 
8141
8428
  // src/picker.ts
8142
- function expectDefined6(value) {
8143
- if (value === null || value === void 0) {
8144
- throw new Error("Expected value to be defined");
8145
- }
8146
- return value;
8147
- }
8148
8429
  var theme = { primary: color.amber };
8149
8430
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
8150
8431
  try {
8151
8432
  const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
8152
- const fs26 = await import('fs/promises');
8433
+ const fs27 = await import('fs/promises');
8153
8434
  let existing = {};
8154
8435
  try {
8155
- const raw = await fs26.readFile(configPath2, "utf8");
8436
+ const raw = await fs27.readFile(configPath2, "utf8");
8156
8437
  existing = JSON.parse(raw);
8157
8438
  } catch {
8158
8439
  }
@@ -8340,7 +8621,7 @@ async function pickModel(provider, registry, renderer, reader, defaultModel) {
8340
8621
  while (offset < models.length) {
8341
8622
  const page = models.slice(offset, offset + pageSize);
8342
8623
  for (let i = 0; i < page.length; i++) {
8343
- const m = expectDefined6(page[i]);
8624
+ const m = expectDefined(page[i]);
8344
8625
  const num = offset + i + 1;
8345
8626
  const ctx = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k`.padStart(6) : " ?";
8346
8627
  const cost = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
@@ -9024,7 +9305,7 @@ ${color.amber("?")} Pick: `)).trim().toLowerCase();
9024
9305
  }
9025
9306
  const idx = Number.parseInt(choice, 10);
9026
9307
  if (!Number.isNaN(idx) && idx >= 1 && idx <= ids.length) {
9027
- const pid = expectDefined7(ids[idx - 1]);
9308
+ const pid = expectDefined(ids[idx - 1]);
9028
9309
  await manageProvider(pid, deps);
9029
9310
  continue;
9030
9311
  }
@@ -9114,7 +9395,7 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
9114
9395
  deps.renderer.write(color.dim(" (no keys saved)\n"));
9115
9396
  } else {
9116
9397
  for (let i = 0; i < keys.length; i++) {
9117
- const k = expectDefined7(keys[i]);
9398
+ const k = expectDefined(keys[i]);
9118
9399
  const marker = k.label === active ? color.green("\u25CF") : color.dim("\u25CB");
9119
9400
  deps.renderer.write(
9120
9401
  ` ${color.dim(`${i + 1}.`.padStart(4))} ${marker} ${k.label.padEnd(20)} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}
@@ -9176,7 +9457,7 @@ ${color.amber("?")} ${providerId} > `)).trim();
9176
9457
  deps.renderer.writeError(`Usage: u <1-${keys.length}>`);
9177
9458
  continue;
9178
9459
  }
9179
- const target = expectDefined7(keys[arg - 1]);
9460
+ const target = expectDefined(keys[arg - 1]);
9180
9461
  const newKey = await readKeyInput(deps, `Updated key for ${target.label}`);
9181
9462
  if (!newKey) continue;
9182
9463
  await mutateProviders(deps, (all) => {
@@ -9196,7 +9477,7 @@ ${color.amber("?")} ${providerId} > `)).trim();
9196
9477
  deps.renderer.writeError(`Usage: d <1-${keys.length}>`);
9197
9478
  continue;
9198
9479
  }
9199
- const target = expectDefined7(keys[arg - 1]);
9480
+ const target = expectDefined(keys[arg - 1]);
9200
9481
  const confirm = (await deps.reader.readLine(
9201
9482
  ` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N/q]")} `
9202
9483
  )).trim().toLowerCase();
@@ -9272,7 +9553,7 @@ ${color.amber("?")} ${providerId} > `)).trim();
9272
9553
  deps.renderer.writeError(`Usage: s <1-${keys.length}>`);
9273
9554
  continue;
9274
9555
  }
9275
- const target = expectDefined7(keys[arg - 1]);
9556
+ const target = expectDefined(keys[arg - 1]);
9276
9557
  await mutateProviders(deps, (all) => {
9277
9558
  const p = all[providerId];
9278
9559
  if (!p) return;
@@ -9605,12 +9886,6 @@ function mutateProviders(deps, mutator) {
9605
9886
  }
9606
9887
 
9607
9888
  // src/subcommands/handlers/auth.ts
9608
- function expectDefined8(value) {
9609
- if (value === null || value === void 0) {
9610
- throw new Error("Expected value to be defined");
9611
- }
9612
- return value;
9613
- }
9614
9889
  var authCmd = async (args, deps) => {
9615
9890
  const flags = parseAuthFlags(args);
9616
9891
  const menuDeps = {
@@ -9622,7 +9897,7 @@ var authCmd = async (args, deps) => {
9622
9897
  };
9623
9898
  if (flags.positional.length === 0) return runAuthMenu(menuDeps);
9624
9899
  return runAuthDirect(menuDeps, {
9625
- providerId: expectDefined8(flags.positional[0]),
9900
+ providerId: expectDefined(flags.positional[0]),
9626
9901
  label: flags.label,
9627
9902
  family: flags.family,
9628
9903
  baseUrl: flags.baseUrl,
@@ -9658,7 +9933,8 @@ var updateCmd = async (args, deps) => {
9658
9933
  const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
9659
9934
  const child = spawn(npmCommand, ["install", "-g", "wrongstack@latest"], {
9660
9935
  cwd,
9661
- stdio: "pipe"
9936
+ stdio: "pipe",
9937
+ signal: AbortSignal.timeout(12e4)
9662
9938
  });
9663
9939
  let _stderr = "";
9664
9940
  child.stderr?.on("data", (d) => {
@@ -9845,12 +10121,6 @@ var doctorCmd = async (_args, deps) => {
9845
10121
  deps.renderer.write(color.green("All checks passed.\n"));
9846
10122
  return 0;
9847
10123
  };
9848
- function expectDefined9(value) {
9849
- if (value === null || value === void 0) {
9850
- throw new Error("Expected value to be defined");
9851
- }
9852
- return value;
9853
- }
9854
10124
  var exportCmd = async (args, deps) => {
9855
10125
  if (!deps.sessionStore) {
9856
10126
  deps.renderer.writeError("No session store configured.");
@@ -9862,7 +10132,7 @@ var exportCmd = async (args, deps) => {
9862
10132
  let includeDiagnostics = true;
9863
10133
  let sessionId;
9864
10134
  for (let i = 0; i < args.length; i++) {
9865
- const a = expectDefined9(args[i]);
10135
+ const a = expectDefined(args[i]);
9866
10136
  if (a === "--format" || a === "-f") {
9867
10137
  const v = args[++i];
9868
10138
  if (v !== "markdown" && v !== "json" && v !== "text") {
@@ -10124,12 +10394,6 @@ async function serveMcpStdio(deps) {
10124
10394
  }
10125
10395
 
10126
10396
  // src/subcommands/handlers/mcp.ts
10127
- function expectDefined10(value) {
10128
- if (value === null || value === void 0) {
10129
- throw new Error("Expected value to be defined");
10130
- }
10131
- return value;
10132
- }
10133
10397
  var BUILT_IN_MCP = allServers();
10134
10398
  var mcpCmd = async (args, deps) => {
10135
10399
  const sub = args[0];
@@ -10180,7 +10444,7 @@ async function addMcpServer(args, deps) {
10180
10444
  `);
10181
10445
  if (Object.keys(deps.config.mcpServers ?? {}).length === 0)
10182
10446
  for (const k of Object.keys(BUILT_IN_MCP)) {
10183
- const s = expectDefined10(BUILT_IN_MCP[k]);
10447
+ const s = expectDefined(BUILT_IN_MCP[k]);
10184
10448
  deps.renderer.write(` ${k.padEnd(20)} ${s.description}
10185
10449
  `);
10186
10450
  }
@@ -10458,12 +10722,6 @@ var projectsCmd = async (_args, deps) => {
10458
10722
  return 0;
10459
10723
  }
10460
10724
  };
10461
- function expectDefined11(value) {
10462
- if (value === null || value === void 0) {
10463
- throw new Error("Expected value to be defined");
10464
- }
10465
- return value;
10466
- }
10467
10725
  var providersCmd = async (args, deps) => {
10468
10726
  const showAll = args.includes("--all");
10469
10727
  const showUnsupported = args.includes("--unsupported");
@@ -10511,7 +10769,7 @@ ${color.dim(`Current: ${deps.config.provider ?? "<unset>"} / ${deps.config.model
10511
10769
  function parseFlags2(args) {
10512
10770
  const flags = {};
10513
10771
  for (let i = 0; i < args.length; i++) {
10514
- const a = expectDefined11(args[i]);
10772
+ const a = expectDefined(args[i]);
10515
10773
  if (a.startsWith("--")) {
10516
10774
  const eq = a.indexOf("=");
10517
10775
  if (eq !== -1) {
@@ -10531,7 +10789,7 @@ function parseFlags2(args) {
10531
10789
  function positionals(args) {
10532
10790
  const out = [];
10533
10791
  for (let i = 0; i < args.length; i++) {
10534
- const a = expectDefined11(args[i]);
10792
+ const a = expectDefined(args[i]);
10535
10793
  if (a.startsWith("--")) {
10536
10794
  const eq = a.indexOf("=");
10537
10795
  if (eq === -1) {
@@ -10686,7 +10944,7 @@ function parseSizeFlag(raw) {
10686
10944
  const s = raw.trim().toLowerCase();
10687
10945
  const match = /^(\d+(?:\.\d+)?)\s*(k|m|b)?$/.exec(s);
10688
10946
  if (!match) return void 0;
10689
- const num = Number.parseFloat(expectDefined11(match[1]));
10947
+ const num = Number.parseFloat(expectDefined(match[1]));
10690
10948
  const unit = match[2];
10691
10949
  if (unit === "b") return Math.round(num * 1e9);
10692
10950
  if (unit === "m") return Math.round(num * 1e6);
@@ -11007,12 +11265,6 @@ Fleet Run: ${runId}
11007
11265
  }
11008
11266
 
11009
11267
  // src/subcommands/handlers/sessions-config.ts
11010
- function expectDefined12(value) {
11011
- if (value === null || value === void 0) {
11012
- throw new Error("Expected value to be defined");
11013
- }
11014
- return value;
11015
- }
11016
11268
  var sessionsCmd = async (args, deps) => {
11017
11269
  const sub = args[0];
11018
11270
  if (sub === "fleet") {
@@ -11058,7 +11310,7 @@ var configCmd = async (args, deps) => {
11058
11310
  };
11059
11311
  function extractArg(args, key) {
11060
11312
  const idx = args.indexOf(key);
11061
- if (idx !== -1 && args[idx + 1] !== void 0) return expectDefined12(args[idx + 1]);
11313
+ if (idx !== -1 && args[idx + 1] !== void 0) return expectDefined(args[idx + 1]);
11062
11314
  const eq = key.startsWith("--") ? args.find((a) => a.startsWith(`${key}=`)) : null;
11063
11315
  if (eq) return eq.slice(eq.indexOf("=") + 1);
11064
11316
  return null;
@@ -11134,12 +11386,6 @@ async function runRestore(args, deps) {
11134
11386
  `);
11135
11387
  return 0;
11136
11388
  }
11137
- function expectDefined13(value) {
11138
- if (value === null || value === void 0) {
11139
- throw new Error("Expected value to be defined");
11140
- }
11141
- return value;
11142
- }
11143
11389
  function parseRewindFlags(args) {
11144
11390
  const flags = {};
11145
11391
  for (let i = 0; i < args.length; i++) {
@@ -11154,7 +11400,7 @@ function parseRewindFlags(args) {
11154
11400
  }
11155
11401
  function findSessionId(args) {
11156
11402
  for (let i = 0; i < args.length; i++) {
11157
- const a = expectDefined13(args[i]);
11403
+ const a = expectDefined(args[i]);
11158
11404
  if (a === "--last" || a === "--to") {
11159
11405
  i++;
11160
11406
  continue;
@@ -11406,10 +11652,10 @@ var auditCmd = async (args, deps) => {
11406
11652
  return verify.ok ? 0 : 1;
11407
11653
  };
11408
11654
  async function listAudits(log, dir, deps) {
11409
- const fs26 = await import('fs/promises');
11655
+ const fs27 = await import('fs/promises');
11410
11656
  let entries;
11411
11657
  try {
11412
- entries = await fs26.readdir(dir);
11658
+ entries = await fs27.readdir(dir);
11413
11659
  } catch {
11414
11660
  deps.renderer.write(
11415
11661
  color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
@@ -11560,22 +11806,22 @@ function fmtDuration(ms) {
11560
11806
  const remMin = m - h * 60;
11561
11807
  return `${h}h${remMin}m`;
11562
11808
  }
11563
- function fmtTaskResultLine(r, color52) {
11809
+ function fmtTaskResultLine(r, color51) {
11564
11810
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
11565
11811
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
11566
11812
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
11567
11813
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
11568
- const errKindChip = errKind ? color52.dim(` [${errKind}]`) : "";
11569
- const errSnip = errMsg || errKind ? `${errKindChip}${color52.dim(errTail)}` : "";
11814
+ const errKindChip = errKind ? color51.dim(` [${errKind}]`) : "";
11815
+ const errSnip = errMsg || errKind ? `${errKindChip}${color51.dim(errTail)}` : "";
11570
11816
  switch (r.status) {
11571
11817
  case "success":
11572
- return { mark: color52.green("\u2713"), stats, tail: "" };
11818
+ return { mark: color51.green("\u2713"), stats, tail: "" };
11573
11819
  case "timeout":
11574
- return { mark: color52.yellow("\u23F1"), stats: `${color52.yellow("timeout")} ${stats}`, tail: errSnip };
11820
+ return { mark: color51.yellow("\u23F1"), stats: `${color51.yellow("timeout")} ${stats}`, tail: errSnip };
11575
11821
  case "stopped":
11576
- return { mark: color52.dim("\u2298"), stats: `${color52.dim("stopped")} ${stats}`, tail: errSnip };
11822
+ return { mark: color51.dim("\u2298"), stats: `${color51.dim("stopped")} ${stats}`, tail: errSnip };
11577
11823
  case "failed":
11578
- return { mark: color52.red("\u2717"), stats: `${color52.red("failed")} ${stats}`, tail: errSnip };
11824
+ return { mark: color51.red("\u2717"), stats: `${color51.red("failed")} ${stats}`, tail: errSnip };
11579
11825
  }
11580
11826
  }
11581
11827
 
@@ -11697,6 +11943,7 @@ async function boot(argv) {
11697
11943
  const isSingleShot = positional.length > 0 || typeof flags["prompt"] === "string";
11698
11944
  const isInteractiveTTY = isStdinTTY() && !isSingleShot;
11699
11945
  if (isInteractiveTTY) {
11946
+ await checkGitInCwd({ cwd, renderer, reader });
11700
11947
  const cont = await runProjectCheck({ projectRoot, cwd, renderer, reader });
11701
11948
  if (!cont) {
11702
11949
  await reader.close();
@@ -11860,6 +12107,61 @@ async function boot(argv) {
11860
12107
  updateInfo
11861
12108
  };
11862
12109
  }
12110
+ async function checkGitInCwd(opts) {
12111
+ const { cwd, renderer, reader } = opts;
12112
+ const cwdGit = path8.join(cwd, ".git");
12113
+ let hasCwdGit = false;
12114
+ try {
12115
+ await fsp4.access(cwdGit);
12116
+ hasCwdGit = true;
12117
+ } catch {
12118
+ }
12119
+ if (!hasCwdGit) {
12120
+ renderer.write(
12121
+ `
12122
+ ${color.amber("\u25CB")} This folder has no ${color.bold(".git")} repository.
12123
+ `
12124
+ );
12125
+ const answer = (await reader.readLine(
12126
+ ` ${color.amber("?")} Initialize one here? ${color.dim("[y/N]")} `
12127
+ )).trim().toLowerCase();
12128
+ if (answer === "y" || answer === "yes") {
12129
+ try {
12130
+ const { spawn: spawn4 } = await import('child_process');
12131
+ await new Promise((resolve5, reject) => {
12132
+ const child = spawn4("git", ["init"], {
12133
+ cwd,
12134
+ signal: AbortSignal.timeout(1e4)
12135
+ });
12136
+ child.on("error", reject);
12137
+ child.on(
12138
+ "close",
12139
+ (code) => code === 0 ? resolve5() : reject(new Error(`git init failed with ${code}`))
12140
+ );
12141
+ });
12142
+ renderer.write(` ${color.green("\u2713")} Git repository initialized
12143
+ `);
12144
+ hasCwdGit = true;
12145
+ } catch (err) {
12146
+ renderer.writeError(
12147
+ `git init failed: ${err instanceof Error ? err.message : String(err)}
12148
+ `
12149
+ );
12150
+ }
12151
+ }
12152
+ }
12153
+ const parentDir = path8.dirname(cwd);
12154
+ if (parentDir !== cwd) {
12155
+ try {
12156
+ await fsp4.access(path8.join(parentDir, ".git"));
12157
+ renderer.write(
12158
+ ` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
12159
+ `
12160
+ );
12161
+ } catch {
12162
+ }
12163
+ }
12164
+ }
11863
12165
  var CONTEXT_OVERFLOW_RE = /context window|exceeds the context|too many tokens|context.*tokens/i;
11864
12166
  function contextOverflowHint(err) {
11865
12167
  const structured = err.code === ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW || err.code === ERROR_CODES.AGENT_CONTEXT_OVERFLOW;
@@ -12168,12 +12470,6 @@ async function predictNextTasks(input, opts) {
12168
12470
  }
12169
12471
  }
12170
12472
  init_sdd();
12171
- function expectDefined14(value) {
12172
- if (value === null || value === void 0) {
12173
- throw new Error("Expected value to be defined");
12174
- }
12175
- return value;
12176
- }
12177
12473
  async function runRepl(opts) {
12178
12474
  if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
12179
12475
  await renderGoalBanner(opts);
@@ -12689,7 +12985,7 @@ async function renderGoalBanner(opts) {
12689
12985
  color.dim("Goal: ") + stateColor(summary) + color.dim(` [${goal.goalState}] (iter ${goal.iterations})`) + "\n"
12690
12986
  );
12691
12987
  if (goal.journal.length > 0) {
12692
- const lastEntry = expectDefined14(goal.journal[goal.journal.length - 1]);
12988
+ const lastEntry = expectDefined(goal.journal[goal.journal.length - 1]);
12693
12989
  const statusIcon = lastEntry.status === "success" ? "\u2713" : lastEntry.status === "failure" ? "\u2717" : lastEntry.status === "aborted" ? "\u2298" : lastEntry.status === "skipped" ? "\u229D" : "\xB7";
12694
12990
  opts.renderer.write(
12695
12991
  color.dim(` Last: ${statusIcon} ${lastEntry.task} (${lastEntry.status})`) + "\n"
@@ -13159,8 +13455,8 @@ async function execute(deps) {
13159
13455
  initialGoal: goalFlag,
13160
13456
  initialAsk: askFlag,
13161
13457
  projectRoot,
13162
- getSDDContext: () => {
13163
- const { getActiveSDDContext: getActiveSDDContext2 } = (init_sdd(), __toCommonJS(sdd_exports));
13458
+ getSDDContext: async () => {
13459
+ const { getActiveSDDContext: getActiveSDDContext2 } = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
13164
13460
  return getActiveSDDContext2();
13165
13461
  },
13166
13462
  onSDDOutput: async (output) => {
@@ -13171,7 +13467,7 @@ async function execute(deps) {
13171
13467
  autoDetectTaskCompletion: autoDetectTaskCompletion2,
13172
13468
  getTaskProgress: getTaskProgress2,
13173
13469
  getActiveSDDPhase: getActiveSDDPhase2
13174
- } = (init_sdd(), __toCommonJS(sdd_exports));
13470
+ } = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
13175
13471
  const messages = [];
13176
13472
  const specSaved = await trySaveSpecFromAIOutput2(output);
13177
13473
  if (specSaved)
@@ -13291,12 +13587,6 @@ async function execute(deps) {
13291
13587
  }
13292
13588
  return code;
13293
13589
  }
13294
- function expectDefined15(value) {
13295
- if (value === null || value === void 0) {
13296
- throw new Error("Expected value to be defined");
13297
- }
13298
- return value;
13299
- }
13300
13590
  function buildRoutingRunner(config, host) {
13301
13591
  const standardRunner = makeAgentSubagentRunner({
13302
13592
  factory: host.makeSubagentFactory(config),
@@ -13305,7 +13595,7 @@ function buildRoutingRunner(config, host) {
13305
13595
  return async (task, ctx) => {
13306
13596
  const subCfg = ctx.config;
13307
13597
  if (subCfg.provider === "acp") {
13308
- const cacheKey = subCfg.role ?? subCfg.name ?? expectDefined15(subCfg.id);
13598
+ const cacheKey = subCfg.role ?? subCfg.name ?? expectDefined(subCfg.id);
13309
13599
  return host.buildACPRunner(cacheKey).then((r) => r(task, ctx));
13310
13600
  }
13311
13601
  return standardRunner(task, ctx);
@@ -14163,7 +14453,8 @@ function gitText(args, cwd) {
14163
14453
  const child = spawn("git", args, {
14164
14454
  cwd,
14165
14455
  env: buildChildEnv(),
14166
- stdio: ["ignore", "pipe", "pipe"]
14456
+ stdio: ["ignore", "pipe", "pipe"],
14457
+ signal: AbortSignal.timeout(1e4)
14167
14458
  });
14168
14459
  child.stdout?.on("data", (c) => {
14169
14460
  out += c.toString();
@@ -14186,7 +14477,8 @@ function runCmd(cmd, args, cwd, shell = false) {
14186
14477
  cwd,
14187
14478
  env: buildChildEnv(),
14188
14479
  stdio: ["ignore", "pipe", "pipe"],
14189
- shell: shell || process.platform === "win32"
14480
+ shell: shell || process.platform === "win32",
14481
+ signal: AbortSignal.timeout(3e4)
14190
14482
  });
14191
14483
  child.stdout?.on("data", (c) => {
14192
14484
  out += c.toString();
@@ -14922,10 +15214,9 @@ async function setupCodebaseIndexing(deps) {
14922
15214
  const onError = (err) => logger.debug(`codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`);
14923
15215
  if (idx.onSessionStart) {
14924
15216
  void runStartupIndex({ projectRoot }).then((r) => {
14925
- const summary = `${color.green("\u2713")} codebase index ready ${color.dim(`\u2014 ${r.symbolsIndexed} symbols \xB7 ${r.filesIndexed} files \xB7 ${r.durationMs}ms`)}`;
14926
- process.stderr.write(`
14927
- ${summary}
14928
- `);
15217
+ logger.info(
15218
+ `codebase index ready: ${r.symbolsIndexed} symbols \xB7 ${r.filesIndexed} files \xB7 ${r.durationMs}ms`
15219
+ );
14929
15220
  }).catch((err) => {
14930
15221
  logger.warn(
14931
15222
  `codebase index (startup) failed: ${err instanceof Error ? err.message : String(err)}`
@@ -15194,15 +15485,21 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
15194
15485
  }
15195
15486
  return { resolvedProvider, provider, providerRegistry };
15196
15487
  }
15197
- function expectDefined16(value) {
15198
- if (value === null || value === void 0) {
15199
- throw new Error("Expected value to be defined");
15200
- }
15201
- return value;
15202
- }
15203
15488
  async function setupSession(params) {
15204
- const { config, wpaths, projectRoot, cwd, sessionStore, systemPrompt, provider, tokenCounter, renderer, flags, onRecovery } = params;
15205
- sessionStore.prune(30).then((count) => {
15489
+ const {
15490
+ config,
15491
+ wpaths,
15492
+ projectRoot,
15493
+ cwd,
15494
+ sessionStore,
15495
+ systemPrompt,
15496
+ provider,
15497
+ tokenCounter,
15498
+ renderer,
15499
+ flags,
15500
+ onRecovery
15501
+ } = params;
15502
+ sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
15206
15503
  if (count > 0) renderer.writeInfo(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
15207
15504
  }).catch(() => void 0);
15208
15505
  let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
@@ -15228,20 +15525,38 @@ async function setupSession(params) {
15228
15525
  const resumed = await sessionStore.resume(resumeId);
15229
15526
  session = resumed.writer;
15230
15527
  restoredMessages = resumed.data.messages;
15231
- renderer.writeInfo(`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`);
15528
+ renderer.writeInfo(
15529
+ `Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
15530
+ );
15232
15531
  } catch (err) {
15233
15532
  renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
15234
15533
  throw Object.assign(new Error("RESUME_FAILED"), { exitCode: 2 });
15235
15534
  }
15236
15535
  } else {
15237
- session = await sessionStore.create({ id: "", title: "", model: config.model, provider: config.provider });
15536
+ session = await sessionStore.create({
15537
+ id: "",
15538
+ title: "",
15539
+ model: config.model,
15540
+ provider: config.provider
15541
+ });
15238
15542
  }
15239
15543
  const sessionRef = { current: session };
15240
15544
  await recoveryLock.write(session?.id).catch(() => void 0);
15241
- const attachments = new DefaultAttachmentStore({ spoolDir: path8.join(wpaths.projectSessions, session?.id, "attachments") });
15545
+ const attachments = new DefaultAttachmentStore({
15546
+ spoolDir: path8.join(wpaths.projectSessions, session?.id, "attachments")
15547
+ });
15242
15548
  const queueStore = new QueueStore({ dir: path8.join(wpaths.projectSessions, session?.id) });
15243
15549
  const ctxSignal = new AbortController().signal;
15244
- const context = new Context({ systemPrompt, provider, session: expectDefined16(session), signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
15550
+ const context = new Context({
15551
+ systemPrompt,
15552
+ provider,
15553
+ session: expectDefined(session),
15554
+ signal: ctxSignal,
15555
+ tokenCounter,
15556
+ cwd,
15557
+ projectRoot,
15558
+ model: config.model
15559
+ });
15245
15560
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
15246
15561
  const todosCheckpointPath = path8.join(wpaths.projectSessions, `${session?.id}.todos.json`);
15247
15562
  if (resumeId) {
@@ -15249,12 +15564,18 @@ async function setupSession(params) {
15249
15564
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
15250
15565
  if (restoredTodos && restoredTodos.length > 0) {
15251
15566
  context.state.replaceTodos(restoredTodos);
15252
- renderer.writeInfo(`Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`);
15567
+ renderer.writeInfo(
15568
+ `Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`
15569
+ );
15253
15570
  }
15254
15571
  } catch {
15255
15572
  }
15256
15573
  }
15257
- const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session?.id);
15574
+ const detachTodosCheckpoint = attachTodosCheckpoint(
15575
+ context.state,
15576
+ todosCheckpointPath,
15577
+ session?.id
15578
+ );
15258
15579
  const planPath = path8.join(wpaths.projectSessions, `${session?.id}.plan.json`);
15259
15580
  context.state.setMeta("plan.path", planPath);
15260
15581
  let dirState;
@@ -15266,7 +15587,9 @@ async function setupSession(params) {
15266
15587
  const tCounts = {};
15267
15588
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
15268
15589
  const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
15269
- renderer.writeInfo(`Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`);
15590
+ renderer.writeInfo(
15591
+ `Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`
15592
+ );
15270
15593
  }
15271
15594
  } catch {
15272
15595
  }
@@ -15275,12 +15598,25 @@ async function setupSession(params) {
15275
15598
  if (plan && plan.items.length > 0) {
15276
15599
  const open = plan.items.filter((p) => p.status !== "done").length;
15277
15600
  const done = plan.items.length - open;
15278
- renderer.writeInfo(`Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`);
15601
+ renderer.writeInfo(
15602
+ `Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`
15603
+ );
15279
15604
  }
15280
15605
  } catch {
15281
15606
  }
15282
15607
  }
15283
- return { session: expectDefined16(session), sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint, priorFleetState: dirState ?? void 0 };
15608
+ return {
15609
+ session: expectDefined(session),
15610
+ sessionRef,
15611
+ context,
15612
+ restoredMessages,
15613
+ attachments,
15614
+ recoveryLock,
15615
+ queueStore,
15616
+ planPath,
15617
+ detachTodosCheckpoint,
15618
+ priorFleetState: dirState ?? void 0
15619
+ };
15284
15620
  }
15285
15621
  function resolveBundledSkillsDir2() {
15286
15622
  try {
@@ -15379,12 +15715,6 @@ async function launchEternalFromFlag(deps) {
15379
15715
  }
15380
15716
 
15381
15717
  // src/cli-main.ts
15382
- function expectDefined17(value) {
15383
- if (value === null || value === void 0) {
15384
- throw new Error("Expected value to be defined");
15385
- }
15386
- return value;
15387
- }
15388
15718
  async function main(argv) {
15389
15719
  const ctx = await boot(argv);
15390
15720
  if (typeof ctx === "number") return ctx;
@@ -15404,11 +15734,14 @@ async function main(argv) {
15404
15734
  } = ctx;
15405
15735
  updateInfo = await printUpdateNotice(updateInfo);
15406
15736
  const pathResolver = new DefaultPathResolver(cwd);
15737
+ const events = new EventBus();
15738
+ events.setLogger(logger);
15407
15739
  const container = createDefaultContainer({
15408
15740
  config,
15409
15741
  wpaths,
15410
15742
  logger,
15411
15743
  modelsRegistry,
15744
+ events,
15412
15745
  permission: {
15413
15746
  yolo: config.yolo,
15414
15747
  yoloDestructive: flags["yolo-destructive"] === true || flags["force-all-yolo"] === true,
@@ -15507,9 +15840,9 @@ async function main(argv) {
15507
15840
  if (config.features.memory) {
15508
15841
  toolRegistry.register(rememberTool(memoryStore));
15509
15842
  toolRegistry.register(forgetTool(memoryStore));
15843
+ toolRegistry.register(searchMemoryTool(memoryStore));
15844
+ toolRegistry.register(relatedMemoryTool(memoryStore));
15510
15845
  }
15511
- const events = new EventBus();
15512
- events.setLogger(logger);
15513
15846
  const { metricsSink, healthRegistry } = (() => {
15514
15847
  const ms = setupMetrics({
15515
15848
  flags,
@@ -15523,35 +15856,40 @@ async function main(argv) {
15523
15856
  const tuiOwnsScreen = flags.tui === true && flags["no-tui"] !== true;
15524
15857
  const spinner = new Spinner(process.stderr, { enabled: !tuiOwnsScreen });
15525
15858
  let lastInputTokens = 0;
15526
- events.on("provider.response", (e) => {
15859
+ const teardownHandlers = [];
15860
+ const evOn = (event, handler) => {
15861
+ events.on(event, handler);
15862
+ teardownHandlers.push(() => events.off(event, handler));
15863
+ };
15864
+ evOn("provider.response", (e) => {
15527
15865
  lastInputTokens = e.usage?.input ?? 0;
15528
15866
  updateSpinnerContext();
15529
15867
  });
15530
- events.on("iteration.started", () => {
15868
+ evOn("iteration.started", () => {
15531
15869
  updateSpinnerContext();
15532
15870
  spinner.start(color.dim(`${config.provider}/${config.model} thinking\u2026`));
15533
15871
  });
15534
- events.on("provider.response", () => {
15872
+ evOn("provider.response", () => {
15535
15873
  spinner.stop();
15536
15874
  });
15537
- events.on("error", () => {
15875
+ evOn("error", () => {
15538
15876
  spinner.stop();
15539
15877
  });
15540
15878
  let streamingActive = false;
15541
- events.on("provider.text_delta", (p) => {
15879
+ evOn("provider.text_delta", (p) => {
15542
15880
  if (!streamingActive) {
15543
15881
  spinner.stop();
15544
15882
  streamingActive = true;
15545
15883
  }
15546
15884
  renderer.write(p.text);
15547
15885
  });
15548
- events.on("iteration.completed", () => {
15886
+ evOn("iteration.completed", () => {
15549
15887
  if (streamingActive) {
15550
15888
  renderer.write("\n");
15551
15889
  streamingActive = false;
15552
15890
  }
15553
15891
  });
15554
- events.on("provider.retry", (p) => {
15892
+ evOn("provider.retry", (p) => {
15555
15893
  spinner.stop();
15556
15894
  if (streamingActive) {
15557
15895
  renderer.write("\n");
@@ -15562,7 +15900,7 @@ async function main(argv) {
15562
15900
  `));
15563
15901
  spinner.start(color.dim(`${config.provider}/${config.model} thinking\u2026`));
15564
15902
  });
15565
- events.on("provider.error", (p) => {
15903
+ evOn("provider.error", (p) => {
15566
15904
  spinner.stop();
15567
15905
  if (streamingActive) {
15568
15906
  renderer.write("\n");
@@ -15615,7 +15953,7 @@ async function main(argv) {
15615
15953
  );
15616
15954
  const stats = new SessionStats(events, tokenCounter);
15617
15955
  const errorRing = [];
15618
- events.on("error", (e) => {
15956
+ evOn("error", (e) => {
15619
15957
  const err = e.err;
15620
15958
  const code = err && typeof err === "object" && "code" in err && typeof err.code === "string" ? err.code : "UNKNOWN";
15621
15959
  const message = e.err instanceof Error ? e.err.message : String(e.err);
@@ -15630,7 +15968,7 @@ async function main(argv) {
15630
15968
  }).catch(() => {
15631
15969
  });
15632
15970
  });
15633
- events.on("tool.started", (e) => {
15971
+ evOn("tool.started", (e) => {
15634
15972
  sessionBridge.append({
15635
15973
  type: "tool_call_start",
15636
15974
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -15640,7 +15978,7 @@ async function main(argv) {
15640
15978
  }).catch(() => {
15641
15979
  });
15642
15980
  });
15643
- events.on("tool.executed", (e) => {
15981
+ evOn("tool.executed", (e) => {
15644
15982
  sessionBridge.append({
15645
15983
  type: "tool_call_end",
15646
15984
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -15656,16 +15994,16 @@ async function main(argv) {
15656
15994
  });
15657
15995
  });
15658
15996
  if (!tuiOwnsScreen) {
15659
- events.on("delegate.started", (e) => {
15997
+ evOn("delegate.started", (e) => {
15660
15998
  const task = e.task.length > 100 ? `${e.task.slice(0, 99)}\u2026` : e.task;
15661
15999
  renderer.writeInfo(`\u{1F91D} Delegating \u2192 ${e.target}: ${task}`);
15662
16000
  });
15663
- events.on("delegate.completed", (e) => {
15664
- const cost = e.costUsd && e.costUsd > 0 ? ` \xB7 $${e.costUsd.toFixed(3)}` : "";
16001
+ evOn("delegate.completed", (e) => {
16002
+ const cost = e.costUsd && e.costUsd > 0 ? ` \xB7 $${e.costUsd.toFixed(4)}` : "";
15665
16003
  renderer.writeInfo(`${e.ok ? "\u2705" : "\u274C"} ${e.summary}${cost}`);
15666
16004
  });
15667
16005
  }
15668
- events.on("tool.progress", (e) => {
16006
+ evOn("tool.progress", (e) => {
15669
16007
  sessionBridge.append({
15670
16008
  type: "tool_progress",
15671
16009
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -15675,7 +16013,7 @@ async function main(argv) {
15675
16013
  }).catch(() => {
15676
16014
  });
15677
16015
  });
15678
- events.on("provider.retry", (e) => {
16016
+ evOn("provider.retry", (e) => {
15679
16017
  sessionBridge.append({
15680
16018
  type: "provider_retry",
15681
16019
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -15687,7 +16025,7 @@ async function main(argv) {
15687
16025
  }).catch(() => {
15688
16026
  });
15689
16027
  });
15690
- events.on("provider.error", (e) => {
16028
+ evOn("provider.error", (e) => {
15691
16029
  sessionBridge.append({
15692
16030
  type: "provider_error",
15693
16031
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -15819,6 +16157,13 @@ async function main(argv) {
15819
16157
  logger
15820
16158
  });
15821
16159
  if (fallbackExtension) agent.extensions.register(fallbackExtension);
16160
+ if (config.features.memory && config.features.memoryConsolidation !== false) {
16161
+ agent.extensions.register(
16162
+ new SessionMemoryConsolidator({
16163
+ memoryStore
16164
+ })
16165
+ );
16166
+ }
15822
16167
  const switchProviderAndModel = (providerId, modelId) => {
15823
16168
  try {
15824
16169
  context.provider = buildProviderForId(providerId);
@@ -15865,10 +16210,10 @@ async function main(argv) {
15865
16210
  }
15866
16211
  };
15867
16212
  const fleetRoot = directorMode ? path8.join(wpaths.projectSessions, session.id) : void 0;
15868
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(expectDefined17(fleetRoot), "fleet.json") : void 0;
15869
- const sharedScratchpadPath = directorMode ? path8.join(expectDefined17(fleetRoot), "shared") : void 0;
15870
- const subagentSessionsRoot = directorMode ? path8.join(expectDefined17(fleetRoot), "subagents") : void 0;
15871
- const stateCheckpointPath = directorMode ? path8.join(expectDefined17(fleetRoot), "director-state.json") : void 0;
16213
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(expectDefined(fleetRoot), "fleet.json") : void 0;
16214
+ const sharedScratchpadPath = directorMode ? path8.join(expectDefined(fleetRoot), "shared") : void 0;
16215
+ const subagentSessionsRoot = directorMode ? path8.join(expectDefined(fleetRoot), "subagents") : void 0;
16216
+ const stateCheckpointPath = directorMode ? path8.join(expectDefined(fleetRoot), "director-state.json") : void 0;
15872
16217
  const fleetRootForPromotion = path8.join(wpaths.projectSessions, session.id);
15873
16218
  const brainQueue = new BrainDecisionQueue(events);
15874
16219
  const brain = new ObservableBrainArbiter(
@@ -16287,7 +16632,7 @@ async function main(argv) {
16287
16632
  ...matches.map((m) => ` ${m.subagentId} (${m.runId})`)
16288
16633
  ].join("\n");
16289
16634
  }
16290
- const t = expectDefined17(matches[0]);
16635
+ const t = expectDefined(matches[0]);
16291
16636
  const raw = await fsp4.readFile(t.file, "utf8");
16292
16637
  if (mode === "raw") return raw;
16293
16638
  const lines = raw.split("\n").filter((l) => l.trim());
@@ -16537,17 +16882,19 @@ Restart WrongStack to load or unload plugin code in this session.`;
16537
16882
  parallelEngine?.stop();
16538
16883
  },
16539
16884
  onExit: () => {
16885
+ for (const teardown of teardownHandlers) teardown();
16886
+ teardownHandlers.length = 0;
16540
16887
  brainQueue.dispose();
16541
16888
  void mcpRegistry.stopAll();
16542
16889
  },
16543
16890
  onBeforeExit: async () => {
16544
- const { spawn: spawn3 } = await import('child_process');
16545
16891
  const cwd2 = projectRoot;
16546
16892
  const statusResult = await new Promise(
16547
16893
  (resolve5, reject) => {
16548
- const child = spawn3("git", ["status", "--porcelain"], {
16894
+ const child = spawn("git", ["status", "--porcelain"], {
16549
16895
  cwd: cwd2,
16550
- stdio: ["ignore", "pipe", "pipe"]
16896
+ stdio: ["ignore", "pipe", "pipe"],
16897
+ signal: AbortSignal.timeout(5e3)
16551
16898
  });
16552
16899
  let stdout = "";
16553
16900
  child.stdout?.on("data", (d) => {