@wrongstack/cli 0.5.0 → 0.5.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,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import * as path17 from 'path';
3
- import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
2
+ import * as path20 from 'path';
3
+ import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
4
+ import { createRequire } from 'module';
5
+ import * as fs3 from 'fs/promises';
6
+ import * as os6 from 'os';
7
+ import os6__default from 'os';
4
8
  import * as crypto from 'crypto';
5
9
  import { randomUUID } from 'crypto';
6
- import * as fs14 from 'fs/promises';
7
10
  import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
8
11
  import { WebSocketServer, WebSocket } from 'ws';
9
12
  import { writeFileSync } from 'fs';
10
- import { createRequire } from 'module';
11
13
  import { MCPRegistry } from '@wrongstack/mcp';
12
14
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
13
15
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
14
16
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
15
- import * as os4 from 'os';
16
17
  import * as readline from 'readline';
18
+ import { spawn } from 'child_process';
17
19
  import { SkillInstaller } from '@wrongstack/core/skills';
18
20
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
19
21
 
@@ -21,6 +23,12 @@ var __defProp = Object.defineProperty;
21
23
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
22
24
  var __getOwnPropNames = Object.getOwnPropertyNames;
23
25
  var __hasOwnProp = Object.prototype.hasOwnProperty;
26
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
27
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
28
+ }) : x)(function(x) {
29
+ if (typeof require !== "undefined") return require.apply(this, arguments);
30
+ throw Error('Dynamic require of "' + x + '" is not supported');
31
+ });
24
32
  var __esm = (fn, res) => function __init() {
25
33
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
26
34
  };
@@ -236,8 +244,8 @@ function buildSddCommand(opts) {
236
244
  async run(args) {
237
245
  const ctx = opts.context;
238
246
  const projectRoot = ctx?.projectRoot ?? process.cwd();
239
- const specsDir = path17.join(projectRoot, ".wrongstack", "specs");
240
- const graphsDir = path17.join(projectRoot, ".wrongstack", "task-graphs");
247
+ const specsDir = path20.join(projectRoot, ".wrongstack", "specs");
248
+ const graphsDir = path20.join(projectRoot, ".wrongstack", "task-graphs");
241
249
  const specStore = new SpecStore({ baseDir: specsDir });
242
250
  new TaskGraphStore({ baseDir: graphsDir });
243
251
  const versioning = new SpecVersioning();
@@ -253,7 +261,7 @@ function buildSddCommand(opts) {
253
261
  const forceFlag = rest.includes("--force") || rest.includes("-f");
254
262
  const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
255
263
  if (!activeBuilder && !forceFlag) {
256
- const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
264
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
257
265
  try {
258
266
  const fsp = await import('fs/promises');
259
267
  await fsp.access(sessionPath);
@@ -291,7 +299,7 @@ function buildSddCommand(opts) {
291
299
  projectContext,
292
300
  minQuestions: 2,
293
301
  maxQuestions: 10,
294
- sessionPath: path17.join(projectRoot, ".wrongstack", "sdd-session.json")
302
+ sessionPath: path20.join(projectRoot, ".wrongstack", "sdd-session.json")
295
303
  });
296
304
  activeBuilder.startSession(title);
297
305
  const aiPrompt = activeBuilder.getAIPrompt();
@@ -551,7 +559,7 @@ Start executing the tasks one by one.`
551
559
  };
552
560
  }
553
561
  case "cancel": {
554
- const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
562
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
555
563
  let deletedFromDisk = false;
556
564
  try {
557
565
  const fsp = await import('fs/promises');
@@ -577,7 +585,7 @@ Start executing the tasks one by one.`
577
585
  if (activeBuilder) {
578
586
  return { message: "An SDD session is already active. Use /sdd cancel first." };
579
587
  }
580
- const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
588
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
581
589
  const projectContext = await gatherProjectContext(projectRoot);
582
590
  activeBuilder = new AISpecBuilder({
583
591
  store: specStore,
@@ -803,7 +811,7 @@ async function gatherProjectContext(projectRoot) {
803
811
  const parts = [];
804
812
  try {
805
813
  const fsp = await import('fs/promises');
806
- const pkgPath = path17.join(projectRoot, "package.json");
814
+ const pkgPath = path20.join(projectRoot, "package.json");
807
815
  const pkgRaw = await fsp.readFile(pkgPath, "utf8");
808
816
  const pkg = JSON.parse(pkgRaw);
809
817
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -820,14 +828,14 @@ async function gatherProjectContext(projectRoot) {
820
828
  }
821
829
  try {
822
830
  const fsp = await import('fs/promises');
823
- const tsconfigPath = path17.join(projectRoot, "tsconfig.json");
831
+ const tsconfigPath = path20.join(projectRoot, "tsconfig.json");
824
832
  await fsp.access(tsconfigPath);
825
833
  parts.push("Language: TypeScript");
826
834
  } catch {
827
835
  }
828
836
  try {
829
837
  const fsp = await import('fs/promises');
830
- const srcDir = path17.join(projectRoot, "src");
838
+ const srcDir = path20.join(projectRoot, "src");
831
839
  const entries = await fsp.readdir(srcDir, { withFileTypes: true });
832
840
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
833
841
  if (dirs.length > 0) {
@@ -899,6 +907,130 @@ var init_provider_config_utils = __esm({
899
907
  }
900
908
  });
901
909
 
910
+ // src/update-check.ts
911
+ var update_check_exports = {};
912
+ __export(update_check_exports, {
913
+ cachePath: () => cachePath,
914
+ checkForUpdate: () => checkForUpdate,
915
+ currentVersion: () => currentVersion,
916
+ getUpdateNotification: () => getUpdateNotification
917
+ });
918
+ function cachePath(homeFn = defaultHomeDir2) {
919
+ return path20.join(homeFn(), ".wrongstack", "update-cache.json");
920
+ }
921
+ function currentVersion() {
922
+ const req2 = createRequire(import.meta.url);
923
+ const candidates = ["../package.json", "../../package.json"];
924
+ for (const rel of candidates) {
925
+ try {
926
+ const pkg = req2(rel);
927
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
928
+ } catch {
929
+ }
930
+ }
931
+ return "dev";
932
+ }
933
+ function isNewer(a, b) {
934
+ const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => parseInt(p, 10) || 0);
935
+ const [ap, bp] = [parse(a), parse(b)];
936
+ for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
937
+ const ai = ap[i] ?? 0;
938
+ const bi = bp[i] ?? 0;
939
+ if (ai > bi) return true;
940
+ if (ai < bi) return false;
941
+ }
942
+ return false;
943
+ }
944
+ async function readCache(homeFn = defaultHomeDir2) {
945
+ try {
946
+ const raw = await fs3.readFile(cachePath(homeFn), "utf8");
947
+ const entry = JSON.parse(raw);
948
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
949
+ return entry;
950
+ } catch {
951
+ return null;
952
+ }
953
+ }
954
+ async function writeCache(entry, homeFn = defaultHomeDir2) {
955
+ try {
956
+ const dir = path20.dirname(cachePath(homeFn));
957
+ await fs3.mkdir(dir, { recursive: true });
958
+ await fs3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
959
+ } catch {
960
+ }
961
+ }
962
+ async function fetchLatestFromNpm(timeoutMs = 3e3) {
963
+ const controller = new AbortController();
964
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
965
+ try {
966
+ const res = await fetch("https://registry.npmjs.org/wrongstack/latest", {
967
+ signal: controller.signal,
968
+ headers: { "Accept": "application/json" }
969
+ });
970
+ clearTimeout(timer);
971
+ if (!res.ok) throw new Error(`npm registry responded ${res.status}`);
972
+ const data = await res.json();
973
+ if (typeof data.version === "string") return data.version;
974
+ throw new Error("No version field in npm response");
975
+ } finally {
976
+ clearTimeout(timer);
977
+ }
978
+ }
979
+ async function checkForUpdate(signal, homeFn) {
980
+ const current = currentVersion();
981
+ const aborted = () => signal?.aborted ?? false;
982
+ const hf = homeFn ?? defaultHomeDir2;
983
+ if (aborted()) {
984
+ return { current, latest: current, outdated: false, checkFailed: true };
985
+ }
986
+ const cached = await readCache(hf);
987
+ if (cached && !cached.error) {
988
+ return {
989
+ current,
990
+ latest: cached.latestVersion,
991
+ outdated: isNewer(cached.latestVersion, current),
992
+ checkFailed: false
993
+ };
994
+ }
995
+ try {
996
+ const latest = await fetchLatestFromNpm();
997
+ await writeCache({ timestamp: Date.now(), latestVersion: latest }, hf);
998
+ return {
999
+ current,
1000
+ latest,
1001
+ outdated: isNewer(latest, current),
1002
+ checkFailed: false
1003
+ };
1004
+ } catch (err) {
1005
+ if (aborted()) {
1006
+ return { current, latest: current, outdated: false, checkFailed: true };
1007
+ }
1008
+ if (cached?.latestVersion) {
1009
+ return {
1010
+ current,
1011
+ latest: cached.latestVersion,
1012
+ outdated: isNewer(cached.latestVersion, current),
1013
+ checkFailed: true
1014
+ };
1015
+ }
1016
+ return { current, latest: current, outdated: false, checkFailed: true };
1017
+ }
1018
+ }
1019
+ async function getUpdateNotification(signal, homeFn) {
1020
+ const info = await checkForUpdate(signal, homeFn);
1021
+ if (info.outdated) {
1022
+ return `Update available: v${info.current} \u2192 v${info.latest}`;
1023
+ }
1024
+ return null;
1025
+ }
1026
+ var defaultHomeDir2, CACHE_TTL_MS;
1027
+ var init_update_check = __esm({
1028
+ "src/update-check.ts"() {
1029
+ defaultHomeDir2 = () => os6.homedir();
1030
+ CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1031
+ }
1032
+ });
1033
+
902
1034
  // src/webui-server.ts
903
1035
  var webui_server_exports = {};
904
1036
  __export(webui_server_exports, {
@@ -1005,7 +1137,7 @@ async function runWebUI(opts) {
1005
1137
  })
1006
1138
  );
1007
1139
  }
1008
- return new Promise((resolve3) => {
1140
+ return new Promise((resolve4) => {
1009
1141
  wss.on("listening", () => {
1010
1142
  console.log(`[WebUI] WebSocket server running on ws://localhost:${port}`);
1011
1143
  setupEvents();
@@ -1075,7 +1207,7 @@ async function runWebUI(opts) {
1075
1207
  clients.clear();
1076
1208
  wss.close(() => {
1077
1209
  console.log("[WebUI] Server stopped");
1078
- resolve3();
1210
+ resolve4();
1079
1211
  });
1080
1212
  }
1081
1213
  process.on("SIGINT", shutdown);
@@ -1381,7 +1513,7 @@ async function runWebUI(opts) {
1381
1513
  if (!opts.globalConfigPath) return {};
1382
1514
  let raw;
1383
1515
  try {
1384
- raw = await fs14.readFile(opts.globalConfigPath, "utf8");
1516
+ raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1385
1517
  } catch {
1386
1518
  return {};
1387
1519
  }
@@ -1392,7 +1524,7 @@ async function runWebUI(opts) {
1392
1524
  return {};
1393
1525
  }
1394
1526
  if (!parsed.providers) return {};
1395
- const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
1527
+ const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1396
1528
  const vault = new DefaultSecretVault$1({ keyFile });
1397
1529
  return decryptConfigSecrets$1(parsed.providers, vault);
1398
1530
  }
@@ -1400,7 +1532,7 @@ async function runWebUI(opts) {
1400
1532
  if (!opts.globalConfigPath) return;
1401
1533
  let raw;
1402
1534
  try {
1403
- raw = await fs14.readFile(opts.globalConfigPath, "utf8");
1535
+ raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1404
1536
  } catch {
1405
1537
  raw = "{}";
1406
1538
  }
@@ -1411,7 +1543,7 @@ async function runWebUI(opts) {
1411
1543
  parsed = {};
1412
1544
  }
1413
1545
  parsed.providers = providers;
1414
- const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
1546
+ const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1415
1547
  const vault = new DefaultSecretVault$1({ keyFile });
1416
1548
  const encrypted = encryptConfigSecrets(parsed, vault);
1417
1549
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -1439,6 +1571,37 @@ var init_plugin_api_factory = __esm({
1439
1571
  }
1440
1572
  });
1441
1573
 
1574
+ // src/slash-commands/commit-llm.ts
1575
+ async function generateCommitMessageWithLLM(diff, opts) {
1576
+ 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";
1577
+ const userPrompt = `Here is the git diff:
1578
+
1579
+ ${diff}`;
1580
+ try {
1581
+ const signal = new AbortController();
1582
+ const timeout = setTimeout(() => signal.abort(), 15e3);
1583
+ const resp = await opts.provider.complete(
1584
+ {
1585
+ model: opts.model,
1586
+ system: [{ type: "text", text: systemPrompt }],
1587
+ messages: [{ role: "user", content: [{ type: "text", text: userPrompt }] }],
1588
+ maxTokens: 80,
1589
+ temperature: 0.3
1590
+ },
1591
+ { signal: signal.signal }
1592
+ );
1593
+ clearTimeout(timeout);
1594
+ const rawContent = resp.content;
1595
+ const text = Array.isArray(rawContent) ? rawContent[0]?.text ?? "" : typeof rawContent === "object" && rawContent !== null ? rawContent.text ?? "" : String(rawContent);
1596
+ const message = text.trim().split("\n")[0];
1597
+ if (message.length > 0 && message.length < 200) {
1598
+ return message;
1599
+ }
1600
+ } catch {
1601
+ }
1602
+ return "chore: update";
1603
+ }
1604
+
1442
1605
  // src/arg-parser.ts
1443
1606
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1444
1607
  "yolo",
@@ -1457,7 +1620,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1457
1620
  "output-json",
1458
1621
  "prompt",
1459
1622
  "metrics",
1460
- "webui"
1623
+ "webui",
1624
+ "no-check"
1461
1625
  ]);
1462
1626
  function parseArgs(argv) {
1463
1627
  const flags = {};
@@ -1563,10 +1727,10 @@ function parseSpawnFlags(input) {
1563
1727
  return { description: rest.trim(), opts };
1564
1728
  }
1565
1729
  async function bootConfig(flags) {
1566
- const cwd = typeof flags["cwd"] === "string" ? path17.resolve(flags["cwd"]) : process.cwd();
1730
+ const cwd = typeof flags["cwd"] === "string" ? path20.resolve(flags["cwd"]) : process.cwd();
1567
1731
  const pathResolver = new DefaultPathResolver(cwd);
1568
1732
  const projectRoot = pathResolver.projectRoot;
1569
- const userHome = os4.homedir();
1733
+ const userHome = os6.homedir();
1570
1734
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
1571
1735
  await ensureProjectMeta(wpaths, projectRoot);
1572
1736
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -1614,13 +1778,13 @@ function flagsToConfigPatch(flags) {
1614
1778
  }
1615
1779
  async function ensureProjectMeta(paths, projectRoot) {
1616
1780
  try {
1617
- await fs14.mkdir(paths.projectDir, { recursive: true });
1781
+ await fs3.mkdir(paths.projectDir, { recursive: true });
1618
1782
  const meta = {
1619
1783
  hash: paths.projectHash,
1620
1784
  root: projectRoot,
1621
1785
  lastSeen: (/* @__PURE__ */ new Date()).toISOString()
1622
1786
  };
1623
- await fs14.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1787
+ await fs3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1624
1788
  } catch {
1625
1789
  }
1626
1790
  }
@@ -1630,11 +1794,11 @@ var ReadlineInputReader = class {
1630
1794
  history = [];
1631
1795
  pending = false;
1632
1796
  constructor(opts = {}) {
1633
- this.historyFile = opts.historyFile ?? path17.join(os4.homedir(), ".wrongstack", "history");
1797
+ this.historyFile = opts.historyFile ?? path20.join(os6.homedir(), ".wrongstack", "history");
1634
1798
  }
1635
1799
  async loadHistory() {
1636
1800
  try {
1637
- const raw = await fs14.readFile(this.historyFile, "utf8");
1801
+ const raw = await fs3.readFile(this.historyFile, "utf8");
1638
1802
  this.history = raw.split("\n").filter(Boolean).slice(-1e3);
1639
1803
  } catch {
1640
1804
  this.history = [];
@@ -1642,8 +1806,8 @@ var ReadlineInputReader = class {
1642
1806
  }
1643
1807
  async saveHistory() {
1644
1808
  try {
1645
- await fs14.mkdir(path17.dirname(this.historyFile), { recursive: true });
1646
- await fs14.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1809
+ await fs3.mkdir(path20.dirname(this.historyFile), { recursive: true });
1810
+ await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1647
1811
  } catch {
1648
1812
  }
1649
1813
  }
@@ -1661,7 +1825,7 @@ var ReadlineInputReader = class {
1661
1825
  async readLine(prompt) {
1662
1826
  if (this.history.length === 0) await this.loadHistory();
1663
1827
  while (this.pending) {
1664
- await new Promise((resolve3) => setTimeout(resolve3, 50));
1828
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
1665
1829
  }
1666
1830
  this.pending = true;
1667
1831
  try {
@@ -1671,15 +1835,15 @@ var ReadlineInputReader = class {
1671
1835
  this.rl = void 0;
1672
1836
  }
1673
1837
  const fresh = this.ensure();
1674
- return new Promise((resolve3) => {
1838
+ return new Promise((resolve4) => {
1675
1839
  fresh.question(prompt ?? "> ", (line) => {
1676
1840
  if (line.trim()) {
1677
1841
  this.history.push(line);
1678
1842
  void this.saveHistory();
1679
1843
  }
1680
- resolve3(line);
1844
+ resolve4(line);
1681
1845
  });
1682
- fresh.once("close", () => resolve3(""));
1846
+ fresh.once("close", () => resolve4(""));
1683
1847
  }).then((result) => {
1684
1848
  this.rl?.close();
1685
1849
  this.rl = void 0;
@@ -1691,7 +1855,7 @@ var ReadlineInputReader = class {
1691
1855
  }
1692
1856
  async readKey(prompt, options) {
1693
1857
  process.stdout.write(prompt);
1694
- return new Promise((resolve3) => {
1858
+ return new Promise((resolve4) => {
1695
1859
  const stdin = process.stdin;
1696
1860
  const wasRaw = stdin.isRaw;
1697
1861
  const wasPaused = stdin.isPaused();
@@ -1702,7 +1866,7 @@ var ReadlineInputReader = class {
1702
1866
  if (key === "") {
1703
1867
  cleanup();
1704
1868
  process.stdout.write("\n");
1705
- resolve3("");
1869
+ resolve4("");
1706
1870
  return;
1707
1871
  }
1708
1872
  const opt = options.find(
@@ -1712,12 +1876,12 @@ var ReadlineInputReader = class {
1712
1876
  cleanup();
1713
1877
  process.stdout.write(`${opt.key}
1714
1878
  `);
1715
- resolve3(opt.value);
1879
+ resolve4(opt.value);
1716
1880
  }
1717
1881
  };
1718
1882
  const onClose = () => {
1719
1883
  cleanup();
1720
- resolve3("");
1884
+ resolve4("");
1721
1885
  };
1722
1886
  const cleanup = () => {
1723
1887
  stdin.off("data", onData);
@@ -1745,7 +1909,7 @@ var ReadlineInputReader = class {
1745
1909
  this.rl?.close();
1746
1910
  this.rl = void 0;
1747
1911
  process.stdout.write(prompt);
1748
- return new Promise((resolve3) => {
1912
+ return new Promise((resolve4) => {
1749
1913
  let buf = "";
1750
1914
  const wasRaw = stdin.isRaw;
1751
1915
  stdin.setRawMode(true);
@@ -1763,7 +1927,7 @@ var ReadlineInputReader = class {
1763
1927
  cleanup();
1764
1928
  process.stdout.write(` ${dim(`[${buf.length} chars]`)}
1765
1929
  `);
1766
- resolve3(buf);
1930
+ resolve4(buf);
1767
1931
  return;
1768
1932
  }
1769
1933
  if (ch === "") {
@@ -1860,8 +2024,247 @@ async function buildPickableProviders(modelsRegistry, config) {
1860
2024
  }
1861
2025
  return out;
1862
2026
  }
2027
+ var PROTECTED_BASENAMES = /* @__PURE__ */ new Set([
2028
+ "config.json",
2029
+ ".key",
2030
+ "index.json"
2031
+ ]);
2032
+ function assertSafeToDelete(filename, parentDir) {
2033
+ if (PROTECTED_BASENAMES.has(filename)) {
2034
+ throw new Error(`Refusing to delete protected file: ${filename}`);
2035
+ }
2036
+ if (filename !== path20.basename(filename)) {
2037
+ throw new Error(`Refusing to delete path with traversal: ${filename}`);
2038
+ }
2039
+ if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
2040
+ throw new Error(`Refusing to delete unknown file: ${filename}`);
2041
+ }
2042
+ const resolvedParent = path20.resolve(parentDir);
2043
+ if (!resolvedParent.endsWith(".wrongstack")) {
2044
+ throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
2045
+ }
2046
+ }
2047
+ async function safeDelete(filePath) {
2048
+ const dir = path20.dirname(filePath);
2049
+ const filename = path20.basename(filePath);
2050
+ try {
2051
+ assertSafeToDelete(filename, dir);
2052
+ await fs3.unlink(filePath);
2053
+ } catch (err) {
2054
+ if (err instanceof Error && err.message.startsWith("Refusing")) {
2055
+ process.stderr.write(`[config-history] SAFETY: ${err.message}
2056
+ `);
2057
+ }
2058
+ }
2059
+ }
2060
+ function maskConfigSecrets(cfg) {
2061
+ if (typeof cfg !== "object" || cfg === null) return {};
2062
+ const out = {};
2063
+ for (const [k, v] of Object.entries(cfg)) {
2064
+ if (k === "apiKey" || k === "apiKeys" || k === "secret" || k === "secrets") {
2065
+ out[k] = "[REDACTED]";
2066
+ } else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
2067
+ out[k] = maskConfigSecrets(v);
2068
+ } else {
2069
+ out[k] = v;
2070
+ }
2071
+ }
2072
+ return out;
2073
+ }
2074
+ function diffSummary(oldCfg, newCfg) {
2075
+ const changes = [];
2076
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);
2077
+ for (const k of allKeys) {
2078
+ const o = JSON.stringify(oldCfg[k]);
2079
+ const n = JSON.stringify(newCfg[k]);
2080
+ if (o !== n) {
2081
+ if (k === "apiKey" || k === "apiKeys" || k === "secret") {
2082
+ changes.push(`${k}: [CHANGED]`);
2083
+ } else if (typeof newCfg[k] !== "object") {
2084
+ changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
2085
+ } else {
2086
+ changes.push(`${k}: [CHANGED]`);
2087
+ }
2088
+ }
2089
+ }
2090
+ return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
2091
+ }
2092
+ var defaultHomeDir = () => os6__default.homedir();
2093
+ function historyDir(homeFn = defaultHomeDir) {
2094
+ return path20.join(homeFn(), ".wrongstack", "config.history", "entries");
2095
+ }
2096
+ function historyIndexPath(homeFn = defaultHomeDir) {
2097
+ return path20.join(homeFn(), ".wrongstack", "config.history", "index.json");
2098
+ }
2099
+ function configPath(homeFn = defaultHomeDir) {
2100
+ return path20.join(homeFn(), ".wrongstack", "config.json");
2101
+ }
2102
+ function backupLastPath(homeFn = defaultHomeDir) {
2103
+ return path20.join(homeFn(), ".wrongstack", "config.json.last");
2104
+ }
2105
+ function entryId(ts) {
2106
+ return ts.replace(/[:.]/g, "-").slice(0, 19);
2107
+ }
2108
+ async function ensureHistoryDir(homeFn = defaultHomeDir) {
2109
+ await fs3.mkdir(historyDir(homeFn), { recursive: true });
2110
+ }
2111
+ async function readIndex(homeFn = defaultHomeDir) {
2112
+ try {
2113
+ const raw = await fs3.readFile(historyIndexPath(homeFn), "utf8");
2114
+ return JSON.parse(raw);
2115
+ } catch {
2116
+ return { version: 1, entries: [] };
2117
+ }
2118
+ }
2119
+ async function writeIndex(idx, homeFn = defaultHomeDir) {
2120
+ await ensureHistoryDir(homeFn);
2121
+ await fs3.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
2122
+ }
2123
+ async function backupCurrent(homeFn = defaultHomeDir) {
2124
+ const cfg = configPath(homeFn);
2125
+ const last = backupLastPath(homeFn);
2126
+ const ts = Date.now();
2127
+ let content;
2128
+ try {
2129
+ content = await fs3.readFile(cfg, "utf8");
2130
+ } catch {
2131
+ }
2132
+ if (content !== void 0) {
2133
+ try {
2134
+ await atomicWrite(last, content);
2135
+ } catch {
2136
+ }
2137
+ }
2138
+ if (content !== void 0) {
2139
+ try {
2140
+ const bakPath = path20.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2141
+ await atomicWrite(bakPath, content);
2142
+ } catch {
2143
+ }
2144
+ }
2145
+ try {
2146
+ const dir = path20.join(homeFn(), ".wrongstack");
2147
+ const files = await fs3.readdir(dir);
2148
+ const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
2149
+ for (const f of baks.slice(10)) {
2150
+ await safeDelete(path20.join(dir, f));
2151
+ }
2152
+ } catch {
2153
+ }
2154
+ }
2155
+ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDir) {
2156
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2157
+ const id = entryId(timestamp);
2158
+ await ensureHistoryDir(homeFn);
2159
+ const entry = {
2160
+ id,
2161
+ timestamp,
2162
+ description,
2163
+ snapshotMasked: maskConfigSecrets(newCfg),
2164
+ diffSummary: diffSummary(oldCfg, newCfg)
2165
+ };
2166
+ await fs3.writeFile(
2167
+ path20.join(historyDir(homeFn), `${id}.json`),
2168
+ JSON.stringify(entry, null, 2),
2169
+ "utf8"
2170
+ );
2171
+ const idx = await readIndex(homeFn);
2172
+ idx.entries.unshift({ id, timestamp, description });
2173
+ await writeIndex(idx, homeFn);
2174
+ return id;
2175
+ }
2176
+ async function listHistory(homeFn = defaultHomeDir) {
2177
+ const idx = await readIndex(homeFn);
2178
+ return idx.entries;
2179
+ }
2180
+ async function getHistoryEntry(id, homeFn = defaultHomeDir) {
2181
+ try {
2182
+ const raw = await fs3.readFile(path20.join(historyDir(homeFn), `${id}.json`), "utf8");
2183
+ return JSON.parse(raw);
2184
+ } catch {
2185
+ return null;
2186
+ }
2187
+ }
2188
+ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
2189
+ const entry = await getHistoryEntry(id, homeFn);
2190
+ if (!entry) return { ok: false, backupId: null, error: "History entry not found" };
2191
+ await backupCurrent(homeFn);
2192
+ let oldCfg = {};
2193
+ try {
2194
+ const raw = await fs3.readFile(configPath(homeFn), "utf8");
2195
+ oldCfg = JSON.parse(raw);
2196
+ } catch {
2197
+ }
2198
+ try {
2199
+ await atomicWrite(configPath(homeFn), JSON.stringify(entry.snapshotMasked, null, 2));
2200
+ } catch (err) {
2201
+ return { ok: false, backupId: null, error: String(err) };
2202
+ }
2203
+ const backupId = await appendHistory(
2204
+ oldCfg,
2205
+ entry.snapshotMasked,
2206
+ `Restored from history ${id}`,
2207
+ homeFn
2208
+ );
2209
+ return { ok: true, backupId };
2210
+ }
2211
+ async function restoreLast(homeFn = defaultHomeDir) {
2212
+ const last = backupLastPath(homeFn);
2213
+ const cfg = configPath(homeFn);
2214
+ let oldCfg = {};
2215
+ try {
2216
+ const raw = await fs3.readFile(cfg, "utf8");
2217
+ oldCfg = JSON.parse(raw);
2218
+ } catch {
2219
+ }
2220
+ let lastCfg = {};
2221
+ try {
2222
+ const raw = await fs3.readFile(last, "utf8");
2223
+ lastCfg = JSON.parse(raw);
2224
+ } catch {
2225
+ return { ok: false, error: "No prior backup found" };
2226
+ }
2227
+ await backupCurrent(homeFn);
2228
+ try {
2229
+ await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));
2230
+ } catch (err) {
2231
+ return { ok: false, error: String(err) };
2232
+ }
2233
+ await appendHistory(oldCfg, lastCfg, "Restored from config.json.last", homeFn);
2234
+ return { ok: true };
2235
+ }
1863
2236
 
1864
2237
  // src/picker.ts
2238
+ var theme = { primary: color.amber };
2239
+ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
2240
+ try {
2241
+ const { atomicWrite: atomicWrite7 } = await import('@wrongstack/core');
2242
+ const fs17 = await import('fs/promises');
2243
+ let existing = {};
2244
+ try {
2245
+ const raw = await fs17.readFile(configPath2, "utf8");
2246
+ existing = JSON.parse(raw);
2247
+ } catch {
2248
+ }
2249
+ const oldCfg = { ...existing };
2250
+ existing.provider = provider;
2251
+ existing.model = model;
2252
+ await backupCurrent(homeFn);
2253
+ await atomicWrite7(configPath2, JSON.stringify(existing, null, 2));
2254
+ try {
2255
+ await appendHistory(
2256
+ oldCfg,
2257
+ existing,
2258
+ `Provider/model changed: ${oldCfg.provider ?? "(none)"} \u2192 ${provider}, ${oldCfg.model ?? "(none)"} \u2192 ${model}`,
2259
+ homeFn
2260
+ );
2261
+ } catch {
2262
+ }
2263
+ return true;
2264
+ } catch {
2265
+ return false;
2266
+ }
2267
+ }
1865
2268
  async function runPicker(deps) {
1866
2269
  const { modelsRegistry, renderer, reader, config, defaultProvider, defaultModel } = deps;
1867
2270
  renderer.write(
@@ -2092,28 +2495,9 @@ async function resolveModelSelection(answer, models, provider, _registry, render
2092
2495
  `);
2093
2496
  return { provider: provider.id, model: modelId };
2094
2497
  }
2095
- var theme = { primary: color.amber };
2096
- async function saveToGlobalConfig(configPath, provider, model) {
2097
- try {
2098
- const { atomicWrite: atomicWrite6 } = await import('@wrongstack/core');
2099
- const fs15 = await import('fs/promises');
2100
- let existing = {};
2101
- try {
2102
- const raw = await fs15.readFile(configPath, "utf8");
2103
- existing = JSON.parse(raw);
2104
- } catch {
2105
- }
2106
- existing.provider = provider;
2107
- existing.model = model;
2108
- await atomicWrite6(configPath, JSON.stringify(existing, null, 2));
2109
- return true;
2110
- } catch {
2111
- return false;
2112
- }
2113
- }
2114
2498
  async function pathExists(file) {
2115
2499
  try {
2116
- await fs14.access(file);
2500
+ await fs3.access(file);
2117
2501
  return true;
2118
2502
  } catch {
2119
2503
  return false;
@@ -2124,10 +2508,10 @@ async function detectPackageManager(root, declared) {
2124
2508
  const name = declared.split("@")[0];
2125
2509
  if (name) return name;
2126
2510
  }
2127
- if (await pathExists(path17.join(root, "pnpm-lock.yaml"))) return "pnpm";
2128
- if (await pathExists(path17.join(root, "bun.lockb"))) return "bun";
2129
- if (await pathExists(path17.join(root, "bun.lock"))) return "bun";
2130
- if (await pathExists(path17.join(root, "yarn.lock"))) return "yarn";
2511
+ if (await pathExists(path20.join(root, "pnpm-lock.yaml"))) return "pnpm";
2512
+ if (await pathExists(path20.join(root, "bun.lockb"))) return "bun";
2513
+ if (await pathExists(path20.join(root, "bun.lock"))) return "bun";
2514
+ if (await pathExists(path20.join(root, "yarn.lock"))) return "yarn";
2131
2515
  return "npm";
2132
2516
  }
2133
2517
  function hasUsableScript(scripts, name) {
@@ -2148,7 +2532,7 @@ function parseMakeTargets(makefile) {
2148
2532
  async function detectProjectFacts(root) {
2149
2533
  const facts = { hints: [] };
2150
2534
  try {
2151
- const pkg = JSON.parse(await fs14.readFile(path17.join(root, "package.json"), "utf8"));
2535
+ const pkg = JSON.parse(await fs3.readFile(path20.join(root, "package.json"), "utf8"));
2152
2536
  const scripts = pkg.scripts ?? {};
2153
2537
  const pm = await detectPackageManager(root, pkg.packageManager);
2154
2538
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2162,14 +2546,14 @@ async function detectProjectFacts(root) {
2162
2546
  } catch {
2163
2547
  }
2164
2548
  try {
2165
- if (!await pathExists(path17.join(root, "pyproject.toml"))) throw new Error("not python");
2549
+ if (!await pathExists(path20.join(root, "pyproject.toml"))) throw new Error("not python");
2166
2550
  facts.test ??= "pytest";
2167
2551
  facts.lint ??= "ruff check .";
2168
2552
  facts.hints.push("pyproject.toml");
2169
2553
  } catch {
2170
2554
  }
2171
2555
  try {
2172
- if (!await pathExists(path17.join(root, "go.mod"))) throw new Error("not go");
2556
+ if (!await pathExists(path20.join(root, "go.mod"))) throw new Error("not go");
2173
2557
  facts.build ??= "go build ./...";
2174
2558
  facts.test ??= "go test ./...";
2175
2559
  facts.run ??= "go run .";
@@ -2177,7 +2561,7 @@ async function detectProjectFacts(root) {
2177
2561
  } catch {
2178
2562
  }
2179
2563
  try {
2180
- if (!await pathExists(path17.join(root, "Cargo.toml"))) throw new Error("not rust");
2564
+ if (!await pathExists(path20.join(root, "Cargo.toml"))) throw new Error("not rust");
2181
2565
  facts.build ??= "cargo build";
2182
2566
  facts.test ??= "cargo test";
2183
2567
  facts.lint ??= "cargo clippy";
@@ -2186,7 +2570,7 @@ async function detectProjectFacts(root) {
2186
2570
  } catch {
2187
2571
  }
2188
2572
  try {
2189
- const makefile = await fs14.readFile(path17.join(root, "Makefile"), "utf8");
2573
+ const makefile = await fs3.readFile(path20.join(root, "Makefile"), "utf8");
2190
2574
  const targets = parseMakeTargets(makefile);
2191
2575
  facts.build ??= targets.has("build") ? "make build" : "make";
2192
2576
  if (targets.has("test")) facts.test ??= "make test";
@@ -2320,6 +2704,179 @@ function buildClearCommand(opts) {
2320
2704
  }
2321
2705
  };
2322
2706
  }
2707
+ async function runGit(args, cwd) {
2708
+ return new Promise((resolve4) => {
2709
+ const child = spawn("git", args, {
2710
+ cwd,
2711
+ stdio: ["ignore", "pipe", "pipe"]
2712
+ });
2713
+ let stdout = "";
2714
+ let stderr = "";
2715
+ child.stdout?.on("data", (d) => stdout += d);
2716
+ child.stderr?.on("data", (d) => stderr += d);
2717
+ child.on("close", (code) => resolve4({ stdout, stderr, code: code ?? 0 }));
2718
+ });
2719
+ }
2720
+ function detectCommitType(stats) {
2721
+ const lines = stats.split("\n");
2722
+ const hasTestFiles = lines.some(
2723
+ (l) => l.includes("_test.") || l.includes(".test.") || l.includes(".spec.")
2724
+ );
2725
+ const hasDocs = lines.some(
2726
+ (l) => l.includes("README") || l.includes("CHANGELOG") || l.includes("docs/") || l.includes(".md")
2727
+ );
2728
+ const hasConfig = lines.some(
2729
+ (l) => l.includes("config") || l.includes("tsconfig") || l.includes(".json")
2730
+ );
2731
+ if (hasTestFiles) return "test";
2732
+ if (hasDocs) return "docs";
2733
+ if (hasConfig) return "chore";
2734
+ return "feat";
2735
+ }
2736
+ async function generateCommitMessageHeuristics(cwd) {
2737
+ const statsResult = await runGit(["diff", "--stat"], cwd);
2738
+ if (statsResult.code !== 0) return "chore: update";
2739
+ const nameResult = await runGit(["diff", "--name-only"], cwd);
2740
+ const files = nameResult.stdout.split("\n").filter(Boolean);
2741
+ const commitType = detectCommitType(statsResult.stdout);
2742
+ let scope = "";
2743
+ if (files.length > 0) {
2744
+ const primary = files[0].split("/")[0];
2745
+ if (primary && primary !== "packages" && primary !== "apps" && primary !== "node_modules") {
2746
+ scope = `(${primary})`;
2747
+ }
2748
+ }
2749
+ if (files.length === 0) {
2750
+ return `${commitType}${scope}: update`;
2751
+ }
2752
+ if (files.length <= 3) {
2753
+ const summary2 = files.map((f) => f.split("/").pop()).join(", ");
2754
+ return `${commitType}${scope}: ${summary2}`;
2755
+ }
2756
+ const summary = files.slice(0, 3).map((f) => f.split("/").pop()).join(", ") + ` and ${files.length - 3} more`;
2757
+ return `${commitType}${scope}: ${summary}`;
2758
+ }
2759
+ async function hasUncommittedChanges(cwd) {
2760
+ const result = await runGit(["status", "--porcelain"], cwd);
2761
+ return result.stdout.trim().length > 0;
2762
+ }
2763
+ async function isGitRepo(cwd) {
2764
+ const result = await runGit(["rev-parse", "--git-dir"], cwd);
2765
+ return result.code === 0;
2766
+ }
2767
+ function buildCommitCommand(_opts, generateCommitMessage) {
2768
+ return {
2769
+ name: "commit",
2770
+ description: "Stage all changes and commit with auto-generated message.",
2771
+ aliases: ["gc"],
2772
+ async run(args, ctx) {
2773
+ const cwd = ctx?.cwd ?? process.cwd();
2774
+ if (!await isGitRepo(cwd)) {
2775
+ return { message: "Not a git repository." };
2776
+ }
2777
+ if (!await hasUncommittedChanges(cwd)) {
2778
+ return { message: "Nothing to commit (working tree clean)." };
2779
+ }
2780
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
2781
+ args.includes("--no-llm");
2782
+ let message;
2783
+ {
2784
+ message = await generateCommitMessageHeuristics(cwd);
2785
+ }
2786
+ if (dryRun) {
2787
+ return {
2788
+ message: `Would commit:
2789
+
2790
+ ${color.green(message)}
2791
+
2792
+ ${color.dim("(dry-run \u2014 no actual commit)")}`
2793
+ };
2794
+ }
2795
+ const stageResult = await runGit(["add", "."], cwd);
2796
+ if (stageResult.code !== 0) {
2797
+ return { message: `Stage failed: ${stageResult.stderr}` };
2798
+ }
2799
+ const commitResult = await runGit(["commit", "-m", message], cwd);
2800
+ if (commitResult.code !== 0) {
2801
+ return { message: `Commit failed: ${commitResult.stderr}` };
2802
+ }
2803
+ const hashResult = await runGit(["rev-parse", "--short", "HEAD"], cwd);
2804
+ const hash = hashResult.stdout.trim();
2805
+ const pushResult = await runGit(["remote"], cwd);
2806
+ const hasRemote = pushResult.stdout.trim().length > 0;
2807
+ let pushMsg = "";
2808
+ if (hasRemote) {
2809
+ pushMsg = `
2810
+
2811
+ ${color.dim("Tip: Run /push to push to remote")}`;
2812
+ }
2813
+ return {
2814
+ message: `${color.green("\u2713")} Committed: ${color.bold(message)}
2815
+ ${color.dim(hash)}${pushMsg}`
2816
+ };
2817
+ }
2818
+ };
2819
+ }
2820
+ function buildGitcheckCommand(_opts) {
2821
+ return {
2822
+ name: "gitcheck",
2823
+ description: "Check for uncommitted changes (for system prompt integration).",
2824
+ aliases: ["gcstatus"],
2825
+ async run(_args, ctx) {
2826
+ const cwd = ctx?.cwd ?? process.cwd();
2827
+ if (!await isGitRepo(cwd)) {
2828
+ return { message: "" };
2829
+ }
2830
+ if (!await hasUncommittedChanges(cwd)) {
2831
+ return { message: "" };
2832
+ }
2833
+ const statusResult = await runGit(["status", "--porcelain"], cwd);
2834
+ const lines = statusResult.stdout.split("\n").filter(Boolean);
2835
+ const count = lines.length;
2836
+ if (count === 0) return { message: "" };
2837
+ return {
2838
+ message: `\u26A0 ${color.yellow(`${count} uncommitted change${count > 1 ? "s" : ""}`)} \u2014 consider /commit`
2839
+ };
2840
+ }
2841
+ };
2842
+ }
2843
+ function buildPushCommand(_opts) {
2844
+ return {
2845
+ name: "push",
2846
+ description: "Push to remote after commit.",
2847
+ async run(args, ctx) {
2848
+ const cwd = ctx?.cwd ?? process.cwd();
2849
+ if (!await isGitRepo(cwd)) {
2850
+ return { message: "Not a git repository." };
2851
+ }
2852
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
2853
+ const force = args.includes("--force") || args.includes("-f");
2854
+ const remoteResult = await runGit(["remote"], cwd);
2855
+ const remotes = remoteResult.stdout.split("\n").filter(Boolean);
2856
+ if (remotes.length === 0) {
2857
+ return { message: "No remote configured. Add one with: git remote add origin <url>" };
2858
+ }
2859
+ if (dryRun) {
2860
+ return {
2861
+ message: `Would push to ${remotes.join(", ")}${force ? " (force)" : ""}
2862
+ ${color.dim("(dry-run)")}`
2863
+ };
2864
+ }
2865
+ const branchResult = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2866
+ const branch = branchResult.stdout.trim() || "main";
2867
+ const pushArgs = ["push"];
2868
+ if (force) pushArgs.push("--force");
2869
+ pushArgs.push(...remotes, branch);
2870
+ const pushResult = await runGit(pushArgs, cwd);
2871
+ if (pushResult.code !== 0) {
2872
+ return { message: `Push failed: ${pushResult.stderr}` };
2873
+ }
2874
+ return {
2875
+ message: `${color.green("\u2713")} Pushed to ${remotes.join(", ")} (${branch})`
2876
+ };
2877
+ }
2878
+ };
2879
+ }
2323
2880
 
2324
2881
  // src/slash-commands/compact.ts
2325
2882
  function buildCompactCommand(opts) {
@@ -2649,10 +3206,10 @@ function buildInitCommand(opts) {
2649
3206
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
2650
3207
  async run(args, ctx) {
2651
3208
  const force = args.trim() === "--force";
2652
- const dir = path17.join(ctx.projectRoot, ".wrongstack");
2653
- const file = path17.join(dir, "AGENTS.md");
3209
+ const dir = path20.join(ctx.projectRoot, ".wrongstack");
3210
+ const file = path20.join(dir, "AGENTS.md");
2654
3211
  try {
2655
- await fs14.access(file);
3212
+ await fs3.access(file);
2656
3213
  if (!force) {
2657
3214
  const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
2658
3215
  opts.renderer.writeWarning(msg2);
@@ -2662,8 +3219,8 @@ function buildInitCommand(opts) {
2662
3219
  }
2663
3220
  const detected = await detectProjectFacts(ctx.projectRoot);
2664
3221
  const body = renderAgentsTemplate(detected);
2665
- await fs14.mkdir(dir, { recursive: true });
2666
- await fs14.writeFile(file, body, "utf8");
3222
+ await fs3.mkdir(dir, { recursive: true });
3223
+ await fs3.writeFile(file, body, "utf8");
2667
3224
  if (detected.hints.length > 0) {
2668
3225
  const msg2 = `Wrote ${file}
2669
3226
  Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
@@ -2892,6 +3449,13 @@ function buildExitCommand(opts) {
2892
3449
  aliases: ["quit", "q"],
2893
3450
  description: "Exit the REPL.",
2894
3451
  async run() {
3452
+ if (opts.onBeforeExit) {
3453
+ const result = await opts.onBeforeExit();
3454
+ if (result?.abort) {
3455
+ opts.onExit?.();
3456
+ return { message: result.message ?? "", exit: true };
3457
+ }
3458
+ }
2895
3459
  opts.onExit?.();
2896
3460
  return { exit: true };
2897
3461
  }
@@ -3264,11 +3828,11 @@ ${lines.join("\n\n")}
3264
3828
  };
3265
3829
  }
3266
3830
  function makeInstaller(opts, projectRoot, global) {
3267
- const globalRoot = path17.join(os4.homedir(), ".wrongstack");
3831
+ const globalRoot = path20.join(os6.homedir(), ".wrongstack");
3268
3832
  return new SkillInstaller({
3269
- manifestPath: path17.join(globalRoot, "installed-skills.json"),
3270
- projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
3271
- globalSkillsDir: path17.join(globalRoot, "skills"),
3833
+ manifestPath: path20.join(globalRoot, "installed-skills.json"),
3834
+ projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
3835
+ globalSkillsDir: path20.join(globalRoot, "skills"),
3272
3836
  projectHash: projectHash(projectRoot),
3273
3837
  skillLoader: opts.skillLoader
3274
3838
  });
@@ -3457,7 +4021,10 @@ function buildBuiltinSlashCommands(opts) {
3457
4021
  buildYoloCommand(opts),
3458
4022
  buildAutonomyCommand(opts),
3459
4023
  buildModeCommand(opts),
3460
- buildExitCommand(opts)
4024
+ buildExitCommand(opts),
4025
+ buildCommitCommand(),
4026
+ buildGitcheckCommand(),
4027
+ buildPushCommand()
3461
4028
  ];
3462
4029
  }
3463
4030
 
@@ -3476,13 +4043,13 @@ var MANIFESTS = [
3476
4043
  ];
3477
4044
  async function detectProjectKind(projectRoot) {
3478
4045
  try {
3479
- await fs14.access(path17.join(projectRoot, ".wrongstack", "AGENTS.md"));
4046
+ await fs3.access(path20.join(projectRoot, ".wrongstack", "AGENTS.md"));
3480
4047
  return "initialized";
3481
4048
  } catch {
3482
4049
  }
3483
4050
  for (const m of MANIFESTS) {
3484
4051
  try {
3485
- await fs14.access(path17.join(projectRoot, m));
4052
+ await fs3.access(path20.join(projectRoot, m));
3486
4053
  return "project";
3487
4054
  } catch {
3488
4055
  }
@@ -3490,12 +4057,12 @@ async function detectProjectKind(projectRoot) {
3490
4057
  return "empty";
3491
4058
  }
3492
4059
  async function scaffoldAgentsMd(projectRoot) {
3493
- const dir = path17.join(projectRoot, ".wrongstack");
3494
- const file = path17.join(dir, "AGENTS.md");
4060
+ const dir = path20.join(projectRoot, ".wrongstack");
4061
+ const file = path20.join(dir, "AGENTS.md");
3495
4062
  const facts = await detectProjectFacts(projectRoot);
3496
4063
  const body = renderAgentsTemplate(facts);
3497
- await fs14.mkdir(dir, { recursive: true });
3498
- await fs14.writeFile(file, body, "utf8");
4064
+ await fs3.mkdir(dir, { recursive: true });
4065
+ await fs3.writeFile(file, body, "utf8");
3499
4066
  return file;
3500
4067
  }
3501
4068
  async function runProjectCheck(opts) {
@@ -3504,7 +4071,7 @@ async function runProjectCheck(opts) {
3504
4071
  if (kind === "initialized") {
3505
4072
  renderer.write(
3506
4073
  `
3507
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path17.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4074
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path20.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
3508
4075
  `
3509
4076
  );
3510
4077
  return true;
@@ -3531,11 +4098,43 @@ async function runProjectCheck(opts) {
3531
4098
  }
3532
4099
  return true;
3533
4100
  }
3534
- renderer.write(
3535
- `
4101
+ const gitDir = path20.join(projectRoot, ".git");
4102
+ let hasGit = false;
4103
+ try {
4104
+ await fs3.access(gitDir);
4105
+ hasGit = true;
4106
+ } catch {
4107
+ }
4108
+ if (!hasGit) {
4109
+ renderer.write(
4110
+ `
3536
4111
  ${color.dim("\u25CB")} ${color.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
3537
4112
  `
3538
- );
4113
+ );
4114
+ const answer2 = (await reader.readLine(
4115
+ ` ${color.amber("?")} No git repo found. ${color.bold("Initialize git?")} ${color.dim("[y/N]")} `
4116
+ )).trim().toLowerCase();
4117
+ if (answer2 === "y" || answer2 === "yes") {
4118
+ try {
4119
+ const { spawn: spawn3 } = await import('child_process');
4120
+ await new Promise((resolve4, reject) => {
4121
+ const child = spawn3("git", ["init"], { cwd: projectRoot });
4122
+ child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
4123
+ });
4124
+ renderer.write(` ${color.green("\u2713")} Git repository initialized
4125
+ `);
4126
+ } catch (err) {
4127
+ renderer.writeError(`git init failed: ${err instanceof Error ? err.message : String(err)}
4128
+ `);
4129
+ }
4130
+ }
4131
+ } else {
4132
+ renderer.write(
4133
+ `
4134
+ ${color.dim("\u25CB")} ${color.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
4135
+ `
4136
+ );
4137
+ }
3539
4138
  const answer = (await reader.readLine(` ${color.amber("?")} Continue anyway? ${color.dim("[Y/n]")} `)).trim().toLowerCase();
3540
4139
  if (answer === "n" || answer === "no") {
3541
4140
  renderer.write(color.dim(" Cancelled.\n"));
@@ -3800,14 +4399,14 @@ function summarize(value, name) {
3800
4399
  if (typeof v === "object" && v !== null) {
3801
4400
  const o = v;
3802
4401
  if (name === "edit") {
3803
- const path18 = typeof o["path"] === "string" ? o["path"] : "";
4402
+ const path21 = typeof o["path"] === "string" ? o["path"] : "";
3804
4403
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
3805
- return `${path18} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4404
+ return `${path21} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
3806
4405
  }
3807
4406
  if (name === "write") {
3808
- const path18 = typeof o["path"] === "string" ? o["path"] : "";
4407
+ const path21 = typeof o["path"] === "string" ? o["path"] : "";
3809
4408
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
3810
- return bytes !== void 0 ? `${path18} ${bytes}B` : path18;
4409
+ return bytes !== void 0 ? `${path21} ${bytes}B` : path21;
3811
4410
  }
3812
4411
  if (typeof o["count"] === "number") {
3813
4412
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -4403,7 +5002,7 @@ async function readKeyInput(deps, intent) {
4403
5002
  async function loadProviders(deps) {
4404
5003
  let raw;
4405
5004
  try {
4406
- raw = await fs14.readFile(deps.globalConfigPath, "utf8");
5005
+ raw = await fs3.readFile(deps.globalConfigPath, "utf8");
4407
5006
  } catch {
4408
5007
  return {};
4409
5008
  }
@@ -4419,7 +5018,7 @@ async function loadProviders(deps) {
4419
5018
  async function mutateProviders(deps, mutator) {
4420
5019
  let raw;
4421
5020
  try {
4422
- raw = await fs14.readFile(deps.globalConfigPath, "utf8");
5021
+ raw = await fs3.readFile(deps.globalConfigPath, "utf8");
4423
5022
  } catch {
4424
5023
  raw = "{}";
4425
5024
  }
@@ -4456,6 +5055,66 @@ var authCmd = async (args, deps) => {
4456
5055
  envVars: flags.envVars
4457
5056
  });
4458
5057
  };
5058
+
5059
+ // src/subcommands/handlers/update.ts
5060
+ init_update_check();
5061
+ var updateCmd = async (args, deps) => {
5062
+ const cwd = deps.cwd;
5063
+ const checkOnly = args.includes("--check-only") || args.includes("-c");
5064
+ const info = await checkForUpdate();
5065
+ if (checkOnly) {
5066
+ if (info.outdated) {
5067
+ deps.renderer.write(`Update available: v${info.current} \u2192 v${info.latest}
5068
+ `);
5069
+ } else {
5070
+ deps.renderer.write(`You are on the latest version: v${info.current}
5071
+ `);
5072
+ }
5073
+ return 0;
5074
+ }
5075
+ if (!info.outdated) {
5076
+ deps.renderer.write(`You are already on the latest version: v${info.current}
5077
+ `);
5078
+ return 0;
5079
+ }
5080
+ deps.renderer.write(`Updating wrongstack from v${info.current} to v${info.latest}...
5081
+ `);
5082
+ try {
5083
+ const result = await new Promise((resolve4) => {
5084
+ const child = spawn("npm", ["install", "-g", "wrongstack@latest"], {
5085
+ cwd,
5086
+ stdio: "pipe"
5087
+ });
5088
+ let stderr = "";
5089
+ child.stderr?.on("data", (d) => {
5090
+ stderr += d;
5091
+ });
5092
+ child.on("close", (code) => resolve4({ code: code ?? 0 }));
5093
+ });
5094
+ if (result.code === 0) {
5095
+ deps.renderer.write(`
5096
+ Updated to v${info.latest}. Restart wrongstack to use the new version.
5097
+ `);
5098
+ } else {
5099
+ deps.renderer.write(`
5100
+ Update failed with exit code ${result.code}.
5101
+ `);
5102
+ }
5103
+ return result.code;
5104
+ } catch (err) {
5105
+ const msg = err instanceof Error ? err.message : String(err);
5106
+ if (msg.includes("ENOENT")) {
5107
+ deps.renderer.write(`
5108
+ Update failed: npm not found in PATH.
5109
+ `);
5110
+ return 1;
5111
+ }
5112
+ deps.renderer.write(`
5113
+ Update failed: ${msg}
5114
+ `);
5115
+ return 1;
5116
+ }
5117
+ };
4459
5118
  var req = createRequire(import.meta.url);
4460
5119
  function readOwnVersion() {
4461
5120
  const candidates = ["../package.json", "../../package.json"];
@@ -4491,7 +5150,7 @@ var diagCmd = async (_args, deps) => {
4491
5150
  ` modelsCache: ${deps.paths.modelsCache}`,
4492
5151
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
4493
5152
  ` node: ${process.version}`,
4494
- ` os: ${os4.platform()} ${os4.release()}`,
5153
+ ` os: ${os6.platform()} ${os6.release()}`,
4495
5154
  ` provider: ${cfg.provider ?? "<unset>"}`,
4496
5155
  ` model: ${cfg.model ?? "<unset>"}`,
4497
5156
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -4559,7 +5218,7 @@ var doctorCmd = async (_args, deps) => {
4559
5218
  });
4560
5219
  }
4561
5220
  try {
4562
- await fs14.access(deps.paths.secretsKey);
5221
+ await fs3.access(deps.paths.secretsKey);
4563
5222
  checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
4564
5223
  } catch {
4565
5224
  checks.push({
@@ -4569,10 +5228,10 @@ var doctorCmd = async (_args, deps) => {
4569
5228
  });
4570
5229
  }
4571
5230
  try {
4572
- await fs14.mkdir(deps.paths.projectSessions, { recursive: true });
4573
- const probe = path17.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
4574
- await fs14.writeFile(probe, "");
4575
- await fs14.unlink(probe);
5231
+ await fs3.mkdir(deps.paths.projectSessions, { recursive: true });
5232
+ const probe = path20.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5233
+ await fs3.writeFile(probe, "");
5234
+ await fs3.unlink(probe);
4576
5235
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
4577
5236
  } catch (err) {
4578
5237
  checks.push({
@@ -4673,8 +5332,8 @@ var exportCmd = async (args, deps) => {
4673
5332
  return 1;
4674
5333
  }
4675
5334
  if (output) {
4676
- await fs14.mkdir(path17.dirname(path17.resolve(deps.cwd, output)), { recursive: true });
4677
- await fs14.writeFile(path17.resolve(deps.cwd, output), rendered, "utf8");
5335
+ await fs3.mkdir(path20.dirname(path20.resolve(deps.cwd, output)), { recursive: true });
5336
+ await fs3.writeFile(path20.resolve(deps.cwd, output), rendered, "utf8");
4678
5337
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
4679
5338
  `);
4680
5339
  } else {
@@ -4731,17 +5390,17 @@ var initCmd = async (_args, deps) => {
4731
5390
  } else {
4732
5391
  deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
4733
5392
  }
4734
- await fs14.mkdir(deps.paths.globalRoot, { recursive: true });
5393
+ await fs3.mkdir(deps.paths.globalRoot, { recursive: true });
4735
5394
  const config = { version: 1, provider: providerId, model: modelId };
4736
5395
  if (apiKey) config.apiKey = apiKey;
4737
- const keyFile = path17.join(path17.dirname(deps.paths.globalConfig), ".key");
5396
+ const keyFile = path20.join(path20.dirname(deps.paths.globalConfig), ".key");
4738
5397
  const vault = new DefaultSecretVault$1({ keyFile });
4739
5398
  const encrypted = encryptConfigSecrets(config, vault);
4740
5399
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
4741
- await fs14.mkdir(path17.join(deps.projectRoot, ".wrongstack"), { recursive: true });
4742
- const agentsFile = path17.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
5400
+ await fs3.mkdir(path20.join(deps.projectRoot, ".wrongstack"), { recursive: true });
5401
+ const agentsFile = path20.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
4743
5402
  try {
4744
- await fs14.access(agentsFile);
5403
+ await fs3.access(agentsFile);
4745
5404
  } catch {
4746
5405
  const detected2 = await detectProjectFacts(deps.projectRoot);
4747
5406
  await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
@@ -4817,7 +5476,7 @@ async function addMcpServer(args, deps) {
4817
5476
  serverCfg.enabled = enable;
4818
5477
  let existing = {};
4819
5478
  try {
4820
- existing = JSON.parse(await fs14.readFile(deps.paths.globalConfig, "utf8"));
5479
+ existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
4821
5480
  } catch {
4822
5481
  }
4823
5482
  const mcpServers = existing.mcpServers ?? {};
@@ -4837,7 +5496,7 @@ async function addMcpServer(args, deps) {
4837
5496
  async function removeMcpServer(name, deps) {
4838
5497
  let existing = {};
4839
5498
  try {
4840
- existing = JSON.parse(await fs14.readFile(deps.paths.globalConfig, "utf8"));
5499
+ existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
4841
5500
  } catch {
4842
5501
  deps.renderer.writeError("No config file found.\n");
4843
5502
  return 1;
@@ -4958,7 +5617,7 @@ function renderConfiguredPlugins(config) {
4958
5617
  }
4959
5618
  async function readConfig(file) {
4960
5619
  try {
4961
- return JSON.parse(await fs14.readFile(file, "utf8"));
5620
+ return JSON.parse(await fs3.readFile(file, "utf8"));
4962
5621
  } catch {
4963
5622
  return {};
4964
5623
  }
@@ -5049,9 +5708,9 @@ var usageCmd = async (_args, deps) => {
5049
5708
  return 0;
5050
5709
  };
5051
5710
  var projectsCmd = async (_args, deps) => {
5052
- const projectsRoot = path17.join(deps.paths.globalRoot, "projects");
5711
+ const projectsRoot = path20.join(deps.paths.globalRoot, "projects");
5053
5712
  try {
5054
- const entries = await fs14.readdir(projectsRoot);
5713
+ const entries = await fs3.readdir(projectsRoot);
5055
5714
  if (entries.length === 0) {
5056
5715
  deps.renderer.write("No projects tracked.\n");
5057
5716
  return 0;
@@ -5059,7 +5718,7 @@ var projectsCmd = async (_args, deps) => {
5059
5718
  for (const hash of entries) {
5060
5719
  try {
5061
5720
  const meta = JSON.parse(
5062
- await fs14.readFile(path17.join(projectsRoot, hash, "meta.json"), "utf8")
5721
+ await fs3.readFile(path20.join(projectsRoot, hash, "meta.json"), "utf8")
5063
5722
  );
5064
5723
  deps.renderer.write(
5065
5724
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -5233,9 +5892,219 @@ var configCmd = async (args, deps) => {
5233
5892
  `);
5234
5893
  return 0;
5235
5894
  }
5236
- deps.renderer.writeError(`Unknown config subcommand: ${sub}`);
5895
+ if (sub === "history") {
5896
+ return runHistory(args.slice(1), deps);
5897
+ }
5898
+ if (sub === "restore") {
5899
+ return runRestore(args.slice(1), deps);
5900
+ }
5901
+ deps.renderer.writeError(`Unknown config subcommand: ${sub}
5902
+ `);
5237
5903
  return 1;
5238
5904
  };
5905
+ function extractArg(args, key) {
5906
+ const idx = args.indexOf(key);
5907
+ if (idx !== -1 && args[idx + 1] !== void 0) return args[idx + 1];
5908
+ const eq = key.startsWith("--") ? args.find((a) => a.startsWith(`${key}=`)) : null;
5909
+ if (eq) return eq.slice(eq.indexOf("=") + 1);
5910
+ return null;
5911
+ }
5912
+ async function runHistory(args, deps) {
5913
+ const idFlag = extractArg(args, "--id");
5914
+ if (idFlag) {
5915
+ const entry = await getHistoryEntry(idFlag);
5916
+ if (!entry) {
5917
+ deps.renderer.writeError(`History entry '${idFlag}' not found.
5918
+ `);
5919
+ return 1;
5920
+ }
5921
+ deps.renderer.write(
5922
+ [
5923
+ `ID: ${entry.id}`,
5924
+ `Time: ${new Date(entry.timestamp).toLocaleString()}`,
5925
+ `Change: ${entry.description}`,
5926
+ `Diff: ${entry.diffSummary}`,
5927
+ "",
5928
+ "Snapshot (secrets masked):",
5929
+ JSON.stringify(entry.snapshotMasked, null, 2)
5930
+ ].join("\n") + "\n"
5931
+ );
5932
+ return 0;
5933
+ }
5934
+ const entries = await listHistory();
5935
+ if (entries.length === 0) {
5936
+ deps.renderer.write("No config history yet.\n");
5937
+ return 0;
5938
+ }
5939
+ deps.renderer.write(
5940
+ [
5941
+ color.bold("Config History"),
5942
+ "",
5943
+ ...entries.map((e, i) => {
5944
+ const ts = new Date(e.timestamp).toLocaleString();
5945
+ const desc = e.description.length > 60 ? e.description.slice(0, 60) + "\u2026" : e.description;
5946
+ return ` [${i + 1}] ${e.id} ${color.dim(ts)}
5947
+ ${desc}`;
5948
+ }),
5949
+ "",
5950
+ " Run `wrongstack config history --id <id>` for details.",
5951
+ " Run `wrongstack config restore <id>` to restore."
5952
+ ].join("\n") + "\n"
5953
+ );
5954
+ return 0;
5955
+ }
5956
+ async function runRestore(args, deps) {
5957
+ const latest = args.includes("--latest") || args.includes("-l");
5958
+ const id = extractArg(args, "--id") ?? (args[0] && !args[0].startsWith("-") ? args[0] : null);
5959
+ if (latest) {
5960
+ const result2 = await restoreLast();
5961
+ if (!result2.ok) {
5962
+ deps.renderer.writeError(`Restore failed: ${result2.error}
5963
+ `);
5964
+ return 1;
5965
+ }
5966
+ deps.renderer.write("Restored from config.json.last.\n");
5967
+ return 0;
5968
+ }
5969
+ if (!id) {
5970
+ deps.renderer.write("Usage: wrongstack config restore <id> | --latest\n");
5971
+ return 1;
5972
+ }
5973
+ const result = await restoreFromHistory(id);
5974
+ if (!result.ok) {
5975
+ deps.renderer.writeError(`Restore failed: ${result.error}
5976
+ `);
5977
+ return 1;
5978
+ }
5979
+ deps.renderer.write(`Restored to history entry '${id}'. Backup created.
5980
+ `);
5981
+ return 0;
5982
+ }
5983
+ function parseRewindFlags(args) {
5984
+ const flags = {};
5985
+ for (let i = 0; i < args.length; i++) {
5986
+ const a = args[i];
5987
+ if (a === "--all") flags.all = true;
5988
+ else if (a === "--last") flags.last = args[++i] ?? "1";
5989
+ else if (a === "--to") flags.to = args[++i] ?? "";
5990
+ else if (a === "--list") flags.list = true;
5991
+ else if (a === "--resume") flags.resume = true;
5992
+ }
5993
+ return flags;
5994
+ }
5995
+ var rewindCmd = async (args, deps) => {
5996
+ const flags = parseRewindFlags(args);
5997
+ const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
5998
+ const sessionsDir = path20.join(wpaths.globalRoot, "sessions");
5999
+ const rewind = new DefaultSessionRewinder(sessionsDir);
6000
+ let sessionId = args.find((a) => !a.startsWith("--"));
6001
+ if (!sessionId) {
6002
+ if (!deps.sessionStore) {
6003
+ deps.renderer.writeError("No session store available.");
6004
+ return 1;
6005
+ }
6006
+ const sessions = await deps.sessionStore.list(1);
6007
+ if (sessions.length === 0) {
6008
+ deps.renderer.writeError("No sessions found.");
6009
+ return 1;
6010
+ }
6011
+ sessionId = sessions[0].id;
6012
+ }
6013
+ if (flags.list) {
6014
+ deps.renderer.write(`Session: ${color.bold(sessionId)}
6015
+
6016
+ `);
6017
+ const checkpoints = await rewind.listCheckpoints(sessionId);
6018
+ if (checkpoints.length === 0) {
6019
+ deps.renderer.write("No checkpoints in this session.\n");
6020
+ return 0;
6021
+ }
6022
+ for (const cp of checkpoints) {
6023
+ deps.renderer.write(
6024
+ ` [${cp.promptIndex}] ${color.dim(cp.ts)} ${cp.promptPreview}${cp.fileCount > 0 ? color.dim(` (${cp.fileCount} file${cp.fileCount === 1 ? "" : "s"})`) : ""}
6025
+ `
6026
+ );
6027
+ }
6028
+ return 0;
6029
+ }
6030
+ try {
6031
+ let result;
6032
+ if (flags.all) {
6033
+ deps.renderer.write("Rewinding to session start...\n");
6034
+ result = await rewind.rewindToStart(sessionId);
6035
+ } else if (flags.last) {
6036
+ const n = parseInt(flags.last, 10);
6037
+ if (isNaN(n) || n < 1) {
6038
+ deps.renderer.writeError("--last requires a positive number");
6039
+ return 1;
6040
+ }
6041
+ deps.renderer.write(`Rewinding last ${n} prompt(s)...
6042
+ `);
6043
+ result = await rewind.rewindLastN(sessionId, n);
6044
+ } else if (flags.to) {
6045
+ const idx = parseInt(flags.to, 10);
6046
+ if (isNaN(idx) || idx < 0) {
6047
+ deps.renderer.writeError("--to requires a non-negative number");
6048
+ return 1;
6049
+ }
6050
+ deps.renderer.write(`Rewinding to checkpoint ${idx}...
6051
+ `);
6052
+ result = await rewind.rewindToCheckpoint(sessionId, idx);
6053
+ } else {
6054
+ deps.renderer.write("Usage: ws rewind --all | --last N | --to <index> [--list] [--resume]\n");
6055
+ deps.renderer.write(" --all Rewind to session start\n");
6056
+ deps.renderer.write(" --last N Rewind last N prompts\n");
6057
+ deps.renderer.write(" --to N Rewind to checkpoint N\n");
6058
+ deps.renderer.write(" --list List checkpoints\n");
6059
+ deps.renderer.write(" --resume After rewind, truncate session history at checkpoint\n");
6060
+ return 1;
6061
+ }
6062
+ if (result.revertedFiles.length === 0) {
6063
+ deps.renderer.write("No files to revert.\n");
6064
+ if (flags.resume) {
6065
+ const store = new DefaultSessionStore({ dir: sessionsDir });
6066
+ const resumed = await store.resume(sessionId);
6067
+ const toIdx = result.toPromptIndex;
6068
+ await resumed.writer.truncateToCheckpoint(toIdx);
6069
+ await resumed.writer.close();
6070
+ deps.renderer.write(` ${color.green("\u2713")} Session truncated at checkpoint ${toIdx}
6071
+ `);
6072
+ }
6073
+ return 0;
6074
+ }
6075
+ deps.renderer.write(`
6076
+ Reverted ${result.revertedFiles.length} file(s):
6077
+ `);
6078
+ for (const f of result.revertedFiles) {
6079
+ deps.renderer.write(` ${color.green("\u2713")} ${f}
6080
+ `);
6081
+ }
6082
+ if (flags.resume) {
6083
+ const store = new DefaultSessionStore({ dir: sessionsDir });
6084
+ const resumed = await store.resume(sessionId);
6085
+ const toIdx = result.toPromptIndex;
6086
+ const removed = await resumed.writer.truncateToCheckpoint(toIdx);
6087
+ await resumed.writer.close();
6088
+ deps.renderer.write(`
6089
+ ${color.green("\u2713")} Session truncated \u2014 ${removed} event(s) removed
6090
+ `);
6091
+ }
6092
+ if (result.errors.length > 0) {
6093
+ deps.renderer.write(`
6094
+ ${result.errors.length} error(s):
6095
+ `);
6096
+ for (const e of result.errors) {
6097
+ deps.renderer.write(` ${color.red("\u2717")} ${e}
6098
+ `);
6099
+ }
6100
+ return 1;
6101
+ }
6102
+ return 0;
6103
+ } catch (err) {
6104
+ deps.renderer.writeError(err instanceof Error ? err.message : String(err));
6105
+ return 1;
6106
+ }
6107
+ };
5239
6108
  var toolsCmd = async (_args, deps) => {
5240
6109
  const reg = deps.toolRegistry;
5241
6110
  if (!reg) return 0;
@@ -5258,7 +6127,7 @@ var skillsCmd = async (_args, deps) => {
5258
6127
  };
5259
6128
  var versionCmd = async (_args, deps) => {
5260
6129
  deps.renderer.write(
5261
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
6130
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
5262
6131
  `
5263
6132
  );
5264
6133
  return 0;
@@ -5296,8 +6165,10 @@ var helpCmd = async (_args, deps) => {
5296
6165
  var subcommands = {
5297
6166
  init: initCmd,
5298
6167
  auth: authCmd,
6168
+ update: updateCmd,
5299
6169
  sessions: sessionsCmd,
5300
6170
  config: configCmd,
6171
+ rewind: rewindCmd,
5301
6172
  tools: toolsCmd,
5302
6173
  skills: skillsCmd,
5303
6174
  providers: providersCmd,
@@ -5334,29 +6205,32 @@ function fmtDuration(ms) {
5334
6205
  const remMin = m - h * 60;
5335
6206
  return `${h}h${remMin}m`;
5336
6207
  }
5337
- function fmtTaskResultLine(r, color30) {
6208
+ function fmtTaskResultLine(r, color32) {
5338
6209
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
5339
6210
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
5340
6211
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
5341
6212
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
5342
- const errKindChip = errKind ? color30.dim(` [${errKind}]`) : "";
5343
- const errSnip = errMsg || errKind ? `${errKindChip}${color30.dim(errTail)}` : "";
6213
+ const errKindChip = errKind ? color32.dim(` [${errKind}]`) : "";
6214
+ const errSnip = errMsg || errKind ? `${errKindChip}${color32.dim(errTail)}` : "";
5344
6215
  switch (r.status) {
5345
6216
  case "success":
5346
- return { mark: color30.green("\u2713"), stats, tail: "" };
6217
+ return { mark: color32.green("\u2713"), stats, tail: "" };
5347
6218
  case "timeout":
5348
- return { mark: color30.yellow("\u23F1"), stats: `${color30.yellow("timeout")} ${stats}`, tail: errSnip };
6219
+ return { mark: color32.yellow("\u23F1"), stats: `${color32.yellow("timeout")} ${stats}`, tail: errSnip };
5349
6220
  case "stopped":
5350
- return { mark: color30.dim("\u2298"), stats: `${color30.dim("stopped")} ${stats}`, tail: errSnip };
6221
+ return { mark: color32.dim("\u2298"), stats: `${color32.dim("stopped")} ${stats}`, tail: errSnip };
5351
6222
  case "failed":
5352
- return { mark: color30.red("\u2717"), stats: `${color30.red("failed")} ${stats}`, tail: errSnip };
6223
+ return { mark: color32.red("\u2717"), stats: `${color32.red("failed")} ${stats}`, tail: errSnip };
5353
6224
  }
5354
6225
  }
6226
+
6227
+ // src/boot.ts
6228
+ init_update_check();
5355
6229
  function resolveBundledSkillsDir() {
5356
6230
  try {
5357
6231
  const req2 = createRequire(import.meta.url);
5358
6232
  const corePkg = req2.resolve("@wrongstack/core/package.json");
5359
- return path17.join(path17.dirname(corePkg), "skills");
6233
+ return path20.join(path20.dirname(corePkg), "skills");
5360
6234
  } catch {
5361
6235
  return void 0;
5362
6236
  }
@@ -5385,6 +6259,13 @@ async function boot(argv) {
5385
6259
  cacheFile: wpaths.modelsCache,
5386
6260
  ttlSeconds: 24 * 3600
5387
6261
  });
6262
+ let updateInfo;
6263
+ if (!flags["no-check"] && !process.env["WRONGSTACK_NO_CHECK"]) {
6264
+ checkForUpdate().then((info) => {
6265
+ updateInfo = info;
6266
+ }).catch(() => {
6267
+ });
6268
+ }
5388
6269
  const first = positional[0];
5389
6270
  if (first && subcommands[first]) {
5390
6271
  const container = createDefaultContainer({
@@ -5493,7 +6374,8 @@ async function boot(argv) {
5493
6374
  modelsRegistry,
5494
6375
  renderer,
5495
6376
  reader,
5496
- logger
6377
+ logger,
6378
+ updateInfo
5497
6379
  };
5498
6380
  }
5499
6381
 
@@ -6074,7 +6956,7 @@ async function execute(deps) {
6074
6956
  supportsVision,
6075
6957
  attachments,
6076
6958
  effectiveMaxContext,
6077
- projectName: path17.basename(projectRoot) || void 0,
6959
+ projectName: path20.basename(projectRoot) || void 0,
6078
6960
  getAutonomy,
6079
6961
  skillLoader
6080
6962
  });
@@ -6092,7 +6974,7 @@ async function execute(deps) {
6092
6974
  supportsVision,
6093
6975
  attachments,
6094
6976
  effectiveMaxContext,
6095
- projectName: path17.basename(projectRoot) || void 0,
6977
+ projectName: path20.basename(projectRoot) || void 0,
6096
6978
  getAutonomy,
6097
6979
  skillLoader
6098
6980
  });
@@ -6364,7 +7246,7 @@ var MultiAgentHost = class {
6364
7246
  model: opts?.model,
6365
7247
  tools: opts?.tools
6366
7248
  };
6367
- const transcriptPath = this.sessionFactory ? path17.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7249
+ const transcriptPath = this.sessionFactory ? path20.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
6368
7250
  if (this.director) {
6369
7251
  const subagentId = await this.director.spawn(subagentConfig);
6370
7252
  const taskId2 = randomUUID();
@@ -6524,16 +7406,16 @@ var MultiAgentHost = class {
6524
7406
  }
6525
7407
  this.opts.directorMode = true;
6526
7408
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
6527
- this.opts.manifestPath = path17.join(this.opts.fleetRoot, "fleet.json");
7409
+ this.opts.manifestPath = path20.join(this.opts.fleetRoot, "fleet.json");
6528
7410
  }
6529
7411
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
6530
- this.opts.sharedScratchpadPath = path17.join(this.opts.fleetRoot, "shared");
7412
+ this.opts.sharedScratchpadPath = path20.join(this.opts.fleetRoot, "shared");
6531
7413
  }
6532
7414
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
6533
- this.opts.sessionsRoot = path17.join(this.opts.fleetRoot, "subagents");
7415
+ this.opts.sessionsRoot = path20.join(this.opts.fleetRoot, "subagents");
6534
7416
  }
6535
7417
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
6536
- this.opts.stateCheckpointPath = path17.join(this.opts.fleetRoot, "director-state.json");
7418
+ this.opts.stateCheckpointPath = path20.join(this.opts.fleetRoot, "director-state.json");
6537
7419
  }
6538
7420
  await this.ensureDirector();
6539
7421
  return this.director ?? null;
@@ -6654,11 +7536,11 @@ var SessionStats = class {
6654
7536
  if (e.name === "bash") this.bashCommands++;
6655
7537
  else if (e.name === "fetch") this.fetches++;
6656
7538
  if (!e.ok) return;
6657
- const path18 = typeof input?.path === "string" ? input.path : void 0;
6658
- if (e.name === "read" && path18) this.readPaths.add(path18);
6659
- else if (e.name === "edit" && path18) this.editedPaths.add(path18);
6660
- else if (e.name === "write" && path18) {
6661
- this.writtenPaths.add(path18);
7539
+ const path21 = typeof input?.path === "string" ? input.path : void 0;
7540
+ if (e.name === "read" && path21) this.readPaths.add(path21);
7541
+ else if (e.name === "edit" && path21) this.editedPaths.add(path21);
7542
+ else if (e.name === "write" && path21) {
7543
+ this.writtenPaths.add(path21);
6662
7544
  const content = typeof input?.content === "string" ? input.content : "";
6663
7545
  this.bytesWritten += Buffer.byteLength(content, "utf8");
6664
7546
  }
@@ -6989,12 +7871,12 @@ async function setupSession(params) {
6989
7871
  }
6990
7872
  const sessionRef = { current: session };
6991
7873
  await recoveryLock.write(session.id).catch(() => void 0);
6992
- const attachments = new DefaultAttachmentStore({ spoolDir: path17.join(wpaths.projectSessions, session.id, "attachments") });
6993
- const queueStore = new QueueStore({ dir: path17.join(wpaths.projectSessions, session.id) });
7874
+ const attachments = new DefaultAttachmentStore({ spoolDir: path20.join(wpaths.projectSessions, session.id, "attachments") });
7875
+ const queueStore = new QueueStore({ dir: path20.join(wpaths.projectSessions, session.id) });
6994
7876
  const ctxSignal = new AbortController().signal;
6995
7877
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
6996
7878
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
6997
- const todosCheckpointPath = path17.join(wpaths.projectSessions, `${session.id}.todos.json`);
7879
+ const todosCheckpointPath = path20.join(wpaths.projectSessions, `${session.id}.todos.json`);
6998
7880
  if (resumeId) {
6999
7881
  try {
7000
7882
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -7006,12 +7888,12 @@ async function setupSession(params) {
7006
7888
  }
7007
7889
  }
7008
7890
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
7009
- const planPath = path17.join(wpaths.projectSessions, `${session.id}.plan.json`);
7891
+ const planPath = path20.join(wpaths.projectSessions, `${session.id}.plan.json`);
7010
7892
  context.state.setMeta("plan.path", planPath);
7011
7893
  if (resumeId) {
7012
7894
  try {
7013
- const fleetRoot = path17.join(wpaths.projectSessions, session.id);
7014
- const dirState = await loadDirectorState(path17.join(fleetRoot, "director-state.json"));
7895
+ const fleetRoot = path20.join(wpaths.projectSessions, session.id);
7896
+ const dirState = await loadDirectorState(path20.join(fleetRoot, "director-state.json"));
7015
7897
  if (dirState) {
7016
7898
  const tCounts = {};
7017
7899
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -7038,7 +7920,7 @@ function resolveBundledSkillsDir2() {
7038
7920
  try {
7039
7921
  const req2 = createRequire(import.meta.url);
7040
7922
  const corePkg = req2.resolve("@wrongstack/core/package.json");
7041
- return path17.join(path17.dirname(corePkg), "skills");
7923
+ return path20.join(path20.dirname(corePkg), "skills");
7042
7924
  } catch {
7043
7925
  return void 0;
7044
7926
  }
@@ -7069,8 +7951,28 @@ async function main(argv) {
7069
7951
  modelsRegistry,
7070
7952
  renderer,
7071
7953
  reader,
7072
- logger
7954
+ logger,
7955
+ updateInfo
7073
7956
  } = ctx;
7957
+ if (!updateInfo?.outdated) {
7958
+ const ac = new AbortController();
7959
+ const timer = setTimeout(() => ac.abort(), 2e3);
7960
+ try {
7961
+ const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
7962
+ updateInfo = await checkForUpdate2(ac.signal);
7963
+ } catch {
7964
+ } finally {
7965
+ clearTimeout(timer);
7966
+ }
7967
+ }
7968
+ if (updateInfo?.outdated) {
7969
+ process.stderr.write(
7970
+ `
7971
+ \x1B[33m\u2191 Update available: v${updateInfo.current} \u2192 v${updateInfo.latest}\x1B[0m Run \`wrongstack update\` to upgrade.
7972
+
7973
+ `
7974
+ );
7975
+ }
7074
7976
  const pathResolver = new DefaultPathResolver(cwd);
7075
7977
  const container = createDefaultContainer({
7076
7978
  config,
@@ -7125,7 +8027,7 @@ async function main(argv) {
7125
8027
  modeId,
7126
8028
  modePrompt,
7127
8029
  modelCapabilities,
7128
- planPath: () => sessionRef.current ? path17.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
8030
+ planPath: () => sessionRef.current ? path20.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
7129
8031
  })
7130
8032
  );
7131
8033
  const toolRegistry = new ToolRegistry();
@@ -7153,7 +8055,7 @@ async function main(argv) {
7153
8055
  name: "session-store",
7154
8056
  check: async () => {
7155
8057
  try {
7156
- await fs14.access(wpaths.projectSessions);
8058
+ await fs3.access(wpaths.projectSessions);
7157
8059
  return { status: "healthy" };
7158
8060
  } catch (e) {
7159
8061
  return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
@@ -7170,7 +8072,7 @@ async function main(argv) {
7170
8072
  const dumpMetrics = () => {
7171
8073
  if (!metricsSink) return;
7172
8074
  try {
7173
- const out = path17.join(wpaths.projectSessions, "metrics.json");
8075
+ const out = path20.join(wpaths.projectSessions, "metrics.json");
7174
8076
  const snap = metricsSink.snapshot();
7175
8077
  writeFileSync(out, JSON.stringify(snap, null, 2));
7176
8078
  } catch {
@@ -7389,12 +8291,12 @@ async function main(argv) {
7389
8291
  const directorMode = flags["director"] === true;
7390
8292
  let director = null;
7391
8293
  let autonomyMode = "off";
7392
- const fleetRoot = directorMode ? path17.join(wpaths.projectSessions, session.id) : void 0;
7393
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path17.join(fleetRoot, "fleet.json") : void 0;
7394
- const sharedScratchpadPath = directorMode ? path17.join(fleetRoot, "shared") : void 0;
7395
- const subagentSessionsRoot = directorMode ? path17.join(fleetRoot, "subagents") : void 0;
7396
- const stateCheckpointPath = directorMode ? path17.join(fleetRoot, "director-state.json") : void 0;
7397
- const fleetRootForPromotion = path17.join(wpaths.projectSessions, session.id);
8294
+ const fleetRoot = directorMode ? path20.join(wpaths.projectSessions, session.id) : void 0;
8295
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path20.join(fleetRoot, "fleet.json") : void 0;
8296
+ const sharedScratchpadPath = directorMode ? path20.join(fleetRoot, "shared") : void 0;
8297
+ const subagentSessionsRoot = directorMode ? path20.join(fleetRoot, "subagents") : void 0;
8298
+ const stateCheckpointPath = directorMode ? path20.join(fleetRoot, "director-state.json") : void 0;
8299
+ const fleetRootForPromotion = path20.join(wpaths.projectSessions, session.id);
7398
8300
  const multiAgentHost = new MultiAgentHost(
7399
8301
  {
7400
8302
  container,
@@ -7573,27 +8475,27 @@ async function main(argv) {
7573
8475
  return `Unknown fleet action: ${action}`;
7574
8476
  },
7575
8477
  onFleetLog: async (subagentId, mode) => {
7576
- const subagentsRoot = path17.join(fleetRootForPromotion, "subagents");
8478
+ const subagentsRoot = path20.join(fleetRootForPromotion, "subagents");
7577
8479
  let runDirs;
7578
8480
  try {
7579
- runDirs = await fs14.readdir(subagentsRoot);
8481
+ runDirs = await fs3.readdir(subagentsRoot);
7580
8482
  } catch {
7581
8483
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
7582
8484
  }
7583
8485
  const found = [];
7584
8486
  for (const runId of runDirs) {
7585
- const runDir = path17.join(subagentsRoot, runId);
8487
+ const runDir = path20.join(subagentsRoot, runId);
7586
8488
  let files;
7587
8489
  try {
7588
- files = await fs14.readdir(runDir);
8490
+ files = await fs3.readdir(runDir);
7589
8491
  } catch {
7590
8492
  continue;
7591
8493
  }
7592
8494
  for (const f of files) {
7593
8495
  if (!f.endsWith(".jsonl")) continue;
7594
- const full = path17.join(runDir, f);
8496
+ const full = path20.join(runDir, f);
7595
8497
  try {
7596
- const stat2 = await fs14.stat(full);
8498
+ const stat2 = await fs3.stat(full);
7597
8499
  found.push({
7598
8500
  runId,
7599
8501
  subagentId: f.replace(/\.jsonl$/, ""),
@@ -7632,7 +8534,7 @@ async function main(argv) {
7632
8534
  ].join("\n");
7633
8535
  }
7634
8536
  const t = matches[0];
7635
- const raw = await fs14.readFile(t.file, "utf8");
8537
+ const raw = await fs3.readFile(t.file, "utf8");
7636
8538
  if (mode === "raw") return raw;
7637
8539
  const lines = raw.split("\n").filter((l) => l.trim());
7638
8540
  const counts = {};
@@ -7688,7 +8590,7 @@ async function main(argv) {
7688
8590
  }
7689
8591
  const dir = await multiAgentHost.ensureDirector();
7690
8592
  if (!dir) return "Director is not available.";
7691
- const dirStatePath = path17.join(fleetRootForPromotion, "director-state.json");
8593
+ const dirStatePath = path20.join(fleetRootForPromotion, "director-state.json");
7692
8594
  const prior = await loadDirectorState(dirStatePath);
7693
8595
  if (!prior) {
7694
8596
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -7759,9 +8661,9 @@ async function main(argv) {
7759
8661
  for (const tool of director2.tools(FLEET_ROSTER)) {
7760
8662
  toolRegistry.register(tool);
7761
8663
  }
7762
- const mp = path17.join(fleetRootForPromotion, "fleet.json");
7763
- const sp = path17.join(fleetRootForPromotion, "shared");
7764
- const ss = path17.join(fleetRootForPromotion, "subagents");
8664
+ const mp = path20.join(fleetRootForPromotion, "fleet.json");
8665
+ const sp = path20.join(fleetRootForPromotion, "shared");
8666
+ const ss = path20.join(fleetRootForPromotion, "subagents");
7765
8667
  const lines = [
7766
8668
  `${color.green("\u2713")} Promoted to director mode.`,
7767
8669
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -7807,6 +8709,24 @@ Restart WrongStack to load or unload plugin code in this session.`;
7807
8709
  onExit: () => {
7808
8710
  void mcpRegistry.stopAll();
7809
8711
  },
8712
+ onBeforeExit: async () => {
8713
+ const { spawn: spawn3 } = await import('child_process');
8714
+ const cwd2 = projectRoot;
8715
+ const statusResult = await new Promise((resolve4) => {
8716
+ const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
8717
+ let stdout = "";
8718
+ child.stdout?.on("data", (d) => stdout += d);
8719
+ child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
8720
+ });
8721
+ if (statusResult.stdout.trim().length > 0) {
8722
+ const lines = statusResult.stdout.split("\n").filter(Boolean);
8723
+ return {
8724
+ abort: true,
8725
+ // signals there are uncommitted changes (used only for the message)
8726
+ message: `\u26A0 ${color.yellow(`${lines.length} uncommitted change${lines.length > 1 ? "s" : ""}`)} \u2014 session ended without commit`
8727
+ };
8728
+ }
8729
+ },
7810
8730
  onClear: () => {
7811
8731
  if (flags.tui && !flags["no-tui"]) return;
7812
8732
  try {
@@ -7834,7 +8754,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
7834
8754
  ...errSection
7835
8755
  ].join("\n");
7836
8756
  },
7837
- onStats: () => stats.format()
8757
+ onStats: () => stats.format(),
8758
+ generateCommitMessage: async (diff) => {
8759
+ return generateCommitMessageWithLLM(diff, {
8760
+ provider: context.provider,
8761
+ model: context.model
8762
+ });
8763
+ }
7838
8764
  });
7839
8765
  for (const cmd of slashCmds) slashRegistry.register(cmd);
7840
8766
  const savedProviderCfg = config.providers?.[config.provider];