@wrongstack/cli 0.5.2 → 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,18 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import * as path18 from 'path';
2
+ import * as path20 from 'path';
3
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 fs5 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';
17
18
  import { spawn } from 'child_process';
18
19
  import { SkillInstaller } from '@wrongstack/core/skills';
@@ -22,6 +23,12 @@ var __defProp = Object.defineProperty;
22
23
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
23
24
  var __getOwnPropNames = Object.getOwnPropertyNames;
24
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
+ });
25
32
  var __esm = (fn, res) => function __init() {
26
33
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
27
34
  };
@@ -237,8 +244,8 @@ function buildSddCommand(opts) {
237
244
  async run(args) {
238
245
  const ctx = opts.context;
239
246
  const projectRoot = ctx?.projectRoot ?? process.cwd();
240
- const specsDir = path18.join(projectRoot, ".wrongstack", "specs");
241
- const graphsDir = path18.join(projectRoot, ".wrongstack", "task-graphs");
247
+ const specsDir = path20.join(projectRoot, ".wrongstack", "specs");
248
+ const graphsDir = path20.join(projectRoot, ".wrongstack", "task-graphs");
242
249
  const specStore = new SpecStore({ baseDir: specsDir });
243
250
  new TaskGraphStore({ baseDir: graphsDir });
244
251
  const versioning = new SpecVersioning();
@@ -254,7 +261,7 @@ function buildSddCommand(opts) {
254
261
  const forceFlag = rest.includes("--force") || rest.includes("-f");
255
262
  const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
256
263
  if (!activeBuilder && !forceFlag) {
257
- const sessionPath = path18.join(projectRoot, ".wrongstack", "sdd-session.json");
264
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
258
265
  try {
259
266
  const fsp = await import('fs/promises');
260
267
  await fsp.access(sessionPath);
@@ -292,7 +299,7 @@ function buildSddCommand(opts) {
292
299
  projectContext,
293
300
  minQuestions: 2,
294
301
  maxQuestions: 10,
295
- sessionPath: path18.join(projectRoot, ".wrongstack", "sdd-session.json")
302
+ sessionPath: path20.join(projectRoot, ".wrongstack", "sdd-session.json")
296
303
  });
297
304
  activeBuilder.startSession(title);
298
305
  const aiPrompt = activeBuilder.getAIPrompt();
@@ -552,7 +559,7 @@ Start executing the tasks one by one.`
552
559
  };
553
560
  }
554
561
  case "cancel": {
555
- const sessionPath = path18.join(projectRoot, ".wrongstack", "sdd-session.json");
562
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
556
563
  let deletedFromDisk = false;
557
564
  try {
558
565
  const fsp = await import('fs/promises');
@@ -578,7 +585,7 @@ Start executing the tasks one by one.`
578
585
  if (activeBuilder) {
579
586
  return { message: "An SDD session is already active. Use /sdd cancel first." };
580
587
  }
581
- const sessionPath = path18.join(projectRoot, ".wrongstack", "sdd-session.json");
588
+ const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
582
589
  const projectContext = await gatherProjectContext(projectRoot);
583
590
  activeBuilder = new AISpecBuilder({
584
591
  store: specStore,
@@ -804,7 +811,7 @@ async function gatherProjectContext(projectRoot) {
804
811
  const parts = [];
805
812
  try {
806
813
  const fsp = await import('fs/promises');
807
- const pkgPath = path18.join(projectRoot, "package.json");
814
+ const pkgPath = path20.join(projectRoot, "package.json");
808
815
  const pkgRaw = await fsp.readFile(pkgPath, "utf8");
809
816
  const pkg = JSON.parse(pkgRaw);
810
817
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -821,14 +828,14 @@ async function gatherProjectContext(projectRoot) {
821
828
  }
822
829
  try {
823
830
  const fsp = await import('fs/promises');
824
- const tsconfigPath = path18.join(projectRoot, "tsconfig.json");
831
+ const tsconfigPath = path20.join(projectRoot, "tsconfig.json");
825
832
  await fsp.access(tsconfigPath);
826
833
  parts.push("Language: TypeScript");
827
834
  } catch {
828
835
  }
829
836
  try {
830
837
  const fsp = await import('fs/promises');
831
- const srcDir = path18.join(projectRoot, "src");
838
+ const srcDir = path20.join(projectRoot, "src");
832
839
  const entries = await fsp.readdir(srcDir, { withFileTypes: true });
833
840
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
834
841
  if (dirs.length > 0) {
@@ -900,6 +907,130 @@ var init_provider_config_utils = __esm({
900
907
  }
901
908
  });
902
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
+
903
1034
  // src/webui-server.ts
904
1035
  var webui_server_exports = {};
905
1036
  __export(webui_server_exports, {
@@ -1006,7 +1137,7 @@ async function runWebUI(opts) {
1006
1137
  })
1007
1138
  );
1008
1139
  }
1009
- return new Promise((resolve3) => {
1140
+ return new Promise((resolve4) => {
1010
1141
  wss.on("listening", () => {
1011
1142
  console.log(`[WebUI] WebSocket server running on ws://localhost:${port}`);
1012
1143
  setupEvents();
@@ -1076,7 +1207,7 @@ async function runWebUI(opts) {
1076
1207
  clients.clear();
1077
1208
  wss.close(() => {
1078
1209
  console.log("[WebUI] Server stopped");
1079
- resolve3();
1210
+ resolve4();
1080
1211
  });
1081
1212
  }
1082
1213
  process.on("SIGINT", shutdown);
@@ -1382,7 +1513,7 @@ async function runWebUI(opts) {
1382
1513
  if (!opts.globalConfigPath) return {};
1383
1514
  let raw;
1384
1515
  try {
1385
- raw = await fs5.readFile(opts.globalConfigPath, "utf8");
1516
+ raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1386
1517
  } catch {
1387
1518
  return {};
1388
1519
  }
@@ -1393,7 +1524,7 @@ async function runWebUI(opts) {
1393
1524
  return {};
1394
1525
  }
1395
1526
  if (!parsed.providers) return {};
1396
- const keyFile = path18.join(path18.dirname(opts.globalConfigPath), ".key");
1527
+ const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1397
1528
  const vault = new DefaultSecretVault$1({ keyFile });
1398
1529
  return decryptConfigSecrets$1(parsed.providers, vault);
1399
1530
  }
@@ -1401,7 +1532,7 @@ async function runWebUI(opts) {
1401
1532
  if (!opts.globalConfigPath) return;
1402
1533
  let raw;
1403
1534
  try {
1404
- raw = await fs5.readFile(opts.globalConfigPath, "utf8");
1535
+ raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1405
1536
  } catch {
1406
1537
  raw = "{}";
1407
1538
  }
@@ -1412,7 +1543,7 @@ async function runWebUI(opts) {
1412
1543
  parsed = {};
1413
1544
  }
1414
1545
  parsed.providers = providers;
1415
- const keyFile = path18.join(path18.dirname(opts.globalConfigPath), ".key");
1546
+ const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1416
1547
  const vault = new DefaultSecretVault$1({ keyFile });
1417
1548
  const encrypted = encryptConfigSecrets(parsed, vault);
1418
1549
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -1440,6 +1571,37 @@ var init_plugin_api_factory = __esm({
1440
1571
  }
1441
1572
  });
1442
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
+
1443
1605
  // src/arg-parser.ts
1444
1606
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1445
1607
  "yolo",
@@ -1458,7 +1620,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1458
1620
  "output-json",
1459
1621
  "prompt",
1460
1622
  "metrics",
1461
- "webui"
1623
+ "webui",
1624
+ "no-check"
1462
1625
  ]);
1463
1626
  function parseArgs(argv) {
1464
1627
  const flags = {};
@@ -1564,10 +1727,10 @@ function parseSpawnFlags(input) {
1564
1727
  return { description: rest.trim(), opts };
1565
1728
  }
1566
1729
  async function bootConfig(flags) {
1567
- const cwd = typeof flags["cwd"] === "string" ? path18.resolve(flags["cwd"]) : process.cwd();
1730
+ const cwd = typeof flags["cwd"] === "string" ? path20.resolve(flags["cwd"]) : process.cwd();
1568
1731
  const pathResolver = new DefaultPathResolver(cwd);
1569
1732
  const projectRoot = pathResolver.projectRoot;
1570
- const userHome = os4.homedir();
1733
+ const userHome = os6.homedir();
1571
1734
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
1572
1735
  await ensureProjectMeta(wpaths, projectRoot);
1573
1736
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -1615,13 +1778,13 @@ function flagsToConfigPatch(flags) {
1615
1778
  }
1616
1779
  async function ensureProjectMeta(paths, projectRoot) {
1617
1780
  try {
1618
- await fs5.mkdir(paths.projectDir, { recursive: true });
1781
+ await fs3.mkdir(paths.projectDir, { recursive: true });
1619
1782
  const meta = {
1620
1783
  hash: paths.projectHash,
1621
1784
  root: projectRoot,
1622
1785
  lastSeen: (/* @__PURE__ */ new Date()).toISOString()
1623
1786
  };
1624
- await fs5.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1787
+ await fs3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1625
1788
  } catch {
1626
1789
  }
1627
1790
  }
@@ -1631,11 +1794,11 @@ var ReadlineInputReader = class {
1631
1794
  history = [];
1632
1795
  pending = false;
1633
1796
  constructor(opts = {}) {
1634
- this.historyFile = opts.historyFile ?? path18.join(os4.homedir(), ".wrongstack", "history");
1797
+ this.historyFile = opts.historyFile ?? path20.join(os6.homedir(), ".wrongstack", "history");
1635
1798
  }
1636
1799
  async loadHistory() {
1637
1800
  try {
1638
- const raw = await fs5.readFile(this.historyFile, "utf8");
1801
+ const raw = await fs3.readFile(this.historyFile, "utf8");
1639
1802
  this.history = raw.split("\n").filter(Boolean).slice(-1e3);
1640
1803
  } catch {
1641
1804
  this.history = [];
@@ -1643,8 +1806,8 @@ var ReadlineInputReader = class {
1643
1806
  }
1644
1807
  async saveHistory() {
1645
1808
  try {
1646
- await fs5.mkdir(path18.dirname(this.historyFile), { recursive: true });
1647
- await fs5.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"));
1648
1811
  } catch {
1649
1812
  }
1650
1813
  }
@@ -1662,7 +1825,7 @@ var ReadlineInputReader = class {
1662
1825
  async readLine(prompt) {
1663
1826
  if (this.history.length === 0) await this.loadHistory();
1664
1827
  while (this.pending) {
1665
- await new Promise((resolve3) => setTimeout(resolve3, 50));
1828
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
1666
1829
  }
1667
1830
  this.pending = true;
1668
1831
  try {
@@ -1672,15 +1835,15 @@ var ReadlineInputReader = class {
1672
1835
  this.rl = void 0;
1673
1836
  }
1674
1837
  const fresh = this.ensure();
1675
- return new Promise((resolve3) => {
1838
+ return new Promise((resolve4) => {
1676
1839
  fresh.question(prompt ?? "> ", (line) => {
1677
1840
  if (line.trim()) {
1678
1841
  this.history.push(line);
1679
1842
  void this.saveHistory();
1680
1843
  }
1681
- resolve3(line);
1844
+ resolve4(line);
1682
1845
  });
1683
- fresh.once("close", () => resolve3(""));
1846
+ fresh.once("close", () => resolve4(""));
1684
1847
  }).then((result) => {
1685
1848
  this.rl?.close();
1686
1849
  this.rl = void 0;
@@ -1692,7 +1855,7 @@ var ReadlineInputReader = class {
1692
1855
  }
1693
1856
  async readKey(prompt, options) {
1694
1857
  process.stdout.write(prompt);
1695
- return new Promise((resolve3) => {
1858
+ return new Promise((resolve4) => {
1696
1859
  const stdin = process.stdin;
1697
1860
  const wasRaw = stdin.isRaw;
1698
1861
  const wasPaused = stdin.isPaused();
@@ -1703,7 +1866,7 @@ var ReadlineInputReader = class {
1703
1866
  if (key === "") {
1704
1867
  cleanup();
1705
1868
  process.stdout.write("\n");
1706
- resolve3("");
1869
+ resolve4("");
1707
1870
  return;
1708
1871
  }
1709
1872
  const opt = options.find(
@@ -1713,12 +1876,12 @@ var ReadlineInputReader = class {
1713
1876
  cleanup();
1714
1877
  process.stdout.write(`${opt.key}
1715
1878
  `);
1716
- resolve3(opt.value);
1879
+ resolve4(opt.value);
1717
1880
  }
1718
1881
  };
1719
1882
  const onClose = () => {
1720
1883
  cleanup();
1721
- resolve3("");
1884
+ resolve4("");
1722
1885
  };
1723
1886
  const cleanup = () => {
1724
1887
  stdin.off("data", onData);
@@ -1746,7 +1909,7 @@ var ReadlineInputReader = class {
1746
1909
  this.rl?.close();
1747
1910
  this.rl = void 0;
1748
1911
  process.stdout.write(prompt);
1749
- return new Promise((resolve3) => {
1912
+ return new Promise((resolve4) => {
1750
1913
  let buf = "";
1751
1914
  const wasRaw = stdin.isRaw;
1752
1915
  stdin.setRawMode(true);
@@ -1764,7 +1927,7 @@ var ReadlineInputReader = class {
1764
1927
  cleanup();
1765
1928
  process.stdout.write(` ${dim(`[${buf.length} chars]`)}
1766
1929
  `);
1767
- resolve3(buf);
1930
+ resolve4(buf);
1768
1931
  return;
1769
1932
  }
1770
1933
  if (ch === "") {
@@ -1861,8 +2024,247 @@ async function buildPickableProviders(modelsRegistry, config) {
1861
2024
  }
1862
2025
  return out;
1863
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
+ }
1864
2236
 
1865
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
+ }
1866
2268
  async function runPicker(deps) {
1867
2269
  const { modelsRegistry, renderer, reader, config, defaultProvider, defaultModel } = deps;
1868
2270
  renderer.write(
@@ -2093,28 +2495,9 @@ async function resolveModelSelection(answer, models, provider, _registry, render
2093
2495
  `);
2094
2496
  return { provider: provider.id, model: modelId };
2095
2497
  }
2096
- var theme = { primary: color.amber };
2097
- async function saveToGlobalConfig(configPath, provider, model) {
2098
- try {
2099
- const { atomicWrite: atomicWrite6 } = await import('@wrongstack/core');
2100
- const fs15 = await import('fs/promises');
2101
- let existing = {};
2102
- try {
2103
- const raw = await fs15.readFile(configPath, "utf8");
2104
- existing = JSON.parse(raw);
2105
- } catch {
2106
- }
2107
- existing.provider = provider;
2108
- existing.model = model;
2109
- await atomicWrite6(configPath, JSON.stringify(existing, null, 2));
2110
- return true;
2111
- } catch {
2112
- return false;
2113
- }
2114
- }
2115
2498
  async function pathExists(file) {
2116
2499
  try {
2117
- await fs5.access(file);
2500
+ await fs3.access(file);
2118
2501
  return true;
2119
2502
  } catch {
2120
2503
  return false;
@@ -2125,10 +2508,10 @@ async function detectPackageManager(root, declared) {
2125
2508
  const name = declared.split("@")[0];
2126
2509
  if (name) return name;
2127
2510
  }
2128
- if (await pathExists(path18.join(root, "pnpm-lock.yaml"))) return "pnpm";
2129
- if (await pathExists(path18.join(root, "bun.lockb"))) return "bun";
2130
- if (await pathExists(path18.join(root, "bun.lock"))) return "bun";
2131
- if (await pathExists(path18.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";
2132
2515
  return "npm";
2133
2516
  }
2134
2517
  function hasUsableScript(scripts, name) {
@@ -2149,7 +2532,7 @@ function parseMakeTargets(makefile) {
2149
2532
  async function detectProjectFacts(root) {
2150
2533
  const facts = { hints: [] };
2151
2534
  try {
2152
- const pkg = JSON.parse(await fs5.readFile(path18.join(root, "package.json"), "utf8"));
2535
+ const pkg = JSON.parse(await fs3.readFile(path20.join(root, "package.json"), "utf8"));
2153
2536
  const scripts = pkg.scripts ?? {};
2154
2537
  const pm = await detectPackageManager(root, pkg.packageManager);
2155
2538
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2163,14 +2546,14 @@ async function detectProjectFacts(root) {
2163
2546
  } catch {
2164
2547
  }
2165
2548
  try {
2166
- if (!await pathExists(path18.join(root, "pyproject.toml"))) throw new Error("not python");
2549
+ if (!await pathExists(path20.join(root, "pyproject.toml"))) throw new Error("not python");
2167
2550
  facts.test ??= "pytest";
2168
2551
  facts.lint ??= "ruff check .";
2169
2552
  facts.hints.push("pyproject.toml");
2170
2553
  } catch {
2171
2554
  }
2172
2555
  try {
2173
- if (!await pathExists(path18.join(root, "go.mod"))) throw new Error("not go");
2556
+ if (!await pathExists(path20.join(root, "go.mod"))) throw new Error("not go");
2174
2557
  facts.build ??= "go build ./...";
2175
2558
  facts.test ??= "go test ./...";
2176
2559
  facts.run ??= "go run .";
@@ -2178,7 +2561,7 @@ async function detectProjectFacts(root) {
2178
2561
  } catch {
2179
2562
  }
2180
2563
  try {
2181
- if (!await pathExists(path18.join(root, "Cargo.toml"))) throw new Error("not rust");
2564
+ if (!await pathExists(path20.join(root, "Cargo.toml"))) throw new Error("not rust");
2182
2565
  facts.build ??= "cargo build";
2183
2566
  facts.test ??= "cargo test";
2184
2567
  facts.lint ??= "cargo clippy";
@@ -2187,7 +2570,7 @@ async function detectProjectFacts(root) {
2187
2570
  } catch {
2188
2571
  }
2189
2572
  try {
2190
- const makefile = await fs5.readFile(path18.join(root, "Makefile"), "utf8");
2573
+ const makefile = await fs3.readFile(path20.join(root, "Makefile"), "utf8");
2191
2574
  const targets = parseMakeTargets(makefile);
2192
2575
  facts.build ??= targets.has("build") ? "make build" : "make";
2193
2576
  if (targets.has("test")) facts.test ??= "make test";
@@ -2322,7 +2705,7 @@ function buildClearCommand(opts) {
2322
2705
  };
2323
2706
  }
2324
2707
  async function runGit(args, cwd) {
2325
- return new Promise((resolve3) => {
2708
+ return new Promise((resolve4) => {
2326
2709
  const child = spawn("git", args, {
2327
2710
  cwd,
2328
2711
  stdio: ["ignore", "pipe", "pipe"]
@@ -2331,7 +2714,7 @@ async function runGit(args, cwd) {
2331
2714
  let stderr = "";
2332
2715
  child.stdout?.on("data", (d) => stdout += d);
2333
2716
  child.stderr?.on("data", (d) => stderr += d);
2334
- child.on("close", (code) => resolve3({ stdout, stderr, code: code ?? 0 }));
2717
+ child.on("close", (code) => resolve4({ stdout, stderr, code: code ?? 0 }));
2335
2718
  });
2336
2719
  }
2337
2720
  function detectCommitType(stats) {
@@ -2350,7 +2733,7 @@ function detectCommitType(stats) {
2350
2733
  if (hasConfig) return "chore";
2351
2734
  return "feat";
2352
2735
  }
2353
- async function generateCommitMessage(cwd) {
2736
+ async function generateCommitMessageHeuristics(cwd) {
2354
2737
  const statsResult = await runGit(["diff", "--stat"], cwd);
2355
2738
  if (statsResult.code !== 0) return "chore: update";
2356
2739
  const nameResult = await runGit(["diff", "--name-only"], cwd);
@@ -2381,7 +2764,7 @@ async function isGitRepo(cwd) {
2381
2764
  const result = await runGit(["rev-parse", "--git-dir"], cwd);
2382
2765
  return result.code === 0;
2383
2766
  }
2384
- function buildCommitCommand(_opts) {
2767
+ function buildCommitCommand(_opts, generateCommitMessage) {
2385
2768
  return {
2386
2769
  name: "commit",
2387
2770
  description: "Stage all changes and commit with auto-generated message.",
@@ -2395,7 +2778,11 @@ function buildCommitCommand(_opts) {
2395
2778
  return { message: "Nothing to commit (working tree clean)." };
2396
2779
  }
2397
2780
  const dryRun = args.includes("--dry-run") || args.includes("-n");
2398
- const message = await generateCommitMessage(cwd);
2781
+ args.includes("--no-llm");
2782
+ let message;
2783
+ {
2784
+ message = await generateCommitMessageHeuristics(cwd);
2785
+ }
2399
2786
  if (dryRun) {
2400
2787
  return {
2401
2788
  message: `Would commit:
@@ -2819,10 +3206,10 @@ function buildInitCommand(opts) {
2819
3206
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
2820
3207
  async run(args, ctx) {
2821
3208
  const force = args.trim() === "--force";
2822
- const dir = path18.join(ctx.projectRoot, ".wrongstack");
2823
- const file = path18.join(dir, "AGENTS.md");
3209
+ const dir = path20.join(ctx.projectRoot, ".wrongstack");
3210
+ const file = path20.join(dir, "AGENTS.md");
2824
3211
  try {
2825
- await fs5.access(file);
3212
+ await fs3.access(file);
2826
3213
  if (!force) {
2827
3214
  const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
2828
3215
  opts.renderer.writeWarning(msg2);
@@ -2832,8 +3219,8 @@ function buildInitCommand(opts) {
2832
3219
  }
2833
3220
  const detected = await detectProjectFacts(ctx.projectRoot);
2834
3221
  const body = renderAgentsTemplate(detected);
2835
- await fs5.mkdir(dir, { recursive: true });
2836
- await fs5.writeFile(file, body, "utf8");
3222
+ await fs3.mkdir(dir, { recursive: true });
3223
+ await fs3.writeFile(file, body, "utf8");
2837
3224
  if (detected.hints.length > 0) {
2838
3225
  const msg2 = `Wrote ${file}
2839
3226
  Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
@@ -3441,11 +3828,11 @@ ${lines.join("\n\n")}
3441
3828
  };
3442
3829
  }
3443
3830
  function makeInstaller(opts, projectRoot, global) {
3444
- const globalRoot = path18.join(os4.homedir(), ".wrongstack");
3831
+ const globalRoot = path20.join(os6.homedir(), ".wrongstack");
3445
3832
  return new SkillInstaller({
3446
- manifestPath: path18.join(globalRoot, "installed-skills.json"),
3447
- projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
3448
- globalSkillsDir: path18.join(globalRoot, "skills"),
3833
+ manifestPath: path20.join(globalRoot, "installed-skills.json"),
3834
+ projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
3835
+ globalSkillsDir: path20.join(globalRoot, "skills"),
3449
3836
  projectHash: projectHash(projectRoot),
3450
3837
  skillLoader: opts.skillLoader
3451
3838
  });
@@ -3656,13 +4043,13 @@ var MANIFESTS = [
3656
4043
  ];
3657
4044
  async function detectProjectKind(projectRoot) {
3658
4045
  try {
3659
- await fs5.access(path18.join(projectRoot, ".wrongstack", "AGENTS.md"));
4046
+ await fs3.access(path20.join(projectRoot, ".wrongstack", "AGENTS.md"));
3660
4047
  return "initialized";
3661
4048
  } catch {
3662
4049
  }
3663
4050
  for (const m of MANIFESTS) {
3664
4051
  try {
3665
- await fs5.access(path18.join(projectRoot, m));
4052
+ await fs3.access(path20.join(projectRoot, m));
3666
4053
  return "project";
3667
4054
  } catch {
3668
4055
  }
@@ -3670,12 +4057,12 @@ async function detectProjectKind(projectRoot) {
3670
4057
  return "empty";
3671
4058
  }
3672
4059
  async function scaffoldAgentsMd(projectRoot) {
3673
- const dir = path18.join(projectRoot, ".wrongstack");
3674
- const file = path18.join(dir, "AGENTS.md");
4060
+ const dir = path20.join(projectRoot, ".wrongstack");
4061
+ const file = path20.join(dir, "AGENTS.md");
3675
4062
  const facts = await detectProjectFacts(projectRoot);
3676
4063
  const body = renderAgentsTemplate(facts);
3677
- await fs5.mkdir(dir, { recursive: true });
3678
- await fs5.writeFile(file, body, "utf8");
4064
+ await fs3.mkdir(dir, { recursive: true });
4065
+ await fs3.writeFile(file, body, "utf8");
3679
4066
  return file;
3680
4067
  }
3681
4068
  async function runProjectCheck(opts) {
@@ -3684,7 +4071,7 @@ async function runProjectCheck(opts) {
3684
4071
  if (kind === "initialized") {
3685
4072
  renderer.write(
3686
4073
  `
3687
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path18.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4074
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path20.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
3688
4075
  `
3689
4076
  );
3690
4077
  return true;
@@ -3711,10 +4098,10 @@ async function runProjectCheck(opts) {
3711
4098
  }
3712
4099
  return true;
3713
4100
  }
3714
- const gitDir = path18.join(projectRoot, ".git");
4101
+ const gitDir = path20.join(projectRoot, ".git");
3715
4102
  let hasGit = false;
3716
4103
  try {
3717
- await fs5.access(gitDir);
4104
+ await fs3.access(gitDir);
3718
4105
  hasGit = true;
3719
4106
  } catch {
3720
4107
  }
@@ -3729,10 +4116,10 @@ async function runProjectCheck(opts) {
3729
4116
  )).trim().toLowerCase();
3730
4117
  if (answer2 === "y" || answer2 === "yes") {
3731
4118
  try {
3732
- const { spawn: spawn2 } = await import('child_process');
3733
- await new Promise((resolve3, reject) => {
3734
- const child = spawn2("git", ["init"], { cwd: projectRoot });
3735
- child.on("close", (code) => code === 0 ? resolve3() : reject(new Error(`git init failed with ${code}`)));
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}`)));
3736
4123
  });
3737
4124
  renderer.write(` ${color.green("\u2713")} Git repository initialized
3738
4125
  `);
@@ -4012,14 +4399,14 @@ function summarize(value, name) {
4012
4399
  if (typeof v === "object" && v !== null) {
4013
4400
  const o = v;
4014
4401
  if (name === "edit") {
4015
- const path19 = typeof o["path"] === "string" ? o["path"] : "";
4402
+ const path21 = typeof o["path"] === "string" ? o["path"] : "";
4016
4403
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
4017
- return `${path19} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4404
+ return `${path21} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4018
4405
  }
4019
4406
  if (name === "write") {
4020
- const path19 = typeof o["path"] === "string" ? o["path"] : "";
4407
+ const path21 = typeof o["path"] === "string" ? o["path"] : "";
4021
4408
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
4022
- return bytes !== void 0 ? `${path19} ${bytes}B` : path19;
4409
+ return bytes !== void 0 ? `${path21} ${bytes}B` : path21;
4023
4410
  }
4024
4411
  if (typeof o["count"] === "number") {
4025
4412
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -4615,7 +5002,7 @@ async function readKeyInput(deps, intent) {
4615
5002
  async function loadProviders(deps) {
4616
5003
  let raw;
4617
5004
  try {
4618
- raw = await fs5.readFile(deps.globalConfigPath, "utf8");
5005
+ raw = await fs3.readFile(deps.globalConfigPath, "utf8");
4619
5006
  } catch {
4620
5007
  return {};
4621
5008
  }
@@ -4631,7 +5018,7 @@ async function loadProviders(deps) {
4631
5018
  async function mutateProviders(deps, mutator) {
4632
5019
  let raw;
4633
5020
  try {
4634
- raw = await fs5.readFile(deps.globalConfigPath, "utf8");
5021
+ raw = await fs3.readFile(deps.globalConfigPath, "utf8");
4635
5022
  } catch {
4636
5023
  raw = "{}";
4637
5024
  }
@@ -4668,6 +5055,66 @@ var authCmd = async (args, deps) => {
4668
5055
  envVars: flags.envVars
4669
5056
  });
4670
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
+ };
4671
5118
  var req = createRequire(import.meta.url);
4672
5119
  function readOwnVersion() {
4673
5120
  const candidates = ["../package.json", "../../package.json"];
@@ -4703,7 +5150,7 @@ var diagCmd = async (_args, deps) => {
4703
5150
  ` modelsCache: ${deps.paths.modelsCache}`,
4704
5151
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
4705
5152
  ` node: ${process.version}`,
4706
- ` os: ${os4.platform()} ${os4.release()}`,
5153
+ ` os: ${os6.platform()} ${os6.release()}`,
4707
5154
  ` provider: ${cfg.provider ?? "<unset>"}`,
4708
5155
  ` model: ${cfg.model ?? "<unset>"}`,
4709
5156
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -4771,7 +5218,7 @@ var doctorCmd = async (_args, deps) => {
4771
5218
  });
4772
5219
  }
4773
5220
  try {
4774
- await fs5.access(deps.paths.secretsKey);
5221
+ await fs3.access(deps.paths.secretsKey);
4775
5222
  checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
4776
5223
  } catch {
4777
5224
  checks.push({
@@ -4781,10 +5228,10 @@ var doctorCmd = async (_args, deps) => {
4781
5228
  });
4782
5229
  }
4783
5230
  try {
4784
- await fs5.mkdir(deps.paths.projectSessions, { recursive: true });
4785
- const probe = path18.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
4786
- await fs5.writeFile(probe, "");
4787
- await fs5.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);
4788
5235
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
4789
5236
  } catch (err) {
4790
5237
  checks.push({
@@ -4885,8 +5332,8 @@ var exportCmd = async (args, deps) => {
4885
5332
  return 1;
4886
5333
  }
4887
5334
  if (output) {
4888
- await fs5.mkdir(path18.dirname(path18.resolve(deps.cwd, output)), { recursive: true });
4889
- await fs5.writeFile(path18.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");
4890
5337
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
4891
5338
  `);
4892
5339
  } else {
@@ -4943,17 +5390,17 @@ var initCmd = async (_args, deps) => {
4943
5390
  } else {
4944
5391
  deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
4945
5392
  }
4946
- await fs5.mkdir(deps.paths.globalRoot, { recursive: true });
5393
+ await fs3.mkdir(deps.paths.globalRoot, { recursive: true });
4947
5394
  const config = { version: 1, provider: providerId, model: modelId };
4948
5395
  if (apiKey) config.apiKey = apiKey;
4949
- const keyFile = path18.join(path18.dirname(deps.paths.globalConfig), ".key");
5396
+ const keyFile = path20.join(path20.dirname(deps.paths.globalConfig), ".key");
4950
5397
  const vault = new DefaultSecretVault$1({ keyFile });
4951
5398
  const encrypted = encryptConfigSecrets(config, vault);
4952
5399
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
4953
- await fs5.mkdir(path18.join(deps.projectRoot, ".wrongstack"), { recursive: true });
4954
- const agentsFile = path18.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");
4955
5402
  try {
4956
- await fs5.access(agentsFile);
5403
+ await fs3.access(agentsFile);
4957
5404
  } catch {
4958
5405
  const detected2 = await detectProjectFacts(deps.projectRoot);
4959
5406
  await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
@@ -5029,7 +5476,7 @@ async function addMcpServer(args, deps) {
5029
5476
  serverCfg.enabled = enable;
5030
5477
  let existing = {};
5031
5478
  try {
5032
- existing = JSON.parse(await fs5.readFile(deps.paths.globalConfig, "utf8"));
5479
+ existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
5033
5480
  } catch {
5034
5481
  }
5035
5482
  const mcpServers = existing.mcpServers ?? {};
@@ -5049,7 +5496,7 @@ async function addMcpServer(args, deps) {
5049
5496
  async function removeMcpServer(name, deps) {
5050
5497
  let existing = {};
5051
5498
  try {
5052
- existing = JSON.parse(await fs5.readFile(deps.paths.globalConfig, "utf8"));
5499
+ existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
5053
5500
  } catch {
5054
5501
  deps.renderer.writeError("No config file found.\n");
5055
5502
  return 1;
@@ -5170,7 +5617,7 @@ function renderConfiguredPlugins(config) {
5170
5617
  }
5171
5618
  async function readConfig(file) {
5172
5619
  try {
5173
- return JSON.parse(await fs5.readFile(file, "utf8"));
5620
+ return JSON.parse(await fs3.readFile(file, "utf8"));
5174
5621
  } catch {
5175
5622
  return {};
5176
5623
  }
@@ -5261,9 +5708,9 @@ var usageCmd = async (_args, deps) => {
5261
5708
  return 0;
5262
5709
  };
5263
5710
  var projectsCmd = async (_args, deps) => {
5264
- const projectsRoot = path18.join(deps.paths.globalRoot, "projects");
5711
+ const projectsRoot = path20.join(deps.paths.globalRoot, "projects");
5265
5712
  try {
5266
- const entries = await fs5.readdir(projectsRoot);
5713
+ const entries = await fs3.readdir(projectsRoot);
5267
5714
  if (entries.length === 0) {
5268
5715
  deps.renderer.write("No projects tracked.\n");
5269
5716
  return 0;
@@ -5271,7 +5718,7 @@ var projectsCmd = async (_args, deps) => {
5271
5718
  for (const hash of entries) {
5272
5719
  try {
5273
5720
  const meta = JSON.parse(
5274
- await fs5.readFile(path18.join(projectsRoot, hash, "meta.json"), "utf8")
5721
+ await fs3.readFile(path20.join(projectsRoot, hash, "meta.json"), "utf8")
5275
5722
  );
5276
5723
  deps.renderer.write(
5277
5724
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -5445,9 +5892,94 @@ var configCmd = async (args, deps) => {
5445
5892
  `);
5446
5893
  return 0;
5447
5894
  }
5448
- 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
+ `);
5449
5903
  return 1;
5450
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
+ }
5451
5983
  function parseRewindFlags(args) {
5452
5984
  const flags = {};
5453
5985
  for (let i = 0; i < args.length; i++) {
@@ -5463,7 +5995,7 @@ function parseRewindFlags(args) {
5463
5995
  var rewindCmd = async (args, deps) => {
5464
5996
  const flags = parseRewindFlags(args);
5465
5997
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
5466
- const sessionsDir = path18.join(wpaths.globalRoot, "sessions");
5998
+ const sessionsDir = path20.join(wpaths.globalRoot, "sessions");
5467
5999
  const rewind = new DefaultSessionRewinder(sessionsDir);
5468
6000
  let sessionId = args.find((a) => !a.startsWith("--"));
5469
6001
  if (!sessionId) {
@@ -5595,7 +6127,7 @@ var skillsCmd = async (_args, deps) => {
5595
6127
  };
5596
6128
  var versionCmd = async (_args, deps) => {
5597
6129
  deps.renderer.write(
5598
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
6130
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
5599
6131
  `
5600
6132
  );
5601
6133
  return 0;
@@ -5633,6 +6165,7 @@ var helpCmd = async (_args, deps) => {
5633
6165
  var subcommands = {
5634
6166
  init: initCmd,
5635
6167
  auth: authCmd,
6168
+ update: updateCmd,
5636
6169
  sessions: sessionsCmd,
5637
6170
  config: configCmd,
5638
6171
  rewind: rewindCmd,
@@ -5690,11 +6223,14 @@ function fmtTaskResultLine(r, color32) {
5690
6223
  return { mark: color32.red("\u2717"), stats: `${color32.red("failed")} ${stats}`, tail: errSnip };
5691
6224
  }
5692
6225
  }
6226
+
6227
+ // src/boot.ts
6228
+ init_update_check();
5693
6229
  function resolveBundledSkillsDir() {
5694
6230
  try {
5695
6231
  const req2 = createRequire(import.meta.url);
5696
6232
  const corePkg = req2.resolve("@wrongstack/core/package.json");
5697
- return path18.join(path18.dirname(corePkg), "skills");
6233
+ return path20.join(path20.dirname(corePkg), "skills");
5698
6234
  } catch {
5699
6235
  return void 0;
5700
6236
  }
@@ -5723,6 +6259,13 @@ async function boot(argv) {
5723
6259
  cacheFile: wpaths.modelsCache,
5724
6260
  ttlSeconds: 24 * 3600
5725
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
+ }
5726
6269
  const first = positional[0];
5727
6270
  if (first && subcommands[first]) {
5728
6271
  const container = createDefaultContainer({
@@ -5831,7 +6374,8 @@ async function boot(argv) {
5831
6374
  modelsRegistry,
5832
6375
  renderer,
5833
6376
  reader,
5834
- logger
6377
+ logger,
6378
+ updateInfo
5835
6379
  };
5836
6380
  }
5837
6381
 
@@ -6412,7 +6956,7 @@ async function execute(deps) {
6412
6956
  supportsVision,
6413
6957
  attachments,
6414
6958
  effectiveMaxContext,
6415
- projectName: path18.basename(projectRoot) || void 0,
6959
+ projectName: path20.basename(projectRoot) || void 0,
6416
6960
  getAutonomy,
6417
6961
  skillLoader
6418
6962
  });
@@ -6430,7 +6974,7 @@ async function execute(deps) {
6430
6974
  supportsVision,
6431
6975
  attachments,
6432
6976
  effectiveMaxContext,
6433
- projectName: path18.basename(projectRoot) || void 0,
6977
+ projectName: path20.basename(projectRoot) || void 0,
6434
6978
  getAutonomy,
6435
6979
  skillLoader
6436
6980
  });
@@ -6702,7 +7246,7 @@ var MultiAgentHost = class {
6702
7246
  model: opts?.model,
6703
7247
  tools: opts?.tools
6704
7248
  };
6705
- const transcriptPath = this.sessionFactory ? path18.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7249
+ const transcriptPath = this.sessionFactory ? path20.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
6706
7250
  if (this.director) {
6707
7251
  const subagentId = await this.director.spawn(subagentConfig);
6708
7252
  const taskId2 = randomUUID();
@@ -6862,16 +7406,16 @@ var MultiAgentHost = class {
6862
7406
  }
6863
7407
  this.opts.directorMode = true;
6864
7408
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
6865
- this.opts.manifestPath = path18.join(this.opts.fleetRoot, "fleet.json");
7409
+ this.opts.manifestPath = path20.join(this.opts.fleetRoot, "fleet.json");
6866
7410
  }
6867
7411
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
6868
- this.opts.sharedScratchpadPath = path18.join(this.opts.fleetRoot, "shared");
7412
+ this.opts.sharedScratchpadPath = path20.join(this.opts.fleetRoot, "shared");
6869
7413
  }
6870
7414
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
6871
- this.opts.sessionsRoot = path18.join(this.opts.fleetRoot, "subagents");
7415
+ this.opts.sessionsRoot = path20.join(this.opts.fleetRoot, "subagents");
6872
7416
  }
6873
7417
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
6874
- this.opts.stateCheckpointPath = path18.join(this.opts.fleetRoot, "director-state.json");
7418
+ this.opts.stateCheckpointPath = path20.join(this.opts.fleetRoot, "director-state.json");
6875
7419
  }
6876
7420
  await this.ensureDirector();
6877
7421
  return this.director ?? null;
@@ -6992,11 +7536,11 @@ var SessionStats = class {
6992
7536
  if (e.name === "bash") this.bashCommands++;
6993
7537
  else if (e.name === "fetch") this.fetches++;
6994
7538
  if (!e.ok) return;
6995
- const path19 = typeof input?.path === "string" ? input.path : void 0;
6996
- if (e.name === "read" && path19) this.readPaths.add(path19);
6997
- else if (e.name === "edit" && path19) this.editedPaths.add(path19);
6998
- else if (e.name === "write" && path19) {
6999
- this.writtenPaths.add(path19);
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);
7000
7544
  const content = typeof input?.content === "string" ? input.content : "";
7001
7545
  this.bytesWritten += Buffer.byteLength(content, "utf8");
7002
7546
  }
@@ -7327,12 +7871,12 @@ async function setupSession(params) {
7327
7871
  }
7328
7872
  const sessionRef = { current: session };
7329
7873
  await recoveryLock.write(session.id).catch(() => void 0);
7330
- const attachments = new DefaultAttachmentStore({ spoolDir: path18.join(wpaths.projectSessions, session.id, "attachments") });
7331
- const queueStore = new QueueStore({ dir: path18.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) });
7332
7876
  const ctxSignal = new AbortController().signal;
7333
7877
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
7334
7878
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
7335
- const todosCheckpointPath = path18.join(wpaths.projectSessions, `${session.id}.todos.json`);
7879
+ const todosCheckpointPath = path20.join(wpaths.projectSessions, `${session.id}.todos.json`);
7336
7880
  if (resumeId) {
7337
7881
  try {
7338
7882
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -7344,12 +7888,12 @@ async function setupSession(params) {
7344
7888
  }
7345
7889
  }
7346
7890
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
7347
- const planPath = path18.join(wpaths.projectSessions, `${session.id}.plan.json`);
7891
+ const planPath = path20.join(wpaths.projectSessions, `${session.id}.plan.json`);
7348
7892
  context.state.setMeta("plan.path", planPath);
7349
7893
  if (resumeId) {
7350
7894
  try {
7351
- const fleetRoot = path18.join(wpaths.projectSessions, session.id);
7352
- const dirState = await loadDirectorState(path18.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"));
7353
7897
  if (dirState) {
7354
7898
  const tCounts = {};
7355
7899
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -7376,7 +7920,7 @@ function resolveBundledSkillsDir2() {
7376
7920
  try {
7377
7921
  const req2 = createRequire(import.meta.url);
7378
7922
  const corePkg = req2.resolve("@wrongstack/core/package.json");
7379
- return path18.join(path18.dirname(corePkg), "skills");
7923
+ return path20.join(path20.dirname(corePkg), "skills");
7380
7924
  } catch {
7381
7925
  return void 0;
7382
7926
  }
@@ -7407,8 +7951,28 @@ async function main(argv) {
7407
7951
  modelsRegistry,
7408
7952
  renderer,
7409
7953
  reader,
7410
- logger
7954
+ logger,
7955
+ updateInfo
7411
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
+ }
7412
7976
  const pathResolver = new DefaultPathResolver(cwd);
7413
7977
  const container = createDefaultContainer({
7414
7978
  config,
@@ -7463,7 +8027,7 @@ async function main(argv) {
7463
8027
  modeId,
7464
8028
  modePrompt,
7465
8029
  modelCapabilities,
7466
- planPath: () => sessionRef.current ? path18.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
7467
8031
  })
7468
8032
  );
7469
8033
  const toolRegistry = new ToolRegistry();
@@ -7491,7 +8055,7 @@ async function main(argv) {
7491
8055
  name: "session-store",
7492
8056
  check: async () => {
7493
8057
  try {
7494
- await fs5.access(wpaths.projectSessions);
8058
+ await fs3.access(wpaths.projectSessions);
7495
8059
  return { status: "healthy" };
7496
8060
  } catch (e) {
7497
8061
  return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
@@ -7508,7 +8072,7 @@ async function main(argv) {
7508
8072
  const dumpMetrics = () => {
7509
8073
  if (!metricsSink) return;
7510
8074
  try {
7511
- const out = path18.join(wpaths.projectSessions, "metrics.json");
8075
+ const out = path20.join(wpaths.projectSessions, "metrics.json");
7512
8076
  const snap = metricsSink.snapshot();
7513
8077
  writeFileSync(out, JSON.stringify(snap, null, 2));
7514
8078
  } catch {
@@ -7727,12 +8291,12 @@ async function main(argv) {
7727
8291
  const directorMode = flags["director"] === true;
7728
8292
  let director = null;
7729
8293
  let autonomyMode = "off";
7730
- const fleetRoot = directorMode ? path18.join(wpaths.projectSessions, session.id) : void 0;
7731
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path18.join(fleetRoot, "fleet.json") : void 0;
7732
- const sharedScratchpadPath = directorMode ? path18.join(fleetRoot, "shared") : void 0;
7733
- const subagentSessionsRoot = directorMode ? path18.join(fleetRoot, "subagents") : void 0;
7734
- const stateCheckpointPath = directorMode ? path18.join(fleetRoot, "director-state.json") : void 0;
7735
- const fleetRootForPromotion = path18.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);
7736
8300
  const multiAgentHost = new MultiAgentHost(
7737
8301
  {
7738
8302
  container,
@@ -7911,27 +8475,27 @@ async function main(argv) {
7911
8475
  return `Unknown fleet action: ${action}`;
7912
8476
  },
7913
8477
  onFleetLog: async (subagentId, mode) => {
7914
- const subagentsRoot = path18.join(fleetRootForPromotion, "subagents");
8478
+ const subagentsRoot = path20.join(fleetRootForPromotion, "subagents");
7915
8479
  let runDirs;
7916
8480
  try {
7917
- runDirs = await fs5.readdir(subagentsRoot);
8481
+ runDirs = await fs3.readdir(subagentsRoot);
7918
8482
  } catch {
7919
8483
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
7920
8484
  }
7921
8485
  const found = [];
7922
8486
  for (const runId of runDirs) {
7923
- const runDir = path18.join(subagentsRoot, runId);
8487
+ const runDir = path20.join(subagentsRoot, runId);
7924
8488
  let files;
7925
8489
  try {
7926
- files = await fs5.readdir(runDir);
8490
+ files = await fs3.readdir(runDir);
7927
8491
  } catch {
7928
8492
  continue;
7929
8493
  }
7930
8494
  for (const f of files) {
7931
8495
  if (!f.endsWith(".jsonl")) continue;
7932
- const full = path18.join(runDir, f);
8496
+ const full = path20.join(runDir, f);
7933
8497
  try {
7934
- const stat2 = await fs5.stat(full);
8498
+ const stat2 = await fs3.stat(full);
7935
8499
  found.push({
7936
8500
  runId,
7937
8501
  subagentId: f.replace(/\.jsonl$/, ""),
@@ -7970,7 +8534,7 @@ async function main(argv) {
7970
8534
  ].join("\n");
7971
8535
  }
7972
8536
  const t = matches[0];
7973
- const raw = await fs5.readFile(t.file, "utf8");
8537
+ const raw = await fs3.readFile(t.file, "utf8");
7974
8538
  if (mode === "raw") return raw;
7975
8539
  const lines = raw.split("\n").filter((l) => l.trim());
7976
8540
  const counts = {};
@@ -8026,7 +8590,7 @@ async function main(argv) {
8026
8590
  }
8027
8591
  const dir = await multiAgentHost.ensureDirector();
8028
8592
  if (!dir) return "Director is not available.";
8029
- const dirStatePath = path18.join(fleetRootForPromotion, "director-state.json");
8593
+ const dirStatePath = path20.join(fleetRootForPromotion, "director-state.json");
8030
8594
  const prior = await loadDirectorState(dirStatePath);
8031
8595
  if (!prior) {
8032
8596
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -8097,9 +8661,9 @@ async function main(argv) {
8097
8661
  for (const tool of director2.tools(FLEET_ROSTER)) {
8098
8662
  toolRegistry.register(tool);
8099
8663
  }
8100
- const mp = path18.join(fleetRootForPromotion, "fleet.json");
8101
- const sp = path18.join(fleetRootForPromotion, "shared");
8102
- const ss = path18.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");
8103
8667
  const lines = [
8104
8668
  `${color.green("\u2713")} Promoted to director mode.`,
8105
8669
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -8146,13 +8710,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
8146
8710
  void mcpRegistry.stopAll();
8147
8711
  },
8148
8712
  onBeforeExit: async () => {
8149
- const { spawn: spawn2 } = await import('child_process');
8713
+ const { spawn: spawn3 } = await import('child_process');
8150
8714
  const cwd2 = projectRoot;
8151
- const statusResult = await new Promise((resolve3) => {
8152
- const child = spawn2("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
8715
+ const statusResult = await new Promise((resolve4) => {
8716
+ const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
8153
8717
  let stdout = "";
8154
8718
  child.stdout?.on("data", (d) => stdout += d);
8155
- child.on("close", (code) => resolve3({ stdout, code: code ?? 0 }));
8719
+ child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
8156
8720
  });
8157
8721
  if (statusResult.stdout.trim().length > 0) {
8158
8722
  const lines = statusResult.stdout.split("\n").filter(Boolean);
@@ -8190,7 +8754,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
8190
8754
  ...errSection
8191
8755
  ].join("\n");
8192
8756
  },
8193
- 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
+ }
8194
8764
  });
8195
8765
  for (const cmd of slashCmds) slashRegistry.register(cmd);
8196
8766
  const savedProviderCfg = config.providers?.[config.provider];