@wrongstack/cli 0.141.0 → 0.148.0

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
@@ -2,7 +2,7 @@
2
2
  import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
3
3
  import * as fsp4 from 'fs/promises';
4
4
  import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
5
- import * as path9 from 'path';
5
+ import * as path10 from 'path';
6
6
  import { join } from 'path';
7
7
  import { createRequire } from 'module';
8
8
  import * as os2 from 'os';
@@ -11,14 +11,14 @@ import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
14
- import { spawn } from 'child_process';
14
+ import { spawn, exec, execFileSync } from 'child_process';
15
15
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
16
16
  import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
17
17
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
18
18
  import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
19
19
  import { fileURLToPath } from 'url';
20
20
  import * as readline from 'readline';
21
- import * as fs14 from 'fs';
21
+ import * as fs15 from 'fs';
22
22
  import { writeFileSync, existsSync, readFileSync } from 'fs';
23
23
  import { WrongStackACPServer } from '@wrongstack/acp/agent';
24
24
  import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
@@ -96,7 +96,7 @@ async function loadConfigProviders(configPath2, vault, opts) {
96
96
  }
97
97
  async function mutateConfigProviders(configPath2, vault, mutator) {
98
98
  let raw;
99
- let fileExists = true;
99
+ let fileExists2 = true;
100
100
  try {
101
101
  raw = await fsp4.readFile(configPath2, "utf8");
102
102
  } catch (err) {
@@ -106,14 +106,14 @@ async function mutateConfigProviders(configPath2, vault, mutator) {
106
106
  { cause: err }
107
107
  );
108
108
  }
109
- fileExists = false;
109
+ fileExists2 = false;
110
110
  raw = "{}";
111
111
  }
112
112
  let parsed;
113
113
  try {
114
114
  parsed = JSON.parse(raw);
115
115
  } catch (err) {
116
- if (fileExists) {
116
+ if (fileExists2) {
117
117
  throw new Error(
118
118
  `Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
119
119
  { cause: err }
@@ -386,7 +386,7 @@ async function findSpec(store, idOrTitle) {
386
386
  async function gatherProjectContext2(projectRoot) {
387
387
  const parts = [];
388
388
  try {
389
- const pkgPath = path9.join(projectRoot, "package.json");
389
+ const pkgPath = path10.join(projectRoot, "package.json");
390
390
  const pkgRaw = await fsp4.readFile(pkgPath, "utf8");
391
391
  const pkg = JSON.parse(pkgRaw);
392
392
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -402,13 +402,13 @@ async function gatherProjectContext2(projectRoot) {
402
402
  } catch {
403
403
  }
404
404
  try {
405
- const tsconfigPath = path9.join(projectRoot, "tsconfig.json");
405
+ const tsconfigPath = path10.join(projectRoot, "tsconfig.json");
406
406
  await fsp4.access(tsconfigPath);
407
407
  parts.push("Language: TypeScript");
408
408
  } catch {
409
409
  }
410
410
  try {
411
- const srcDir = path9.join(projectRoot, "src");
411
+ const srcDir = path10.join(projectRoot, "src");
412
412
  const entries = await fsp4.readdir(srcDir, { withFileTypes: true });
413
413
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
414
414
  if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
@@ -1602,7 +1602,7 @@ __export(update_check_exports, {
1602
1602
  getUpdateNotification: () => getUpdateNotification
1603
1603
  });
1604
1604
  function cachePath(homeFn = defaultHomeDir2) {
1605
- return path9.join(homeFn(), ".wrongstack", "update-cache.json");
1605
+ return path10.join(homeFn(), ".wrongstack", "update-cache.json");
1606
1606
  }
1607
1607
  function currentVersion() {
1608
1608
  const req2 = createRequire(import.meta.url);
@@ -1639,7 +1639,7 @@ async function readCache(homeFn = defaultHomeDir2) {
1639
1639
  }
1640
1640
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1641
1641
  try {
1642
- const dir = path9.dirname(cachePath(homeFn));
1642
+ const dir = path10.dirname(cachePath(homeFn));
1643
1643
  await fsp4.mkdir(dir, { recursive: true });
1644
1644
  await fsp4.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1645
1645
  } catch {
@@ -1746,7 +1746,7 @@ async function runWebUI(opts) {
1746
1746
  try {
1747
1747
  const requireFromHere = createRequire(import.meta.url);
1748
1748
  const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
1749
- const distDir = path9.resolve(path9.dirname(serverEntry), "..");
1749
+ const distDir = path10.resolve(path10.dirname(serverEntry), "..");
1750
1750
  httpServer = createHttpServer({ host, distDir, wsPort });
1751
1751
  const openUrl = `http://${host}:${httpPort}`;
1752
1752
  httpServer?.listen(httpPort, host, () => {
@@ -1763,7 +1763,7 @@ async function runWebUI(opts) {
1763
1763
  `[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`): ${err instanceof Error ? err.message : String(err)}. WS bridge still active on ws://${host}:${wsPort}.`
1764
1764
  );
1765
1765
  }
1766
- const registryBaseDir = opts.globalConfigPath ? path9.dirname(opts.globalConfigPath) : void 0;
1766
+ const registryBaseDir = opts.globalConfigPath ? path10.dirname(opts.globalConfigPath) : void 0;
1767
1767
  if (opts.projectRoot) {
1768
1768
  void registerInstance(
1769
1769
  {
@@ -1772,7 +1772,7 @@ async function runWebUI(opts) {
1772
1772
  wsPort,
1773
1773
  host,
1774
1774
  projectRoot: opts.projectRoot,
1775
- projectName: path9.basename(opts.projectRoot) || opts.projectRoot,
1775
+ projectName: path10.basename(opts.projectRoot) || opts.projectRoot,
1776
1776
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1777
1777
  url: `http://${host}:${httpPort}`
1778
1778
  },
@@ -2159,15 +2159,12 @@ async function runWebUI(opts) {
2159
2159
  await handleProviderRemove(ws, m.payload.providerId);
2160
2160
  break;
2161
2161
  }
2162
- default:
2163
- send(ws, {
2164
- type: "error",
2165
- payload: {
2166
- phase: "ws.dispatch",
2167
- message: `Unknown message type: ${String(msg.type)}`
2168
- }
2169
- });
2162
+ default: {
2163
+ console.debug(
2164
+ `[WebUI] Unhandled message type: ${String(msg.type)}`
2165
+ );
2170
2166
  break;
2167
+ }
2171
2168
  }
2172
2169
  }
2173
2170
  async function handleUserMessage(ws, _client, content) {
@@ -2408,7 +2405,7 @@ async function runWebUI(opts) {
2408
2405
  }
2409
2406
  }
2410
2407
  function getVault() {
2411
- const keyFile = path9.join(path9.dirname(opts.globalConfigPath ?? ""), ".key");
2408
+ const keyFile = path10.join(path10.dirname(opts.globalConfigPath ?? ""), ".key");
2412
2409
  return new DefaultSecretVault({ keyFile });
2413
2410
  }
2414
2411
  async function loadSavedProviders() {
@@ -2709,10 +2706,10 @@ async function detectPackageManager(root, declared) {
2709
2706
  const name = declared.split("@")[0];
2710
2707
  if (name) return name;
2711
2708
  }
2712
- if (await pathExists(path9.join(root, "pnpm-lock.yaml"))) return "pnpm";
2713
- if (await pathExists(path9.join(root, "bun.lockb"))) return "bun";
2714
- if (await pathExists(path9.join(root, "bun.lock"))) return "bun";
2715
- if (await pathExists(path9.join(root, "yarn.lock"))) return "yarn";
2709
+ if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
2710
+ if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
2711
+ if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
2712
+ if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
2716
2713
  return "npm";
2717
2714
  }
2718
2715
  function hasUsableScript(scripts, name) {
@@ -2733,7 +2730,7 @@ function parseMakeTargets(makefile) {
2733
2730
  async function detectProjectFacts(root) {
2734
2731
  const facts = { hints: [] };
2735
2732
  try {
2736
- const pkg = JSON.parse(await fsp4.readFile(path9.join(root, "package.json"), "utf8"));
2733
+ const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
2737
2734
  const scripts = pkg.scripts ?? {};
2738
2735
  const pm = await detectPackageManager(root, pkg.packageManager);
2739
2736
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2747,14 +2744,14 @@ async function detectProjectFacts(root) {
2747
2744
  } catch {
2748
2745
  }
2749
2746
  try {
2750
- if (!await pathExists(path9.join(root, "pyproject.toml"))) throw new Error("not python");
2747
+ if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
2751
2748
  facts.test ??= "pytest";
2752
2749
  facts.lint ??= "ruff check .";
2753
2750
  facts.hints.push("pyproject.toml");
2754
2751
  } catch {
2755
2752
  }
2756
2753
  try {
2757
- if (!await pathExists(path9.join(root, "go.mod"))) throw new Error("not go");
2754
+ if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
2758
2755
  facts.build ??= "go build ./...";
2759
2756
  facts.test ??= "go test ./...";
2760
2757
  facts.run ??= "go run .";
@@ -2762,7 +2759,7 @@ async function detectProjectFacts(root) {
2762
2759
  } catch {
2763
2760
  }
2764
2761
  try {
2765
- if (!await pathExists(path9.join(root, "Cargo.toml"))) throw new Error("not rust");
2762
+ if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
2766
2763
  facts.build ??= "cargo build";
2767
2764
  facts.test ??= "cargo test";
2768
2765
  facts.lint ??= "cargo clippy";
@@ -2771,7 +2768,7 @@ async function detectProjectFacts(root) {
2771
2768
  } catch {
2772
2769
  }
2773
2770
  try {
2774
- const makefile = await fsp4.readFile(path9.join(root, "Makefile"), "utf8");
2771
+ const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
2775
2772
  const targets = parseMakeTargets(makefile);
2776
2773
  facts.build ??= targets.has("build") ? "make build" : "make";
2777
2774
  if (targets.has("test")) facts.test ??= "make test";
@@ -3066,7 +3063,7 @@ function formatPhaseList(graph) {
3066
3063
  }
3067
3064
  async function gatherProjectContext(projectRoot) {
3068
3065
  try {
3069
- const raw = await fsp4.readFile(path9.join(projectRoot, "package.json"), "utf8");
3066
+ const raw = await fsp4.readFile(path10.join(projectRoot, "package.json"), "utf8");
3070
3067
  const pkg = JSON.parse(raw);
3071
3068
  const parts = [
3072
3069
  `Project: ${String(pkg.name ?? "unknown")}`,
@@ -3964,6 +3961,94 @@ function formatLimit(limit) {
3964
3961
  function pct(n) {
3965
3962
  return `${Math.round(n * 100)}%`;
3966
3963
  }
3964
+ var DEFAULT_TIMEOUT_MS = 6e4;
3965
+ var MAX_OUTPUT_LINES = 500;
3966
+ function runCommand(cmd, cwd, timeout) {
3967
+ return new Promise((resolve5) => {
3968
+ exec(
3969
+ cmd,
3970
+ {
3971
+ cwd,
3972
+ timeout,
3973
+ maxBuffer: 2 * 1024 * 1024,
3974
+ // 2 MB
3975
+ windowsHide: true
3976
+ },
3977
+ (error, stdout, stderr) => {
3978
+ resolve5({
3979
+ stdout,
3980
+ stderr,
3981
+ exitCode: error?.code ?? 0,
3982
+ killed: error?.killed ?? false
3983
+ });
3984
+ }
3985
+ );
3986
+ });
3987
+ }
3988
+ function formatOutput(cmd, result, elapsed) {
3989
+ const lines = [];
3990
+ const exitLabel = result.killed ? color.red("TIMEOUT") : result.exitCode === 0 ? color.green("OK") : color.red(`EXIT ${result.exitCode}`);
3991
+ lines.push(`${color.cyan("$")} ${color.bold(cmd)} ${exitLabel} ${color.dim(`${elapsed}ms`)}`);
3992
+ const combined = (result.stdout + result.stderr).trimEnd();
3993
+ if (combined) {
3994
+ const outputLines = combined.split("\n");
3995
+ const truncated = outputLines.length > MAX_OUTPUT_LINES;
3996
+ const shown = truncated ? outputLines.slice(0, MAX_OUTPUT_LINES) : outputLines;
3997
+ lines.push("");
3998
+ lines.push(color.dim("\u2500\u2500"));
3999
+ for (const line of shown) {
4000
+ lines.push(line);
4001
+ }
4002
+ if (truncated) {
4003
+ lines.push(color.dim(`\u2026 (truncated, showing first ${MAX_OUTPUT_LINES} of ${outputLines.length} lines)`));
4004
+ }
4005
+ lines.push(color.dim("\u2500\u2500"));
4006
+ } else {
4007
+ lines.push(color.dim("(no output)"));
4008
+ }
4009
+ return lines.join("\n");
4010
+ }
4011
+ function buildDevCommand(opts) {
4012
+ return {
4013
+ name: "dev",
4014
+ category: "Run",
4015
+ description: "Run a shell command and see the output (LLM does not see it).",
4016
+ argsHint: "<shell command>",
4017
+ help: [
4018
+ "Usage:",
4019
+ " /dev <shell command> Run a command from the chat input.",
4020
+ "",
4021
+ "Examples:",
4022
+ " /dev pnpm release:check",
4023
+ " /dev git diff --stat",
4024
+ " /dev ls -la src/",
4025
+ "",
4026
+ "The command runs in the current working directory. Output is displayed",
4027
+ "in the chat history but is NOT fed to the LLM \u2014 use this for your own",
4028
+ "eyes only. Timeout: 60s. Max output: 500 lines.",
4029
+ "",
4030
+ "This is a convenience shortcut \u2014 equivalent to switching to a terminal",
4031
+ "tab. For commands the LLM should see, use the `exec` tool instead."
4032
+ ].join("\n"),
4033
+ async run(args, _ctx) {
4034
+ const cmd = args.trim();
4035
+ if (!cmd) {
4036
+ return { message: `${color.yellow("Usage:")} /dev <shell command>
4037
+
4038
+ Examples:
4039
+ /dev pnpm release:check
4040
+ /dev git diff --stat` };
4041
+ }
4042
+ const cwd = opts.cwd;
4043
+ const startedAt = Date.now();
4044
+ opts.renderer.write(color.dim(`$ ${cmd}`));
4045
+ const result = await runCommand(cmd, cwd, DEFAULT_TIMEOUT_MS);
4046
+ const elapsed = Date.now() - startedAt;
4047
+ const display = formatOutput(cmd, result, elapsed);
4048
+ return { message: display };
4049
+ }
4050
+ };
4051
+ }
3967
4052
 
3968
4053
  // src/slash-commands/diag-stats.ts
3969
4054
  function buildDiagCommand(opts) {
@@ -3997,7 +4082,7 @@ function resolvePersistPath(deps) {
3997
4082
  return deps.globalConfigPath;
3998
4083
  }
3999
4084
  async function ensureProjectDir(filePath) {
4000
- const dir = path9.dirname(filePath);
4085
+ const dir = path10.dirname(filePath);
4001
4086
  try {
4002
4087
  await fsp4.mkdir(dir, { recursive: true });
4003
4088
  } catch {
@@ -4036,21 +4121,21 @@ async function persistAutonomySetting(deps, mutator) {
4036
4121
  const targetPath = resolvePersistPath(deps);
4037
4122
  await ensureProjectDir(targetPath);
4038
4123
  let raw;
4039
- let fileExists = true;
4124
+ let fileExists2 = true;
4040
4125
  try {
4041
4126
  raw = await fsp4.readFile(targetPath, "utf8");
4042
4127
  } catch (err) {
4043
4128
  if (err.code !== "ENOENT") {
4044
4129
  throw new Error(`Could not read ${targetPath}: ${err.message}`);
4045
4130
  }
4046
- fileExists = false;
4131
+ fileExists2 = false;
4047
4132
  raw = "{}";
4048
4133
  }
4049
4134
  let parsed;
4050
4135
  try {
4051
4136
  parsed = JSON.parse(raw);
4052
4137
  } catch (err) {
4053
- if (fileExists) {
4138
+ if (fileExists2) {
4054
4139
  throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
4055
4140
  }
4056
4141
  parsed = {};
@@ -4073,21 +4158,21 @@ async function persistConfigSetting(deps, mutator) {
4073
4158
  const targetPath = resolvePersistPath(deps);
4074
4159
  await ensureProjectDir(targetPath);
4075
4160
  let raw;
4076
- let fileExists = true;
4161
+ let fileExists2 = true;
4077
4162
  try {
4078
4163
  raw = await fsp4.readFile(targetPath, "utf8");
4079
4164
  } catch (err) {
4080
4165
  if (err.code !== "ENOENT") {
4081
4166
  throw new Error(`Could not read ${targetPath}: ${err.message}`);
4082
4167
  }
4083
- fileExists = false;
4168
+ fileExists2 = false;
4084
4169
  raw = "{}";
4085
4170
  }
4086
4171
  let parsed;
4087
4172
  try {
4088
4173
  parsed = JSON.parse(raw);
4089
4174
  } catch (err) {
4090
- if (fileExists) {
4175
+ if (fileExists2) {
4091
4176
  throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
4092
4177
  }
4093
4178
  parsed = {};
@@ -4106,21 +4191,21 @@ async function persistConfigSetting(deps, mutator) {
4106
4191
  }
4107
4192
  async function persistTelegramConfig(deps, mutator) {
4108
4193
  let raw;
4109
- let fileExists = true;
4194
+ let fileExists2 = true;
4110
4195
  try {
4111
4196
  raw = await fsp4.readFile(deps.globalConfigPath, "utf8");
4112
4197
  } catch (err) {
4113
4198
  if (err.code !== "ENOENT") {
4114
4199
  throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
4115
4200
  }
4116
- fileExists = false;
4201
+ fileExists2 = false;
4117
4202
  raw = "{}";
4118
4203
  }
4119
4204
  let parsed;
4120
4205
  try {
4121
4206
  parsed = JSON.parse(raw);
4122
4207
  } catch (err) {
4123
- if (fileExists) {
4208
+ if (fileExists2) {
4124
4209
  throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
4125
4210
  }
4126
4211
  parsed = {};
@@ -4214,6 +4299,7 @@ function buildEnhanceCommand(opts) {
4214
4299
 
4215
4300
  // src/slash-commands/fix-classifier.ts
4216
4301
  var TS = ["typescript-strict"];
4302
+ var TC = ["tech-stack"];
4217
4303
  var BH = ["bug-hunter"];
4218
4304
  var SS = ["security-scanner"];
4219
4305
  var NM = ["node-modern"];
@@ -4588,6 +4674,90 @@ var P = [
4588
4674
  detail: "JavaScript runtime error",
4589
4675
  conf: 0.95
4590
4676
  },
4677
+ // ── Tech stack validation — BEFORE generic dep patterns ─────────────
4678
+ // Technology choice questions: "should I use X?", "is Y deprecated?", etc.
4679
+ {
4680
+ pat: /\b(should I (use|install|add|pick|choose)|is .+ (still (good|maintained|supported|relevant)|deprecated|dead|obsolete|outdated)|what replaces|alternative to|instead of)\b/i,
4681
+ cat: "tech",
4682
+ sub: "tech-choice",
4683
+ lang: void 0,
4684
+ hints: TC,
4685
+ detail: "Technology choice validation",
4686
+ conf: 0.85
4687
+ },
4688
+ {
4689
+ pat: /\bwhat (is the latest|are the latest|version of|versions of)|what version|upgrade to latest|downgrade to|which version of\b/i,
4690
+ cat: "tech",
4691
+ sub: "version-check",
4692
+ lang: void 0,
4693
+ hints: TC,
4694
+ detail: "Version verification needed",
4695
+ conf: 0.9
4696
+ },
4697
+ {
4698
+ pat: /\b(adding|installing|using|switching to|migrating to) (a |an |the )?(package|dependency|library|module|framework|gem|crate)\b/i,
4699
+ cat: "tech",
4700
+ sub: "tech-choice",
4701
+ lang: void 0,
4702
+ hints: TC,
4703
+ detail: "Technology choice validation",
4704
+ conf: 0.8
4705
+ },
4706
+ // Commands that imply tech choices: "pip install X", "cargo add X", etc.
4707
+ {
4708
+ pat: /\b(pip install|pip3 install|pipenv install|poetry add|uv add)\s+[a-zA-Z0-9_-]+/i,
4709
+ cat: "tech",
4710
+ sub: "python-pkg",
4711
+ lang: "python",
4712
+ hints: TC,
4713
+ detail: "Python package choice \u2014 validate before installing",
4714
+ conf: 0.85
4715
+ },
4716
+ {
4717
+ pat: /\b(cargo add|cargo install)\s+[a-zA-Z0-9_-]+/i,
4718
+ cat: "tech",
4719
+ sub: "rust-crate",
4720
+ lang: "rust",
4721
+ hints: TC,
4722
+ detail: "Rust crate choice \u2014 validate before installing",
4723
+ conf: 0.85
4724
+ },
4725
+ {
4726
+ pat: /\b(go get|go install)\s+[a-zA-Z0-9_./-]+/i,
4727
+ cat: "tech",
4728
+ sub: "go-module",
4729
+ lang: "go",
4730
+ hints: TC,
4731
+ detail: "Go module choice \u2014 validate before installing",
4732
+ conf: 0.85
4733
+ },
4734
+ {
4735
+ pat: /\b(gem install|bundle add)\s+[a-zA-Z0-9_-]+/i,
4736
+ cat: "tech",
4737
+ sub: "ruby-gem",
4738
+ lang: "ruby",
4739
+ hints: TC,
4740
+ detail: "Ruby gem choice \u2014 validate before installing",
4741
+ conf: 0.85
4742
+ },
4743
+ {
4744
+ pat: /\b(npm install|pnpm add|yarn add)\s+[a-zA-Z0-9@/_-]+/i,
4745
+ cat: "tech",
4746
+ sub: "js-pkg",
4747
+ lang: "javascript",
4748
+ hints: TC,
4749
+ detail: "JS package choice \u2014 validate before installing",
4750
+ conf: 0.85
4751
+ },
4752
+ {
4753
+ pat: /\b(composer require|nuget install|dotnet add package)\s+[a-zA-Z0-9./_-]+/i,
4754
+ cat: "tech",
4755
+ sub: "pkg-choice",
4756
+ lang: void 0,
4757
+ hints: TC,
4758
+ detail: "Package choice \u2014 validate before installing",
4759
+ conf: 0.85
4760
+ },
4591
4761
  // ── Dependency / Import ───────────────────────────────────────────────
4592
4762
  {
4593
4763
  pat: /\bcannot find module|modulenotfounderror|no such module|missing module/i,
@@ -4724,7 +4894,7 @@ function needsSubagent(c) {
4724
4894
  return c.confidence < 0.85;
4725
4895
  }
4726
4896
  function isSimpleFix(c) {
4727
- return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85;
4897
+ return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85 || c.category === "tech" && c.confidence >= 0.85;
4728
4898
  }
4729
4899
 
4730
4900
  // src/slash-commands/fix.ts
@@ -4820,6 +4990,22 @@ function buildDirective(cli, errorText) {
4820
4990
  "3. Fix the config",
4821
4991
  "4. Verify"
4822
4992
  ].join("\n");
4993
+ case "tech":
4994
+ return [
4995
+ `## Tech Stack Validation${lang}`,
4996
+ "",
4997
+ "```",
4998
+ `${errorText}`,
4999
+ "```",
5000
+ "",
5001
+ "Your task:",
5002
+ "1. Detect the ecosystem from project files or context",
5003
+ "2. Verify the package/version against the correct registry",
5004
+ "3. Check if the package is dead, deprecated, or superseded (prehistoric)",
5005
+ "4. Check if the language standard library already covers this need",
5006
+ "5. Report APPROVED (with install command) or REJECTED (with replacement + migration)",
5007
+ `6. Use "This isn't code, this is X-year-old technology" when rejecting on age grounds`
5008
+ ].join("\n");
4823
5009
  case "perf":
4824
5010
  return [
4825
5011
  `## Fix: Performance / Memory Issue${lang}`,
@@ -4859,6 +5045,8 @@ function delegateRoleFor(cli) {
4859
5045
  return "security-scanner";
4860
5046
  case "perf":
4861
5047
  return "refactor-planner";
5048
+ case "tech":
5049
+ return "tech-stack";
4862
5050
  default:
4863
5051
  return "bug-hunter";
4864
5052
  }
@@ -4905,7 +5093,8 @@ Docker, Git, CI/CD, and more.
4905
5093
  | Security / secrets | Any | \`security-scanner\` |
4906
5094
  | Compiler error | Rust, Go, C/C++, Python | \`bug-hunter\` |
4907
5095
  | Dependency / import | Any | \`bug-hunter\` |
4908
- | Performance / leak | Any | \`bug-hunter\` + \`refactor-planner\` |
5096
+ | Tech choice / pkg | Any | \`tech-stack\` |
5097
+ | Performance / leak | Any | \`bug-hunter\` + \`refactor-planner\` |
4909
5098
  | Infrastructure | Config, Docker, Git, CI | \`bug-hunter\` |
4910
5099
  | React / Next.js | JavaScript | \`react-modern\` |
4911
5100
  | Node.js | JavaScript | \`node-modern\` |
@@ -4927,6 +5116,10 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
4927
5116
  /fix react-dom.development.js:172 Error: Invalid hook call
4928
5117
  /fix Security: hardcoded API key in config.ts
4929
5118
  /fix ERRO1014: SQL injection vulnerability in query builder
5119
+ /fix Should I use axios for API calls?
5120
+ /fix is moment.js still maintained?
5121
+ /fix pip install urllib2
5122
+ /fix what replaces lodash?
4930
5123
  \`\`\`
4931
5124
  `,
4932
5125
  async run(args, _ctx) {
@@ -4948,6 +5141,7 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
4948
5141
  ` /fix ${color.dim("AttributeError: 'NoneType' object has no attribute 'encode'")}`,
4949
5142
  ` /fix ${color.dim("react-dom.development.js:172 Error: Invalid hook call")}`,
4950
5143
  ` /fix ${color.dim("Security: hardcoded API key in config.ts")}`,
5144
+ ` /fix ${color.dim("Should I use axios for API calls?")}`,
4951
5145
  "",
4952
5146
  "Run `/help fix` for full documentation."
4953
5147
  ].join("\n")
@@ -5746,28 +5940,52 @@ function buildInitCommand(opts) {
5746
5940
  category: "Config",
5747
5941
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
5748
5942
  async run(_args, ctx) {
5749
- const dir = path9.join(ctx.projectRoot, ".wrongstack");
5750
- const file = path9.join(dir, "AGENTS.md");
5943
+ const dir = path10.join(ctx.projectRoot, ".wrongstack");
5944
+ const file = path10.join(dir, "AGENTS.md");
5945
+ const isFirstInit = !await fileExists(file);
5751
5946
  const detected = await detectProjectFacts(ctx.projectRoot);
5752
5947
  const body = renderAgentsTemplate(detected);
5753
5948
  await fsp4.mkdir(dir, { recursive: true });
5754
5949
  await fsp4.writeFile(file, body, "utf8");
5950
+ let nodePkg = false;
5951
+ try {
5952
+ await fsp4.access(path10.join(ctx.projectRoot, "package.json"));
5953
+ nodePkg = true;
5954
+ } catch {
5955
+ }
5956
+ const lines = [];
5957
+ lines.push(`Wrote ${file}`);
5755
5958
  if (detected.hints.length > 0) {
5756
- const msg2 = `Wrote ${file}
5757
- Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
5758
5959
  opts.renderer.writeInfo(`Wrote ${file}`);
5759
- opts.renderer.writeInfo(
5760
- `Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`
5761
- );
5762
- return { message: msg2 };
5960
+ const hintLine = `Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
5961
+ opts.renderer.writeInfo(hintLine);
5962
+ lines.push(hintLine);
5963
+ } else {
5964
+ opts.renderer.writeInfo(`Wrote ${file}`);
5965
+ lines.push("No project type auto-detected. Edit the file with project context and instructions the system prompt should carry.");
5763
5966
  }
5764
- const msg = `Wrote ${file}
5765
- No project type auto-detected. Edit the file with project context and instructions the system prompt should carry.`;
5766
- opts.renderer.writeInfo(`Wrote ${file}`);
5767
- return { message: msg };
5967
+ if (nodePkg && isFirstInit) {
5968
+ const techHint = [
5969
+ "",
5970
+ `${color.cyan("\u{1F4A1}")} ${color.bold("Tech Stack Audit")} \u2014 This is a Node.js project with a fresh init.`,
5971
+ color.dim(" The LLM may have suggested stale version numbers. Run"),
5972
+ ` ${color.cyan("/techstack --init")} to scan dependencies and verify versions.`
5973
+ ].join("\n");
5974
+ opts.renderer.write(techHint);
5975
+ lines.push(techHint);
5976
+ }
5977
+ return { message: lines.join("\n") };
5768
5978
  }
5769
5979
  };
5770
5980
  }
5981
+ async function fileExists(filePath) {
5982
+ try {
5983
+ await fsp4.access(filePath);
5984
+ return true;
5985
+ } catch {
5986
+ return false;
5987
+ }
5988
+ }
5771
5989
  function parseMcpArgs(args) {
5772
5990
  const trimmed = args.trim();
5773
5991
  if (!trimmed || trimmed === "list") return { action: "list", name: "" };
@@ -5962,9 +6180,9 @@ function stateBadge(state) {
5962
6180
  return color.dim(state);
5963
6181
  }
5964
6182
  }
5965
- async function readConfig(path27) {
6183
+ async function readConfig(path28) {
5966
6184
  try {
5967
- return JSON.parse(await fsp4.readFile(path27, "utf8"));
6185
+ return JSON.parse(await fsp4.readFile(path28, "utf8"));
5968
6186
  } catch {
5969
6187
  return {};
5970
6188
  }
@@ -5972,11 +6190,11 @@ async function readConfig(path27) {
5972
6190
  function isMcpServerRecord(value) {
5973
6191
  return !!value && typeof value === "object" && !Array.isArray(value);
5974
6192
  }
5975
- async function writeConfig(path27, cfg) {
6193
+ async function writeConfig(path28, cfg) {
5976
6194
  const raw = JSON.stringify(cfg, null, 2);
5977
- const tmp = path27 + ".tmp";
6195
+ const tmp = path28 + ".tmp";
5978
6196
  await fsp4.writeFile(tmp, raw, "utf8");
5979
- await fsp4.rename(tmp, path27);
6197
+ await fsp4.rename(tmp, path28);
5980
6198
  }
5981
6199
 
5982
6200
  // src/slash-commands/mcp.ts
@@ -6506,18 +6724,18 @@ var noOpVault2 = {
6506
6724
  };
6507
6725
  async function patchGlobalConfig(globalConfigPath, mutate) {
6508
6726
  let raw = "{}";
6509
- let fileExists = true;
6727
+ let fileExists2 = true;
6510
6728
  try {
6511
6729
  raw = await fsp4.readFile(globalConfigPath, "utf8");
6512
6730
  } catch (err) {
6513
6731
  if (err.code !== "ENOENT") throw err;
6514
- fileExists = false;
6732
+ fileExists2 = false;
6515
6733
  }
6516
6734
  let parsed;
6517
6735
  try {
6518
6736
  parsed = JSON.parse(raw);
6519
6737
  } catch (err) {
6520
- if (fileExists) {
6738
+ if (fileExists2) {
6521
6739
  throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
6522
6740
  }
6523
6741
  parsed = {};
@@ -6724,31 +6942,51 @@ function buildModelsCommand(opts) {
6724
6942
  function buildNextCommand(opts) {
6725
6943
  return {
6726
6944
  name: "next",
6727
- category: "Config",
6728
- description: "Toggle next-task prediction \u2014 show likely next steps after each turn.",
6729
- argsHint: "[on|off|toggle]",
6945
+ category: "Agent",
6946
+ description: "Show or select next-step suggestions. /next 1 to execute, /next list to view.",
6947
+ argsHint: "[on|off|toggle|list|clear|1 2 3...]",
6730
6948
  help: [
6731
6949
  "Usage:",
6732
- " /next Show whether next-task prediction is on or off",
6733
- " /next on Enable \u2014 after each turn, show 1-3 predicted next steps",
6734
- " /next off Disable (default)",
6735
- " /next toggle Flip the current state",
6950
+ " /next Show whether next-task prediction is on or off",
6951
+ " /next on Enable \u2014 after each turn, show 1-3 predicted next steps",
6952
+ " /next off Disable (default)",
6953
+ " /next toggle Flip the current state",
6954
+ " /next list Show the current suggestion list",
6955
+ " /next clear Clear the suggestion list",
6956
+ " /next 1 Execute suggestion #1 as the next agent turn",
6957
+ " /next 1 2 3 Execute suggestions 1, 2, and 3 in sequence",
6958
+ " /next 1,2,3 Same \u2014 comma separators work too",
6736
6959
  "",
6737
- "Predictions are informational only. They come from a cheap single-shot",
6738
- "model call (no tools, no context replay) and are never run automatically \u2014",
6739
- "copy or retype one to act on it. The setting persists across sessions."
6960
+ "Suggestions are generated automatically after each turn (when prediction is",
6961
+ `on) or manually via ${color.cyan("/suggest")}. Selected suggestions execute`,
6962
+ "immediately \u2014 no refinement or classification step."
6740
6963
  ].join("\n"),
6741
6964
  async run(args) {
6965
+ const trimmed = args.trim();
6966
+ if (/^\d[\d,\s]*$/.test(trimmed) && /\d/.test(trimmed)) {
6967
+ return handleSelection(trimmed, opts);
6968
+ }
6969
+ const arg = trimmed.toLowerCase();
6970
+ if (arg === "list" || arg === "ls" || arg === "show") {
6971
+ return handleList(opts);
6972
+ }
6973
+ if (arg === "clear" || arg === "reset") {
6974
+ opts.onSuggestions?.([]);
6975
+ return { message: color.dim("Suggestion list cleared.") };
6976
+ }
6742
6977
  if (!opts.onNextPredict) {
6743
6978
  const msg2 = "Next-task prediction is not available in this session.";
6744
6979
  opts.renderer.writeWarning(msg2);
6745
6980
  return { message: msg2 };
6746
6981
  }
6747
- const arg = args.trim().toLowerCase();
6748
6982
  const current = opts.onNextPredict();
6749
6983
  const label = (on) => on ? `${color.cyan("ON")} ${color.dim("(predicted next steps shown after each turn)")}` : `${color.green("OFF")} ${color.dim("(no predictions)")}`;
6750
6984
  if (!arg || arg === "status") {
6751
- const msg2 = `Next-task prediction: ${label(current)}`;
6985
+ const suggestions = opts.onSuggestions?.() ?? [];
6986
+ const msg2 = [
6987
+ `Next-task prediction: ${label(current)}`,
6988
+ suggestions.length > 0 ? color.dim(` ${suggestions.length} suggestion(s) available \u2014 use /next list to view, /next 1 to execute`) : color.dim(" No suggestions stored \u2014 use /suggest to generate")
6989
+ ].join("\n");
6752
6990
  opts.renderer.write(msg2);
6753
6991
  return { message: msg2 };
6754
6992
  }
@@ -6760,7 +6998,7 @@ function buildNextCommand(opts) {
6760
6998
  } else if (arg === "toggle" || arg === "cycle") {
6761
6999
  target = !current;
6762
7000
  } else {
6763
- const msg2 = `Unknown argument: ${arg}. Use /next on, off, or toggle.`;
7001
+ const msg2 = `Unknown argument: ${arg}. Use /next on, off, toggle, list, clear, or a number (e.g. /next 1).`;
6764
7002
  opts.renderer.writeWarning(msg2);
6765
7003
  return { message: msg2 };
6766
7004
  }
@@ -6771,6 +7009,67 @@ function buildNextCommand(opts) {
6771
7009
  }
6772
7010
  };
6773
7011
  }
7012
+ function handleSelection(input, opts) {
7013
+ const parts = input.split(/[\s,]+/).filter(Boolean);
7014
+ const indices = parts.map((p) => Number.parseInt(p, 10)).filter((n) => !Number.isNaN(n) && n > 0);
7015
+ if (indices.length === 0) {
7016
+ return { message: color.amber("No valid suggestion numbers found. Use /next 1, /next 1 2 3, etc.") };
7017
+ }
7018
+ const suggestions = opts.onSuggestions?.() ?? [];
7019
+ if (suggestions.length === 0) {
7020
+ return {
7021
+ message: color.amber("No suggestions available. Run /suggest first, or enable prediction with /next on.")
7022
+ };
7023
+ }
7024
+ const invalid = indices.filter((i) => i > suggestions.length);
7025
+ if (invalid.length > 0) {
7026
+ const max = suggestions.length;
7027
+ return {
7028
+ message: color.amber(`Invalid suggestion number(s): ${invalid.join(", ")}. Valid range: 1\u2013${max}.`)
7029
+ };
7030
+ }
7031
+ const selected = indices.map((i) => suggestions[i - 1]).filter((s) => s !== void 0);
7032
+ if (selected.length === 1) {
7033
+ const text = selected[0] ?? "";
7034
+ return {
7035
+ message: `${color.green("\u25B6")} Executing suggestion #${indices[0]}: ${color.dim(text)}`,
7036
+ runText: text
7037
+ };
7038
+ }
7039
+ const tasks = selected.map((s, i) => `${i + 1}. ${s}`).join("\n");
7040
+ const runText = [
7041
+ `## Execute the following tasks in order`,
7042
+ "",
7043
+ tasks,
7044
+ "",
7045
+ "Complete each task before moving to the next. Report results as you go."
7046
+ ].join("\n");
7047
+ return {
7048
+ message: `${color.green("\u25B6")} Executing ${selected.length} tasks: ${indices.join(", ")}`,
7049
+ runText
7050
+ };
7051
+ }
7052
+ function handleList(opts) {
7053
+ const suggestions = opts.onSuggestions?.() ?? [];
7054
+ if (suggestions.length === 0) {
7055
+ return {
7056
+ message: [
7057
+ color.dim("No suggestions available."),
7058
+ "",
7059
+ `Generate suggestions with ${color.cyan("/suggest")} or enable`,
7060
+ `prediction with ${color.cyan("/next on")}.`
7061
+ ].join("\n")
7062
+ };
7063
+ }
7064
+ const lines = [
7065
+ ` ${color.cyan("\u{1F4A1} Suggestions")} ${color.dim(`(use /next 1, /next 1 2 3 to execute)`)}`,
7066
+ ""
7067
+ ];
7068
+ for (let i = 0; i < suggestions.length; i++) {
7069
+ lines.push(` ${color.bold(`${i + 1}.`)} ${suggestions[i]}`);
7070
+ }
7071
+ return { message: lines.join("\n") };
7072
+ }
6774
7073
 
6775
7074
  // src/slash-commands/plugin.ts
6776
7075
  function buildPluginCommand(opts) {
@@ -7108,18 +7407,18 @@ function fmtEntry(e) {
7108
7407
  }
7109
7408
  async function patchGlobalConfig2(globalConfigPath, mutate) {
7110
7409
  let raw = "{}";
7111
- let fileExists = true;
7410
+ let fileExists2 = true;
7112
7411
  try {
7113
7412
  raw = await fsp4.readFile(globalConfigPath, "utf8");
7114
7413
  } catch (err) {
7115
7414
  if (err.code !== "ENOENT") throw err;
7116
- fileExists = false;
7415
+ fileExists2 = false;
7117
7416
  }
7118
7417
  let parsed;
7119
7418
  try {
7120
7419
  parsed = JSON.parse(raw);
7121
7420
  } catch (err) {
7122
- if (fileExists)
7421
+ if (fileExists2)
7123
7422
  throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
7124
7423
  parsed = {};
7125
7424
  }
@@ -7559,6 +7858,161 @@ function buildModelCapsCommand(opts) {
7559
7858
  }
7560
7859
  };
7561
7860
  }
7861
+ function collectContext(opts) {
7862
+ const parts = [];
7863
+ try {
7864
+ const gitStatus = execFileSync("git", ["status", "--short", "--branch"], {
7865
+ cwd: opts.projectRoot,
7866
+ encoding: "utf8",
7867
+ timeout: 5e3,
7868
+ windowsHide: true
7869
+ }).trim();
7870
+ if (gitStatus) {
7871
+ parts.push("### Git Status", "```", gitStatus, "```");
7872
+ }
7873
+ } catch {
7874
+ }
7875
+ parts.push(`Working directory: ${opts.cwd}`);
7876
+ parts.push(`Project root: ${opts.projectRoot}`);
7877
+ return parts.join("\n");
7878
+ }
7879
+ function buildSuggestPrompt(contextText) {
7880
+ return [
7881
+ "## Suggest Next Steps",
7882
+ "",
7883
+ "Based on the current project state below, generate 3-5 actionable next-step",
7884
+ "suggestions. Each suggestion must be a single imperative sentence that can be",
7885
+ "executed immediately. Be specific \u2014 mention file names, tool names, or commands",
7886
+ "when relevant. Do NOT include preamble, explanation, or wrap in code blocks.",
7887
+ "",
7888
+ "Rules:",
7889
+ '- One suggestion per line, prefixed with the number (e.g. "1. Run tests...")',
7890
+ "- Order by priority: most impactful first",
7891
+ "- Suggestions should be independent \u2014 user can pick any subset",
7892
+ '- If nothing is needed, say "No pending actions \u2014 everything is up to date."',
7893
+ "",
7894
+ contextText || "(No project context available \u2014 suggest generic next steps.)",
7895
+ "",
7896
+ "Output format (strict \u2014 no other text):",
7897
+ "1. First suggestion here",
7898
+ "2. Second suggestion here",
7899
+ "3. Third suggestion here"
7900
+ ].join("\n");
7901
+ }
7902
+ function buildSuggestCommand(opts) {
7903
+ return {
7904
+ name: "suggest",
7905
+ aliases: ["next-steps", "what-next"],
7906
+ category: "Agent",
7907
+ description: "Generate context-aware next-step suggestions for the current session.",
7908
+ argsHint: "[--fast]",
7909
+ help: [
7910
+ "Usage:",
7911
+ " /suggest Generate suggestions using a lightweight subagent",
7912
+ " /suggest --fast Heuristic-only suggestions (no subagent, instant)",
7913
+ "",
7914
+ "Analyzes the current session state (git status, working directory, recent",
7915
+ "activity) and generates 3-5 actionable next-step suggestions. Suggestions",
7916
+ "are stored and can be selected with `/next 1`, `/next 1 2 3`, etc.",
7917
+ "",
7918
+ "Use `/next list` to see the current suggestions at any time."
7919
+ ].join("\n"),
7920
+ async run(args, _ctx) {
7921
+ const trimmed = args.trim().toLowerCase();
7922
+ const fast = /\b(--fast|-f)\b/.test(trimmed);
7923
+ if (fast) {
7924
+ const suggestions = generateHeuristicSuggestions(opts);
7925
+ opts.onSuggestions?.(suggestions);
7926
+ const display = formatSuggestions(suggestions);
7927
+ return { message: display };
7928
+ }
7929
+ if (!opts.onSpawnAndWait) {
7930
+ const suggestions = generateHeuristicSuggestions(opts);
7931
+ opts.onSuggestions?.(suggestions);
7932
+ const display = formatSuggestions(suggestions) + "\n" + color.dim("(Heuristic fallback \u2014 multi-agent not enabled)");
7933
+ return { message: display };
7934
+ }
7935
+ const contextText = collectContext({
7936
+ cwd: opts.cwd,
7937
+ projectRoot: opts.projectRoot
7938
+ });
7939
+ const task = buildSuggestPrompt(contextText);
7940
+ opts.renderer.write(color.dim("Generating suggestions..."));
7941
+ try {
7942
+ const raw = await opts.onSpawnAndWait(task, {
7943
+ name: "suggest"
7944
+ });
7945
+ const suggestions = parseSuggestions(raw);
7946
+ if (suggestions.length === 0) {
7947
+ const fallback = ["No pending actions \u2014 everything is up to date."];
7948
+ opts.onSuggestions?.(fallback);
7949
+ return { message: formatSuggestions(fallback) };
7950
+ }
7951
+ opts.onSuggestions?.(suggestions);
7952
+ return { message: formatSuggestions(suggestions) };
7953
+ } catch (err) {
7954
+ const msg = `Suggestion generation failed: ${err instanceof Error ? err.message : String(err)}`;
7955
+ opts.renderer.writeWarning(msg);
7956
+ return { message: msg };
7957
+ }
7958
+ }
7959
+ };
7960
+ }
7961
+ function parseSuggestions(raw) {
7962
+ const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
7963
+ const numbered = lines.filter((l) => /^\d+[.)]\s/.test(l)).map((l) => l.replace(/^\d+[.)]\s*/, "").trim());
7964
+ if (numbered.length > 0) return numbered.slice(0, 5);
7965
+ const bullets = lines.filter((l) => /^[-*•]\s/.test(l)).map((l) => l.replace(/^[-*•]\s*/, "").trim());
7966
+ if (bullets.length > 0) return bullets.slice(0, 5);
7967
+ return lines.filter((l) => l.length > 10 && !l.startsWith("#") && !l.startsWith("```")).slice(0, 5);
7968
+ }
7969
+ function generateHeuristicSuggestions(opts) {
7970
+ const suggestions = [];
7971
+ try {
7972
+ const gitStatus = execFileSync("git", ["status", "--short"], {
7973
+ cwd: opts.projectRoot,
7974
+ encoding: "utf8",
7975
+ timeout: 5e3,
7976
+ windowsHide: true
7977
+ }).trim();
7978
+ if (gitStatus) {
7979
+ const staged = gitStatus.split("\n").filter((l) => /^[MADRC]/.test(l)).length;
7980
+ const unstaged = gitStatus.split("\n").filter((l) => /^.[MADRC]/.test(l)).length;
7981
+ const untracked = gitStatus.split("\n").filter((l) => l.startsWith("??")).length;
7982
+ if (staged > 0) {
7983
+ suggestions.push(`Commit ${staged} staged file(s) with a descriptive message`);
7984
+ }
7985
+ if (unstaged > 0) {
7986
+ suggestions.push(`Stage and review ${unstaged} modified file(s)`);
7987
+ }
7988
+ if (untracked > 0) {
7989
+ suggestions.push(`Review ${untracked} untracked file(s) \u2014 add to git or .gitignore`);
7990
+ }
7991
+ }
7992
+ } catch {
7993
+ }
7994
+ if (suggestions.length === 0) {
7995
+ suggestions.push("Review recent changes with a diff");
7996
+ suggestions.push("Run the test suite to verify everything passes");
7997
+ suggestions.push("Check for lint or type errors");
7998
+ }
7999
+ return suggestions.slice(0, 5);
8000
+ }
8001
+ function formatSuggestions(suggestions) {
8002
+ if (suggestions.length === 0) {
8003
+ return color.dim("No suggestions available.");
8004
+ }
8005
+ const lines = [
8006
+ ` ${color.cyan("\u{1F4A1} Next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}`,
8007
+ ""
8008
+ ];
8009
+ for (let i = 0; i < suggestions.length; i++) {
8010
+ const num = color.bold(`${i + 1}.`);
8011
+ const text = suggestions[i] ?? "";
8012
+ lines.push(` ${num} ${text}`);
8013
+ }
8014
+ return lines.join("\n");
8015
+ }
7562
8016
  var noOpVault4 = {
7563
8017
  encrypt: (v) => v,
7564
8018
  decrypt: (v) => v,
@@ -7956,7 +8410,7 @@ var DEFAULTS = {
7956
8410
  cost: true
7957
8411
  };
7958
8412
  function resolveConfigPath() {
7959
- return process.env[CONFIG_ENV] ?? path9.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
8413
+ return process.env[CONFIG_ENV] ?? path10.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
7960
8414
  }
7961
8415
  async function loadStatuslineConfig() {
7962
8416
  const p = resolveConfigPath();
@@ -7970,7 +8424,7 @@ async function loadStatuslineConfig() {
7970
8424
  async function saveStatuslineConfig(cfg) {
7971
8425
  const p = resolveConfigPath();
7972
8426
  try {
7973
- await fsp4.mkdir(path9.dirname(p), { recursive: true });
8427
+ await fsp4.mkdir(path10.dirname(p), { recursive: true });
7974
8428
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
7975
8429
  } catch (err) {
7976
8430
  throw new FsError({
@@ -8397,6 +8851,199 @@ function buildWorktreeCommand(opts) {
8397
8851
  }
8398
8852
  };
8399
8853
  }
8854
+ async function discoverPackageFiles(projectRoot) {
8855
+ const files = [];
8856
+ const rootPkg = path10.join(projectRoot, "package.json");
8857
+ try {
8858
+ await fsp4.access(rootPkg);
8859
+ files.push(rootPkg);
8860
+ } catch {
8861
+ }
8862
+ const workspaceFile = path10.join(projectRoot, "pnpm-workspace.yaml");
8863
+ try {
8864
+ await fsp4.access(workspaceFile);
8865
+ const content = await fsp4.readFile(workspaceFile, "utf8");
8866
+ const globMatch = /packages?:\s*\[([^\]]+)\]/s.exec(content);
8867
+ const rawGlobs = globMatch?.[1];
8868
+ if (!rawGlobs) return files;
8869
+ const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
8870
+ for (const g of globs) {
8871
+ const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
8872
+ const dir = path10.join(projectRoot, dirPrefix);
8873
+ try {
8874
+ const entries = await fsp4.readdir(dir, { withFileTypes: true });
8875
+ for (const e of entries) {
8876
+ if (!e.isDirectory()) continue;
8877
+ const subPkg = path10.join(dir, e.name, "package.json");
8878
+ try {
8879
+ await fsp4.access(subPkg);
8880
+ files.push(subPkg);
8881
+ } catch {
8882
+ }
8883
+ }
8884
+ } catch {
8885
+ }
8886
+ }
8887
+ } catch {
8888
+ }
8889
+ return files;
8890
+ }
8891
+ function buildTechStackTask(opts) {
8892
+ const pkgList = opts.packageFiles.map((f) => ` - ${path10.relative(opts.projectRoot, f)}`).join("\n");
8893
+ const header = opts.isInit ? [
8894
+ "## Tech Stack Audit \u2014 First-Time Project Init",
8895
+ "",
8896
+ "This project is being initialized for the first time. Scan its dependencies,",
8897
+ "check every package and framework version against the npm registry, and produce",
8898
+ "a report that warns about outdated choices. The LLM that scaffolded this project",
8899
+ "may have suggested stale version numbers \u2014 verify every single one.",
8900
+ ""
8901
+ ].join("\n") : [
8902
+ "## Tech Stack Audit \u2014 Full Project Scan",
8903
+ "",
8904
+ "Scan all project dependencies, verify every package version against the npm",
8905
+ "registry, and produce a report that flags outdated, dead, or obsolete packages.",
8906
+ ""
8907
+ ].join("\n");
8908
+ const outputPath = opts.outputFormat === "json" ? "techstack.json" : "techstack.md";
8909
+ return [
8910
+ header,
8911
+ "",
8912
+ "### Project package files to scan",
8913
+ `${pkgList || " - package.json (root only)"}`,
8914
+ "",
8915
+ "### Instructions",
8916
+ "",
8917
+ "1. **Read** each package.json and extract ALL dependencies (dependencies +",
8918
+ " devDependencies + peerDependencies). Include the workspace root.",
8919
+ "",
8920
+ "2. **For every dependency**, look up its latest version from the npm registry:",
8921
+ ' - `fetch("https://registry.npmjs.org/<package>/latest")`',
8922
+ " - Extract the `version` field from the JSON response",
8923
+ " - Also check `description`, `license`, and `time` fields for age/dead checks",
8924
+ "",
8925
+ "3. **For each package, determine status:**",
8926
+ " - \u{1F7E2} CURRENT: installed version is within 1 minor of latest",
8927
+ " - \u{1F7E1} OUTDATED: installed version is behind latest (major or >1 minor gap)",
8928
+ " - \u{1F534} CRITICAL: package has known CVEs, is deprecated, or >2 years without release",
8929
+ " - \u2620\uFE0F DEAD: package is deprecated, archived, or superseded \u22655 years ago",
8930
+ "",
8931
+ "4. **Apply the tech-stack skill rules:**",
8932
+ ' - Reject packages that are "prehistoric" (superseded \u22655 years ago)',
8933
+ " - Flag dead packages (no release >2 years + critical issues)",
8934
+ " - Prefer Node.js built-ins over third-party packages",
8935
+ "",
8936
+ `5. **Write the report** to \`${outputPath}\` in the project root:`,
8937
+ " - Markdown format: grouped by category, with version tables and warnings",
8938
+ " - JSON format: structured array with name, current, latest, status, notes",
8939
+ "",
8940
+ "6. **Report a summary to chat**: total packages scanned, counts per status,",
8941
+ " top 5 most urgent issues, and total cost estimate (optional).",
8942
+ "",
8943
+ "### Output format (markdown)",
8944
+ "",
8945
+ "```markdown",
8946
+ "# Tech Stack Report",
8947
+ "",
8948
+ "Generated: <date> \xB7 Scanned: <total> packages across <N> files",
8949
+ "",
8950
+ "## \u{1F7E2} Up to Date (<count>)",
8951
+ "| Package | Current | Latest | Age | Notes |",
8952
+ "|---------|---------|--------|-----|-------|",
8953
+ "",
8954
+ "## \u{1F7E1} Outdated (<count>)",
8955
+ "...",
8956
+ "",
8957
+ "## \u{1F534} Critical Issues (<count>)",
8958
+ "...",
8959
+ "",
8960
+ "## \u2620\uFE0F Dead / Obsolete (<count>)",
8961
+ "...",
8962
+ "",
8963
+ "## Recommendations",
8964
+ "- Top 3-5 actionable fixes",
8965
+ "```",
8966
+ "",
8967
+ "### Guardrails",
8968
+ "",
8969
+ "- Use `fetch()` with `AbortSignal.timeout(10000)` on every npm registry call.",
8970
+ "- Skip packages that return 404 (private/internal packages).",
8971
+ "- Deduplicate: same package in multiple package.json files = one row.",
8972
+ "- Do NOT modify any files except writing the report.",
8973
+ "- Run in 2-3 iterations max. Parallel fetch where possible.",
8974
+ "- **IMPORTANT**: Output the chat summary FIRST, then write the file. I need to see results."
8975
+ ].join("\n");
8976
+ }
8977
+ function buildTechStackCommand(opts) {
8978
+ return {
8979
+ name: "techstack",
8980
+ category: "Inspect",
8981
+ aliases: ["tech", "deps"],
8982
+ description: "Scan all project dependencies, verify versions against npm, and produce a techstack report.",
8983
+ argsHint: "[--json] [--init]",
8984
+ help: [
8985
+ "Usage:",
8986
+ " /techstack Scan dependencies + write techstack.md report",
8987
+ " /techstack --json Write techstack.json instead of markdown",
8988
+ " /techstack --init Init-mode scan (compares scaffolded vs latest)",
8989
+ "",
8990
+ "Spawns a subagent that:",
8991
+ " 1. Reads every package.json in the project",
8992
+ " 2. Looks up latest versions on the npm registry",
8993
+ " 3. Flags outdated, dead, or obsolete packages",
8994
+ ` 4. Writes a ${color.cyan("techstack.md")} (or .json) report to the project root`,
8995
+ "",
8996
+ "Uses the `tech-stack` skill for version verification rules.",
8997
+ `Hooked into ${color.cyan("/init")} \u2014 runs automatically on first project setup.`
8998
+ ].join("\n"),
8999
+ async run(args, _ctx) {
9000
+ const trimmed = args.trim().toLowerCase();
9001
+ const outputFormat = /\b(--json|-j)\b/.test(trimmed) ? "json" : "md";
9002
+ const isInit = /\b(--init|-i)\b/.test(trimmed);
9003
+ let packageFiles = [];
9004
+ let discoveryNote = "";
9005
+ try {
9006
+ packageFiles = await discoverPackageFiles(opts.projectRoot);
9007
+ if (packageFiles.length === 0) {
9008
+ discoveryNote = color.amber(
9009
+ "\u26A0 No package.json files found. This does not look like a Node.js project."
9010
+ );
9011
+ }
9012
+ } catch (err) {
9013
+ discoveryNote = color.red(
9014
+ `Could not scan for package files: ${err instanceof Error ? err.message : String(err)}`
9015
+ );
9016
+ }
9017
+ const task = buildTechStackTask({
9018
+ projectRoot: opts.projectRoot,
9019
+ packageFiles,
9020
+ outputFormat,
9021
+ isInit
9022
+ });
9023
+ if (!opts.onSpawnAndWait) {
9024
+ const msg = "Multi-agent is not enabled in this session. Cannot spawn techstack subagent.";
9025
+ opts.renderer.writeWarning(msg);
9026
+ return { message: msg };
9027
+ }
9028
+ const header = isInit ? "Tech Stack Init Audit" : "Tech Stack Audit";
9029
+ const label = `${color.cyan("\u{1F50D}")} ${color.bold(header)} ${color.dim(`(${packageFiles.length} package files)`)}`;
9030
+ opts.renderer.write(label);
9031
+ if (discoveryNote) opts.renderer.write(discoveryNote);
9032
+ opts.renderer.write(
9033
+ color.dim(`Spawning tech-stack subagent \u2192 writes ${outputFormat === "json" ? "techstack.json" : "techstack.md"} when done.`)
9034
+ );
9035
+ try {
9036
+ const name = isInit ? "techstack-init" : "techstack-audit";
9037
+ const summary = await opts.onSpawnAndWait(task, { name });
9038
+ return { message: summary };
9039
+ } catch (err) {
9040
+ const msg = `Tech stack scan failed: ${err instanceof Error ? err.message : String(err)}`;
9041
+ opts.renderer.writeWarning(msg);
9042
+ return { message: msg };
9043
+ }
9044
+ }
9045
+ };
9046
+ }
8400
9047
  function buildYoloCommand(opts) {
8401
9048
  return {
8402
9049
  name: "yolo",
@@ -8455,11 +9102,14 @@ function buildBuiltinSlashCommands(opts) {
8455
9102
  buildClearCommand(opts),
8456
9103
  buildCompactCommand(opts),
8457
9104
  buildContextCommand(opts),
9105
+ buildDevCommand(opts),
8458
9106
  buildCodebaseReindexCommand(opts),
9107
+ buildTechStackCommand(opts),
8459
9108
  buildToolsCommand(opts),
8460
9109
  buildPluginCommand(opts),
8461
9110
  buildPruneCommand(opts),
8462
9111
  buildMcpSlashCommand(opts),
9112
+ buildSuggestCommand(opts),
8463
9113
  buildAuthCommand(opts),
8464
9114
  buildDiagCommand(opts),
8465
9115
  buildStatsCommand(opts),
@@ -8517,13 +9167,13 @@ var MANIFESTS = [
8517
9167
  ];
8518
9168
  async function detectProjectKind(projectRoot) {
8519
9169
  try {
8520
- await fsp4.access(path9.join(projectRoot, ".wrongstack", "AGENTS.md"));
9170
+ await fsp4.access(path10.join(projectRoot, ".wrongstack", "AGENTS.md"));
8521
9171
  return "initialized";
8522
9172
  } catch {
8523
9173
  }
8524
9174
  for (const m of MANIFESTS) {
8525
9175
  try {
8526
- await fsp4.access(path9.join(projectRoot, m));
9176
+ await fsp4.access(path10.join(projectRoot, m));
8527
9177
  return "project";
8528
9178
  } catch {
8529
9179
  }
@@ -8531,8 +9181,8 @@ async function detectProjectKind(projectRoot) {
8531
9181
  return "empty";
8532
9182
  }
8533
9183
  async function scaffoldAgentsMd(projectRoot) {
8534
- const dir = path9.join(projectRoot, ".wrongstack");
8535
- const file = path9.join(dir, "AGENTS.md");
9184
+ const dir = path10.join(projectRoot, ".wrongstack");
9185
+ const file = path10.join(dir, "AGENTS.md");
8536
9186
  const facts = await detectProjectFacts(projectRoot);
8537
9187
  const body = renderAgentsTemplate(facts);
8538
9188
  await fsp4.mkdir(dir, { recursive: true });
@@ -8545,7 +9195,7 @@ async function runProjectCheck(opts) {
8545
9195
  if (kind === "initialized") {
8546
9196
  renderer.write(
8547
9197
  `
8548
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path9.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
9198
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path10.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
8549
9199
  `
8550
9200
  );
8551
9201
  return true;
@@ -8576,7 +9226,7 @@ async function runProjectCheck(opts) {
8576
9226
  }
8577
9227
  return true;
8578
9228
  }
8579
- const gitDir = path9.join(projectRoot, ".git");
9229
+ const gitDir = path10.join(projectRoot, ".git");
8580
9230
  let hasGit = false;
8581
9231
  try {
8582
9232
  await fsp4.access(gitDir);
@@ -8770,7 +9420,7 @@ var ReadlineInputReader = class {
8770
9420
  history = [];
8771
9421
  pending = false;
8772
9422
  constructor(opts = {}) {
8773
- this.historyFile = opts.historyFile ?? path9.join(os2.homedir(), ".wrongstack", "history");
9423
+ this.historyFile = opts.historyFile ?? path10.join(os2.homedir(), ".wrongstack", "history");
8774
9424
  }
8775
9425
  async loadHistory() {
8776
9426
  try {
@@ -8782,7 +9432,7 @@ var ReadlineInputReader = class {
8782
9432
  }
8783
9433
  async saveHistory() {
8784
9434
  try {
8785
- await fsp4.mkdir(path9.dirname(this.historyFile), { recursive: true });
9435
+ await fsp4.mkdir(path10.dirname(this.historyFile), { recursive: true });
8786
9436
  await fsp4.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
8787
9437
  } catch {
8788
9438
  }
@@ -9075,20 +9725,20 @@ function assertSafeToDelete(filename, parentDir) {
9075
9725
  if (PROTECTED_BASENAMES.has(filename)) {
9076
9726
  throw new Error(`Refusing to delete protected file: ${filename}`);
9077
9727
  }
9078
- if (filename !== path9.basename(filename)) {
9728
+ if (filename !== path10.basename(filename)) {
9079
9729
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
9080
9730
  }
9081
9731
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
9082
9732
  throw new Error(`Refusing to delete unknown file: ${filename}`);
9083
9733
  }
9084
- const resolvedParent = path9.resolve(parentDir);
9734
+ const resolvedParent = path10.resolve(parentDir);
9085
9735
  if (!resolvedParent.endsWith(".wrongstack")) {
9086
9736
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
9087
9737
  }
9088
9738
  }
9089
9739
  async function safeDelete(filePath) {
9090
- const dir = path9.dirname(filePath);
9091
- const filename = path9.basename(filePath);
9740
+ const dir = path10.dirname(filePath);
9741
+ const filename = path10.basename(filePath);
9092
9742
  try {
9093
9743
  assertSafeToDelete(filename, dir);
9094
9744
  await fsp4.unlink(filePath);
@@ -9133,16 +9783,16 @@ function diffSummary(oldCfg, newCfg) {
9133
9783
  }
9134
9784
  var defaultHomeDir = () => os2__default.homedir();
9135
9785
  function historyDir(homeFn = defaultHomeDir) {
9136
- return path9.join(homeFn(), ".wrongstack", "config.history", "entries");
9786
+ return path10.join(homeFn(), ".wrongstack", "config.history", "entries");
9137
9787
  }
9138
9788
  function historyIndexPath(homeFn = defaultHomeDir) {
9139
- return path9.join(homeFn(), ".wrongstack", "config.history", "index.json");
9789
+ return path10.join(homeFn(), ".wrongstack", "config.history", "index.json");
9140
9790
  }
9141
9791
  function configPath(homeFn = defaultHomeDir) {
9142
- return path9.join(homeFn(), ".wrongstack", "config.json");
9792
+ return path10.join(homeFn(), ".wrongstack", "config.json");
9143
9793
  }
9144
9794
  function backupLastPath(homeFn = defaultHomeDir) {
9145
- return path9.join(homeFn(), ".wrongstack", "config.json.last");
9795
+ return path10.join(homeFn(), ".wrongstack", "config.json.last");
9146
9796
  }
9147
9797
  function entryId(ts) {
9148
9798
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -9197,17 +9847,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
9197
9847
  }
9198
9848
  if (content !== void 0) {
9199
9849
  try {
9200
- const bakPath = path9.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
9850
+ const bakPath = path10.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
9201
9851
  await atomicWrite(bakPath, content);
9202
9852
  } catch {
9203
9853
  }
9204
9854
  }
9205
9855
  try {
9206
- const dir = path9.join(homeFn(), ".wrongstack");
9856
+ const dir = path10.join(homeFn(), ".wrongstack");
9207
9857
  const files = await fsp4.readdir(dir);
9208
9858
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
9209
9859
  for (const f of baks.slice(10)) {
9210
- await safeDelete(path9.join(dir, f));
9860
+ await safeDelete(path10.join(dir, f));
9211
9861
  }
9212
9862
  } catch {
9213
9863
  }
@@ -9225,7 +9875,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
9225
9875
  };
9226
9876
  try {
9227
9877
  await fsp4.writeFile(
9228
- path9.join(historyDir(homeFn), `${id}.json`),
9878
+ path10.join(historyDir(homeFn), `${id}.json`),
9229
9879
  JSON.stringify(entry, null, 2),
9230
9880
  "utf8"
9231
9881
  );
@@ -9233,7 +9883,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
9233
9883
  throw new FsError({
9234
9884
  message: err instanceof Error ? err.message : String(err),
9235
9885
  code: ERROR_CODES.FS_WRITE_FAILED,
9236
- path: path9.join(historyDir(homeFn), `${id}.json`),
9886
+ path: path10.join(historyDir(homeFn), `${id}.json`),
9237
9887
  cause: err
9238
9888
  });
9239
9889
  }
@@ -9248,7 +9898,7 @@ async function listHistory(homeFn = defaultHomeDir) {
9248
9898
  }
9249
9899
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
9250
9900
  try {
9251
- const raw = await fsp4.readFile(path9.join(historyDir(homeFn), `${id}.json`), "utf8");
9901
+ const raw = await fsp4.readFile(path10.join(historyDir(homeFn), `${id}.json`), "utf8");
9252
9902
  return JSON.parse(raw);
9253
9903
  } catch {
9254
9904
  return null;
@@ -9314,10 +9964,10 @@ var theme = { primary: color.amber };
9314
9964
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
9315
9965
  try {
9316
9966
  const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
9317
- const fs29 = await import('fs/promises');
9967
+ const fs30 = await import('fs/promises');
9318
9968
  let existing = {};
9319
9969
  try {
9320
- const raw = await fs29.readFile(configPath2, "utf8");
9970
+ const raw = await fs30.readFile(configPath2, "utf8");
9321
9971
  existing = JSON.parse(raw);
9322
9972
  } catch {
9323
9973
  }
@@ -9653,12 +10303,12 @@ function pickGroupIndex(opts) {
9653
10303
  try {
9654
10304
  let current = 0;
9655
10305
  try {
9656
- const parsed = Number.parseInt(fs14.readFileSync(opts.cursorFile, "utf8").trim(), 10);
10306
+ const parsed = Number.parseInt(fs15.readFileSync(opts.cursorFile, "utf8").trim(), 10);
9657
10307
  if (Number.isFinite(parsed)) current = wrap(parsed);
9658
10308
  } catch {
9659
10309
  }
9660
- fs14.mkdirSync(path9.dirname(opts.cursorFile), { recursive: true });
9661
- fs14.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
10310
+ fs15.mkdirSync(path10.dirname(opts.cursorFile), { recursive: true });
10311
+ fs15.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
9662
10312
  return current;
9663
10313
  } catch {
9664
10314
  }
@@ -9994,14 +10644,14 @@ function summarize(value, name) {
9994
10644
  if (typeof v === "object" && v !== null) {
9995
10645
  const o = v;
9996
10646
  if (name === "edit") {
9997
- const path27 = typeof o["path"] === "string" ? o["path"] : "";
10647
+ const path28 = typeof o["path"] === "string" ? o["path"] : "";
9998
10648
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
9999
- return `${path27} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
10649
+ return `${path28} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
10000
10650
  }
10001
10651
  if (name === "write") {
10002
- const path27 = typeof o["path"] === "string" ? o["path"] : "";
10652
+ const path28 = typeof o["path"] === "string" ? o["path"] : "";
10003
10653
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
10004
- return bytes !== void 0 ? `${path27} ${bytes}B` : path27;
10654
+ return bytes !== void 0 ? `${path28} ${bytes}B` : path28;
10005
10655
  }
10006
10656
  if (typeof o["count"] === "number") {
10007
10657
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -11219,7 +11869,7 @@ var doctorCmd = async (_args, deps) => {
11219
11869
  }
11220
11870
  try {
11221
11871
  await fsp4.mkdir(deps.paths.projectSessions, { recursive: true });
11222
- const probe = path9.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
11872
+ const probe = path10.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
11223
11873
  await fsp4.writeFile(probe, "");
11224
11874
  await fsp4.unlink(probe);
11225
11875
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -11322,8 +11972,8 @@ var exportCmd = async (args, deps) => {
11322
11972
  return 1;
11323
11973
  }
11324
11974
  if (output) {
11325
- await fsp4.mkdir(path9.dirname(path9.resolve(deps.cwd, output)), { recursive: true });
11326
- await fsp4.writeFile(path9.resolve(deps.cwd, output), rendered, "utf8");
11975
+ await fsp4.mkdir(path10.dirname(path10.resolve(deps.cwd, output)), { recursive: true });
11976
+ await fsp4.writeFile(path10.resolve(deps.cwd, output), rendered, "utf8");
11327
11977
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
11328
11978
  `);
11329
11979
  } else {
@@ -11396,8 +12046,8 @@ var initCmd = async (_args, deps) => {
11396
12046
  const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
11397
12047
  const encrypted = encryptConfigSecrets(config, vault);
11398
12048
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
11399
- await fsp4.mkdir(path9.join(deps.projectRoot, ".wrongstack"), { recursive: true });
11400
- const agentsFile = path9.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
12049
+ await fsp4.mkdir(path10.join(deps.projectRoot, ".wrongstack"), { recursive: true });
12050
+ const agentsFile = path10.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
11401
12051
  const projectFacts = await detectProjectFacts(deps.projectRoot);
11402
12052
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
11403
12053
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -11854,7 +12504,7 @@ var usageCmd = async (_args, deps) => {
11854
12504
  return 0;
11855
12505
  };
11856
12506
  var projectsCmd = async (_args, deps) => {
11857
- const projectsRoot = path9.join(deps.paths.globalRoot, "projects");
12507
+ const projectsRoot = path10.join(deps.paths.globalRoot, "projects");
11858
12508
  try {
11859
12509
  const entries = await fsp4.readdir(projectsRoot);
11860
12510
  if (entries.length === 0) {
@@ -11864,7 +12514,7 @@ var projectsCmd = async (_args, deps) => {
11864
12514
  for (const hash of entries) {
11865
12515
  try {
11866
12516
  const meta = JSON.parse(
11867
- await fsp4.readFile(path9.join(projectsRoot, hash, "meta.json"), "utf8")
12517
+ await fsp4.readFile(path10.join(projectsRoot, hash, "meta.json"), "utf8")
11868
12518
  );
11869
12519
  deps.renderer.write(
11870
12520
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -12071,20 +12721,20 @@ ${navLines.join(" \xB7 ")}
12071
12721
  async function mutateModelsConfig(deps, mutator) {
12072
12722
  const vault = deps.vault;
12073
12723
  const configPath2 = deps.paths.globalConfig;
12074
- let fileExists = true;
12724
+ let fileExists2 = true;
12075
12725
  let raw;
12076
12726
  try {
12077
12727
  raw = await fsp4.readFile(configPath2, "utf8");
12078
12728
  } catch (err) {
12079
12729
  if (err.code !== "ENOENT") throw err;
12080
- fileExists = false;
12730
+ fileExists2 = false;
12081
12731
  raw = "{}";
12082
12732
  }
12083
12733
  let parsed;
12084
12734
  try {
12085
12735
  parsed = JSON.parse(raw);
12086
12736
  } catch (err) {
12087
- if (fileExists) {
12737
+ if (fileExists2) {
12088
12738
  throw new Error(
12089
12739
  `Refusing to overwrite corrupt config at ${configPath2} (${err.message}).`
12090
12740
  );
@@ -12243,7 +12893,7 @@ async function listFleetRuns(deps) {
12243
12893
  }
12244
12894
  const runs = [];
12245
12895
  for (const id of entries) {
12246
- const runDir = path9.join(deps.paths.projectSessions, id);
12896
+ const runDir = path10.join(deps.paths.projectSessions, id);
12247
12897
  let stat4;
12248
12898
  try {
12249
12899
  stat4 = await fsp4.stat(runDir);
@@ -12256,17 +12906,17 @@ async function listFleetRuns(deps) {
12256
12906
  let subagentCount = 0;
12257
12907
  let subagentsDir;
12258
12908
  try {
12259
- await fsp4.access(path9.join(runDir, "fleet.json"));
12909
+ await fsp4.access(path10.join(runDir, "fleet.json"));
12260
12910
  manifest = true;
12261
12911
  } catch {
12262
12912
  }
12263
12913
  try {
12264
- await fsp4.access(path9.join(runDir, "checkpoint.json"));
12914
+ await fsp4.access(path10.join(runDir, "checkpoint.json"));
12265
12915
  checkpoint = true;
12266
12916
  } catch {
12267
12917
  }
12268
12918
  try {
12269
- subagentsDir = path9.join(runDir, "subagents");
12919
+ subagentsDir = path10.join(runDir, "subagents");
12270
12920
  const files = await fsp4.readdir(subagentsDir);
12271
12921
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
12272
12922
  } catch {
@@ -12295,7 +12945,7 @@ async function listFleetRuns(deps) {
12295
12945
  return 0;
12296
12946
  }
12297
12947
  async function showFleetRun(runId, deps) {
12298
- const runDir = path9.join(deps.paths.projectSessions, runId);
12948
+ const runDir = path10.join(deps.paths.projectSessions, runId);
12299
12949
  let stat4;
12300
12950
  try {
12301
12951
  stat4 = await fsp4.stat(runDir);
@@ -12312,7 +12962,7 @@ async function showFleetRun(runId, deps) {
12312
12962
  deps.renderer.write(color.bold(`
12313
12963
  Fleet Run: ${runId}
12314
12964
  `) + "\n");
12315
- const manifestPath = path9.join(runDir, "fleet.json");
12965
+ const manifestPath = path10.join(runDir, "fleet.json");
12316
12966
  let manifestData = null;
12317
12967
  try {
12318
12968
  manifestData = await fsp4.readFile(manifestPath, "utf8");
@@ -12328,7 +12978,7 @@ Fleet Run: ${runId}
12328
12978
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
12329
12979
  `);
12330
12980
  }
12331
- const checkpointPath = path9.join(runDir, "checkpoint.json");
12981
+ const checkpointPath = path10.join(runDir, "checkpoint.json");
12332
12982
  let checkpointData = null;
12333
12983
  try {
12334
12984
  checkpointData = await fsp4.readFile(checkpointPath, "utf8");
@@ -12375,7 +13025,7 @@ Fleet Run: ${runId}
12375
13025
  } catch {
12376
13026
  }
12377
13027
  }
12378
- const subagentsDir = path9.join(runDir, "subagents");
13028
+ const subagentsDir = path10.join(runDir, "subagents");
12379
13029
  let subagentFiles = [];
12380
13030
  try {
12381
13031
  subagentFiles = await fsp4.readdir(subagentsDir);
@@ -12387,7 +13037,7 @@ Fleet Run: ${runId}
12387
13037
  Subagent transcripts (${subagentFiles.length}):
12388
13038
  `);
12389
13039
  for (const f of subagentFiles.sort()) {
12390
- const filePath = path9.join(subagentsDir, f);
13040
+ const filePath = path10.join(subagentsDir, f);
12391
13041
  let size;
12392
13042
  try {
12393
13043
  const s = await fsp4.stat(filePath);
@@ -12404,7 +13054,7 @@ Fleet Run: ${runId}
12404
13054
  ${color.dim("\u25CB")} No subagent transcripts
12405
13055
  `);
12406
13056
  }
12407
- const sharedDir = path9.join(runDir, "shared");
13057
+ const sharedDir = path10.join(runDir, "shared");
12408
13058
  try {
12409
13059
  const files = await fsp4.readdir(sharedDir);
12410
13060
  deps.renderer.write(`
@@ -12571,7 +13221,7 @@ function findSessionId(args) {
12571
13221
  var rewindCmd = async (args, deps) => {
12572
13222
  const flags = parseRewindFlags(args);
12573
13223
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
12574
- const sessionsDir = path9.join(wpaths.globalRoot, "sessions");
13224
+ const sessionsDir = path10.join(wpaths.globalRoot, "sessions");
12575
13225
  const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
12576
13226
  let sessionId = findSessionId(args);
12577
13227
  if (!sessionId) {
@@ -12811,10 +13461,10 @@ var auditCmd = async (args, deps) => {
12811
13461
  return verify.ok ? 0 : 1;
12812
13462
  };
12813
13463
  async function listAudits(log, dir, deps) {
12814
- const fs29 = await import('fs/promises');
13464
+ const fs30 = await import('fs/promises');
12815
13465
  let entries;
12816
13466
  try {
12817
- entries = await fs29.readdir(dir);
13467
+ entries = await fs30.readdir(dir);
12818
13468
  } catch {
12819
13469
  deps.renderer.write(
12820
13470
  color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
@@ -13083,7 +13733,7 @@ Output ONLY a ranked list, one per line:
13083
13733
  const text = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
13084
13734
  const rankings = [];
13085
13735
  for (const line of text.split("\n")) {
13086
- const m = line.match(/^\s*(\d+)[\.\)]\s*Response\s+([A-Z])/i);
13736
+ const m = line.match(/^\s*(\d+)[.)]\s*Response\s+([A-Z])/i);
13087
13737
  if (m) {
13088
13738
  const label = m[2].toUpperCase();
13089
13739
  const idx = labelToIdx.get(label);
@@ -13714,22 +14364,22 @@ function fmtDuration(ms) {
13714
14364
  const remMin = m - h * 60;
13715
14365
  return `${h}h${remMin}m`;
13716
14366
  }
13717
- function fmtTaskResultLine(r, color58) {
14367
+ function fmtTaskResultLine(r, color62) {
13718
14368
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
13719
14369
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
13720
14370
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
13721
14371
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
13722
- const errKindChip = errKind ? color58.dim(` [${errKind}]`) : "";
13723
- const errSnip = errMsg || errKind ? `${errKindChip}${color58.dim(errTail)}` : "";
14372
+ const errKindChip = errKind ? color62.dim(` [${errKind}]`) : "";
14373
+ const errSnip = errMsg || errKind ? `${errKindChip}${color62.dim(errTail)}` : "";
13724
14374
  switch (r.status) {
13725
14375
  case "success":
13726
- return { mark: color58.green("\u2713"), stats, tail: "" };
14376
+ return { mark: color62.green("\u2713"), stats, tail: "" };
13727
14377
  case "timeout":
13728
- return { mark: color58.yellow("\u23F1"), stats: `${color58.yellow("timeout")} ${stats}`, tail: errSnip };
14378
+ return { mark: color62.yellow("\u23F1"), stats: `${color62.yellow("timeout")} ${stats}`, tail: errSnip };
13729
14379
  case "stopped":
13730
- return { mark: color58.dim("\u2298"), stats: `${color58.dim("stopped")} ${stats}`, tail: errSnip };
14380
+ return { mark: color62.dim("\u2298"), stats: `${color62.dim("stopped")} ${stats}`, tail: errSnip };
13731
14381
  case "failed":
13732
- return { mark: color58.red("\u2717"), stats: `${color58.red("failed")} ${stats}`, tail: errSnip };
14382
+ return { mark: color62.red("\u2717"), stats: `${color62.red("failed")} ${stats}`, tail: errSnip };
13733
14383
  }
13734
14384
  }
13735
14385
 
@@ -13747,7 +14397,7 @@ function resolveBundledSkillsDir() {
13747
14397
  try {
13748
14398
  const req2 = createRequire(import.meta.url);
13749
14399
  const corePkg = req2.resolve("@wrongstack/core/package.json");
13750
- return path9.join(path9.dirname(corePkg), "skills");
14400
+ return path10.join(path10.dirname(corePkg), "skills");
13751
14401
  } catch {
13752
14402
  return void 0;
13753
14403
  }
@@ -13997,7 +14647,7 @@ async function boot(argv) {
13997
14647
  } catch {
13998
14648
  }
13999
14649
  printLaunchHints(renderer, flags, {
14000
- cursorFile: path9.join(wpaths.cacheDir, "hint-cursor")
14650
+ cursorFile: path10.join(wpaths.cacheDir, "hint-cursor")
14001
14651
  });
14002
14652
  }
14003
14653
  return {
@@ -14018,7 +14668,7 @@ async function boot(argv) {
14018
14668
  }
14019
14669
  async function checkGitInCwd(opts) {
14020
14670
  const { cwd, renderer, reader } = opts;
14021
- const cwdGit = path9.join(cwd, ".git");
14671
+ const cwdGit = path10.join(cwd, ".git");
14022
14672
  let hasCwdGit = false;
14023
14673
  try {
14024
14674
  await fsp4.access(cwdGit);
@@ -14059,10 +14709,10 @@ async function checkGitInCwd(opts) {
14059
14709
  }
14060
14710
  }
14061
14711
  }
14062
- const parentDir = path9.dirname(cwd);
14712
+ const parentDir = path10.dirname(cwd);
14063
14713
  if (parentDir !== cwd) {
14064
14714
  try {
14065
- await fsp4.access(path9.join(parentDir, ".git"));
14715
+ await fsp4.access(path10.join(parentDir, ".git"));
14066
14716
  renderer.write(
14067
14717
  ` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
14068
14718
  `
@@ -14379,11 +15029,29 @@ async function predictNextTasks(input, opts) {
14379
15029
  }
14380
15030
  }
14381
15031
  init_sdd();
15032
+ function parseSuggestionsFromOutput(finalText) {
15033
+ const patterns = [
15034
+ /💡\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
15035
+ /##?\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
15036
+ /Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i
15037
+ ];
15038
+ for (const pat of patterns) {
15039
+ const m = pat.exec(finalText);
15040
+ if (m && m[1]) {
15041
+ const block = m[1].trim();
15042
+ const lines = block.split("\n").filter(Boolean);
15043
+ const suggestions = lines.map((l) => l.replace(/^\d+\.\s*/, "").trim()).filter((s) => s.length > 3);
15044
+ if (suggestions.length > 0) return suggestions.slice(0, 5);
15045
+ }
15046
+ }
15047
+ return null;
15048
+ }
14382
15049
  async function runRepl(opts) {
14383
15050
  if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
14384
15051
  await renderGoalBanner(opts);
14385
15052
  let activeCtrl;
14386
15053
  let interrupts = 0;
15054
+ let autoIterCount = 0;
14387
15055
  let exiting = false;
14388
15056
  const onSigint = () => {
14389
15057
  interrupts++;
@@ -14538,6 +15206,96 @@ async function runRepl(opts) {
14538
15206
  continue;
14539
15207
  }
14540
15208
  }
15209
+ {
15210
+ const mode = opts.getAutonomy?.() ?? "off";
15211
+ const suggestions = opts.getSuggestions?.() ?? [];
15212
+ if (mode === "suggest" && suggestions.length > 0) {
15213
+ const lines = suggestions.map(
15214
+ (s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
15215
+ );
15216
+ opts.renderer.write(
15217
+ `
15218
+ ${color.cyan(" \u{1F4A1} Suggested next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}
15219
+ ${lines.join("\n")}
15220
+
15221
+ `
15222
+ );
15223
+ }
15224
+ if (mode === "auto" && suggestions.length > 0) {
15225
+ const maxIter = opts.autoProceedMaxIterations ?? 50;
15226
+ if (maxIter > 0 && autoIterCount >= maxIter) {
15227
+ console.log(JSON.stringify({
15228
+ level: "info",
15229
+ event: "auto_proceed_limit_reached",
15230
+ iterations: autoIterCount,
15231
+ maxIterations: maxIter
15232
+ }));
15233
+ opts.renderer.write(
15234
+ `
15235
+ ${color.amber(" \u26A0 Auto-proceed limit reached")} \u2014 ${color.dim(`${autoIterCount} iterations. Waiting for input.`)}
15236
+
15237
+ `
15238
+ );
15239
+ autoIterCount = 0;
15240
+ } else {
15241
+ const top = suggestions[0] ?? "";
15242
+ const delay = opts.autoProceedDelayMs ?? 45e3;
15243
+ if (opts.onValidateAutoProceed) {
15244
+ try {
15245
+ const lastOutput = (
15246
+ /* last known agent output — use the stored suggestions
15247
+ and any recent context the host can provide */
15248
+ ""
15249
+ );
15250
+ const ok = await opts.onValidateAutoProceed(top, lastOutput);
15251
+ if (!ok) {
15252
+ opts.renderer.write(
15253
+ `
15254
+ ${color.amber(" \u26A0 Auto-proceed held")} \u2014 ${color.dim("suggestions need review. Waiting for input.")}
15255
+
15256
+ `
15257
+ );
15258
+ const lines = suggestions.map(
15259
+ (s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
15260
+ );
15261
+ opts.renderer.write(
15262
+ `${color.dim(" Next steps:")}
15263
+ ${lines.join("\n")}
15264
+
15265
+ `
15266
+ );
15267
+ autoIterCount = 0;
15268
+ } else {
15269
+ const ctrl = new AbortController();
15270
+ activeCtrl = ctrl;
15271
+ try {
15272
+ autoIterCount++;
15273
+ await runAutoProceed(opts, top, delay, ctrl);
15274
+ } finally {
15275
+ activeCtrl = void 0;
15276
+ }
15277
+ continue;
15278
+ }
15279
+ } catch {
15280
+ opts.renderer.write(
15281
+ color.dim(" Auto-proceed validation unavailable \u2014 waiting for input.\n")
15282
+ );
15283
+ autoIterCount = 0;
15284
+ }
15285
+ } else {
15286
+ const ctrl = new AbortController();
15287
+ activeCtrl = ctrl;
15288
+ try {
15289
+ autoIterCount++;
15290
+ await runAutoProceed(opts, top, delay, ctrl);
15291
+ } finally {
15292
+ activeCtrl = void 0;
15293
+ }
15294
+ continue;
15295
+ }
15296
+ }
15297
+ }
15298
+ }
14541
15299
  let raw;
14542
15300
  try {
14543
15301
  raw = await readPossiblyMultiline(opts);
@@ -14631,6 +15389,10 @@ ${color.dim(taskList2)}
14631
15389
  }
14632
15390
  }
14633
15391
  }
15392
+ if (opts.onSuggestionsParsed) {
15393
+ const parsed = parseSuggestionsFromOutput(runResult.finalText);
15394
+ opts.onSuggestionsParsed(parsed);
15395
+ }
14634
15396
  }
14635
15397
  } catch (_runErr) {
14636
15398
  opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
@@ -14791,6 +15553,10 @@ ${color.dim(taskList2)}
14791
15553
  }
14792
15554
  }
14793
15555
  }
15556
+ if (result.status === "done" && result.finalText && opts.onSuggestionsParsed) {
15557
+ const parsed = parseSuggestionsFromOutput(result.finalText);
15558
+ opts.onSuggestionsParsed(parsed);
15559
+ }
14794
15560
  if (opts.tokenCounter && before) {
14795
15561
  const after = opts.tokenCounter.total();
14796
15562
  const costAfter = opts.tokenCounter.estimateCost().total;
@@ -14853,6 +15619,10 @@ ${color.cyan(" Suggested next steps:")}
14853
15619
  ${suggestResult.finalText}
14854
15620
  `
14855
15621
  );
15622
+ if (opts.onSuggestionsParsed) {
15623
+ const parsed = parseSuggestionsFromOutput(suggestResult.finalText);
15624
+ opts.onSuggestionsParsed(parsed);
15625
+ }
14856
15626
  }
14857
15627
  } catch {
14858
15628
  } finally {
@@ -14983,6 +15753,79 @@ async function renderGoalBanner(opts) {
14983
15753
  }
14984
15754
  opts.renderer.write("\n");
14985
15755
  }
15756
+ async function runAutoProceed(opts, suggestion, delayMs, ctrl) {
15757
+ const truncated = suggestion.length > 80 ? `${suggestion.slice(0, 77)}\u2026` : suggestion;
15758
+ console.log(JSON.stringify({
15759
+ level: "info",
15760
+ event: "auto_proceed_started",
15761
+ suggestion: truncated,
15762
+ delayMs
15763
+ }));
15764
+ try {
15765
+ await autoProceedCountdown(opts, delayMs, suggestion, ctrl.signal);
15766
+ const runBlocks = [{ type: "text", text: suggestion }];
15767
+ const runResult = await opts.agent.run(runBlocks, { signal: ctrl.signal });
15768
+ console.log(JSON.stringify({
15769
+ level: "info",
15770
+ event: "auto_proceed_completed",
15771
+ suggestion: truncated,
15772
+ status: runResult.status,
15773
+ iterations: runResult.iterations
15774
+ }));
15775
+ opts.onAgentIterationComplete?.(
15776
+ estimateRequestTokensCalibrated(
15777
+ opts.agent.ctx.messages,
15778
+ opts.agent.ctx.systemPrompt,
15779
+ opts.agent.ctx.tools ?? []
15780
+ ).total
15781
+ );
15782
+ if (runResult.status === "done" && runResult.finalText && opts.onSuggestionsParsed) {
15783
+ const parsed = parseSuggestionsFromOutput(runResult.finalText);
15784
+ opts.onSuggestionsParsed(parsed);
15785
+ }
15786
+ } finally {
15787
+ }
15788
+ }
15789
+ async function autoProceedCountdown(opts, delayMs, suggestion, signal) {
15790
+ const sec = Math.ceil(delayMs / 1e3);
15791
+ const truncated = suggestion.length > 100 ? `${suggestion.slice(0, 97)}\u2026` : suggestion;
15792
+ opts.renderer.write(
15793
+ `
15794
+ ${color.cyan("\u23F3 Auto-proceeding")} in ${sec}s\u2026 ${color.dim("(Ctrl+C to cancel)")}
15795
+ `
15796
+ );
15797
+ opts.renderer.write(`${color.dim(" \u25B8")} ${color.dim(truncated)}
15798
+ `);
15799
+ const start = Date.now();
15800
+ let lastSec = sec;
15801
+ return new Promise((resolve5, reject) => {
15802
+ const onAbort = () => {
15803
+ clearInterval(interval);
15804
+ reject(new DOMException("Aborted", "AbortError"));
15805
+ };
15806
+ signal.addEventListener("abort", onAbort, { once: true });
15807
+ const interval = setInterval(() => {
15808
+ if (signal.aborted) return;
15809
+ const elapsed = Date.now() - start;
15810
+ const remaining = Math.max(0, Math.ceil((delayMs - elapsed) / 1e3));
15811
+ if (remaining <= 0) {
15812
+ clearInterval(interval);
15813
+ signal.removeEventListener("abort", onAbort);
15814
+ opts.renderer.write(color.dim(` \u21B3 Proceeding with: ${truncated}
15815
+ `));
15816
+ resolve5();
15817
+ return;
15818
+ }
15819
+ if (remaining !== lastSec) {
15820
+ lastSec = remaining;
15821
+ opts.renderer.write(
15822
+ color.dim(` \u23F3 ${remaining}s remaining\u2026 (Ctrl+C to cancel)
15823
+ `)
15824
+ );
15825
+ }
15826
+ }, 500);
15827
+ });
15828
+ }
14986
15829
  async function readPossiblyMultiline(opts) {
14987
15830
  const firstPrompt = theme2.primary("\u203A ");
14988
15831
  const contPrompt = color.dim("\xB7 ");
@@ -15075,6 +15918,11 @@ async function execute(deps) {
15075
15918
  getAutonomy,
15076
15919
  onAutonomy,
15077
15920
  getNextPredict,
15921
+ onSuggestionsParsed,
15922
+ getSuggestions,
15923
+ autoProceedDelayMs,
15924
+ autoProceedMaxIterations,
15925
+ onValidateAutoProceed,
15078
15926
  getEternalEngine,
15079
15927
  getParallelEngine,
15080
15928
  subscribeEternalIteration,
@@ -15303,6 +16151,7 @@ async function execute(deps) {
15303
16151
  auditLevel: cfg.session?.auditLevel ?? "standard",
15304
16152
  indexOnStart: cfg.indexing?.onSessionStart !== false,
15305
16153
  maxIterations: cfg.tools?.maxIterations ?? 500,
16154
+ autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
15306
16155
  debugStream: cfg.debugStream ?? false,
15307
16156
  configScope: cfg.configScope ?? "global",
15308
16157
  enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4
@@ -15386,10 +16235,15 @@ async function execute(deps) {
15386
16235
  autonomy.enhanceDelayMs = s.enhanceDelayMs;
15387
16236
  decrypted.autonomy = autonomy;
15388
16237
  }
16238
+ if (s.autoProceedMaxIterations !== void 0) {
16239
+ const autonomy = decrypted.autonomy ?? {};
16240
+ autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
16241
+ decrypted.autonomy = autonomy;
16242
+ }
15389
16243
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
15390
16244
  const encrypted = encryptConfigSecrets$1(toWrite, vault);
15391
16245
  if (targetPath !== wpaths.globalConfig) {
15392
- await fsp4.mkdir(path9.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
16246
+ await fsp4.mkdir(path10.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
15393
16247
  }
15394
16248
  await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
15395
16249
  configStore.update({
@@ -15519,11 +16373,16 @@ async function execute(deps) {
15519
16373
  supportsVision,
15520
16374
  attachments,
15521
16375
  effectiveMaxContext,
15522
- projectName: path9.basename(projectRoot) || void 0,
16376
+ projectName: path10.basename(projectRoot) || void 0,
15523
16377
  projectRoot,
15524
16378
  getAutonomy,
15525
16379
  onAutonomy,
15526
16380
  getNextPredict,
16381
+ onSuggestionsParsed,
16382
+ getSuggestions,
16383
+ autoProceedDelayMs,
16384
+ autoProceedMaxIterations,
16385
+ onValidateAutoProceed,
15527
16386
  getEternalEngine,
15528
16387
  getParallelEngine,
15529
16388
  skillLoader,
@@ -15547,10 +16406,15 @@ async function execute(deps) {
15547
16406
  supportsVision,
15548
16407
  attachments,
15549
16408
  effectiveMaxContext,
15550
- projectName: path9.basename(projectRoot) || void 0,
16409
+ projectName: path10.basename(projectRoot) || void 0,
15551
16410
  getAutonomy,
15552
16411
  onAutonomy,
15553
16412
  getNextPredict,
16413
+ onSuggestionsParsed,
16414
+ getSuggestions,
16415
+ autoProceedDelayMs,
16416
+ onValidateAutoProceed,
16417
+ autoProceedMaxIterations,
15554
16418
  getEternalEngine,
15555
16419
  getParallelEngine,
15556
16420
  skillLoader,
@@ -15679,7 +16543,7 @@ var MultiAgentHost = class {
15679
16543
  doneCondition: { type: "all_tasks_done" },
15680
16544
  maxConcurrent: this.opts.maxConcurrent ?? 4
15681
16545
  };
15682
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path9.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
16546
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path10.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
15683
16547
  this.director = new Director({
15684
16548
  config: coordinatorConfig,
15685
16549
  manifestPath: this.opts.manifestPath,
@@ -16014,10 +16878,30 @@ var MultiAgentHost = class {
16014
16878
  model: opts?.model,
16015
16879
  tools: opts?.tools
16016
16880
  };
16017
- const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
16881
+ const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig, description);
16018
16882
  this.fleetManager?.addPendingTask(taskId, subagentId, description);
16019
16883
  return { subagentId, taskId };
16020
16884
  }
16885
+ /**
16886
+ * Spawn a fresh subagent, assign a task, and **await** its completion.
16887
+ *
16888
+ * Unlike `spawn()`, which returns immediately with spawn metadata, this
16889
+ * method blocks until the subagent finishes (success, failure, or timeout)
16890
+ * and returns the full `TaskResult`. Use this when the caller needs the
16891
+ * subagent's actual output — e.g. `/techstack` displaying the generated report
16892
+ * in chat, or `/spawn` showing the result inline.
16893
+ *
16894
+ * Optional `opts` lets the caller override the subagent's provider, model,
16895
+ * and tool slice per spawn.
16896
+ */
16897
+ async spawnAndWait(description, opts) {
16898
+ const { taskId } = await this.spawn(description, opts);
16899
+ if (!this.director) throw new Error("Director is not initialized");
16900
+ const results = await this.director.awaitTasks([taskId]);
16901
+ const result = results[0];
16902
+ if (!result) throw new Error(`Task ${taskId} completed but no result returned`);
16903
+ return result;
16904
+ }
16021
16905
  /**
16022
16906
  * Common spawn + assign logic shared by both director mode and raw
16023
16907
  * coordinator mode. Extracts the identical body from the two branches
@@ -16027,11 +16911,11 @@ var MultiAgentHost = class {
16027
16911
  * Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
16028
16912
  * and event emission — the helper only talks to the coordinator.
16029
16913
  */
16030
- async _spawnAndAssign(subagentConfig) {
16914
+ async _spawnAndAssign(subagentConfig, description = "") {
16031
16915
  const taskId = randomUUID();
16032
16916
  if (!this.director) throw new Error("Director is not initialized");
16033
16917
  const subagentId = await this.director.spawn(subagentConfig);
16034
- await this.director.assign({ id: taskId, description: "", subagentId });
16918
+ await this.director.assign({ id: taskId, description, subagentId });
16035
16919
  return { subagentId, taskId };
16036
16920
  }
16037
16921
  /**
@@ -16148,16 +17032,16 @@ var MultiAgentHost = class {
16148
17032
  if (this.director) return this.director;
16149
17033
  this.opts.directorMode = true;
16150
17034
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
16151
- this.opts.manifestPath = path9.join(this.opts.fleetRoot, "fleet.json");
17035
+ this.opts.manifestPath = path10.join(this.opts.fleetRoot, "fleet.json");
16152
17036
  }
16153
17037
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
16154
- this.opts.sharedScratchpadPath = path9.join(this.opts.fleetRoot, "shared");
17038
+ this.opts.sharedScratchpadPath = path10.join(this.opts.fleetRoot, "shared");
16155
17039
  }
16156
17040
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
16157
- this.opts.sessionsRoot = path9.join(this.opts.fleetRoot, "subagents");
17041
+ this.opts.sessionsRoot = path10.join(this.opts.fleetRoot, "subagents");
16158
17042
  }
16159
17043
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
16160
- this.opts.stateCheckpointPath = path9.join(this.opts.fleetRoot, "director-state.json");
17044
+ this.opts.stateCheckpointPath = path10.join(this.opts.fleetRoot, "director-state.json");
16161
17045
  }
16162
17046
  await this.ensureDirector();
16163
17047
  return this.director ?? null;
@@ -16329,11 +17213,11 @@ var SessionStats = class {
16329
17213
  if (e.name === "bash") this.bashCommands++;
16330
17214
  else if (e.name === "fetch") this.fetches++;
16331
17215
  if (!e.ok) return;
16332
- const path27 = typeof input?.path === "string" ? input.path : void 0;
16333
- if (e.name === "read" && path27) this.readPaths.add(path27);
16334
- else if (e.name === "edit" && path27) this.editedPaths.add(path27);
16335
- else if (e.name === "write" && path27) {
16336
- this.writtenPaths.add(path27);
17216
+ const path28 = typeof input?.path === "string" ? input.path : void 0;
17217
+ if (e.name === "read" && path28) this.readPaths.add(path28);
17218
+ else if (e.name === "edit" && path28) this.editedPaths.add(path28);
17219
+ else if (e.name === "write" && path28) {
17220
+ this.writtenPaths.add(path28);
16337
17221
  const content = typeof input?.content === "string" ? input.content : "";
16338
17222
  this.bytesWritten += Buffer.byteLength(content, "utf8");
16339
17223
  }
@@ -17160,7 +18044,7 @@ function setupMetrics(params) {
17160
18044
  const dumpMetrics = () => {
17161
18045
  if (!metricsSink) return;
17162
18046
  try {
17163
- const out = path9.join(wpaths.projectSessions, "metrics.json");
18047
+ const out = path10.join(wpaths.projectSessions, "metrics.json");
17164
18048
  const snap = metricsSink.snapshot();
17165
18049
  writeFileSync(out, JSON.stringify(snap, null, 2));
17166
18050
  } catch {
@@ -17235,7 +18119,7 @@ async function setupCodebaseIndexing(deps) {
17235
18119
  if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
17236
18120
  const fp = payload.toolUse.input?.file_path;
17237
18121
  if (typeof fp === "string" && fp.length > 0) {
17238
- const abs = path9.resolve(payload.ctx.cwd, fp);
18122
+ const abs = path10.resolve(payload.ctx.cwd, fp);
17239
18123
  if (isIndexableFile(abs)) {
17240
18124
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
17241
18125
  }
@@ -17250,11 +18134,11 @@ async function setupCodebaseIndexing(deps) {
17250
18134
  let watcher;
17251
18135
  if (idx.watchExternal) {
17252
18136
  try {
17253
- watcher = fs14.watch(projectRoot, { recursive: true }, (_event, filename) => {
18137
+ watcher = fs15.watch(projectRoot, { recursive: true }, (_event, filename) => {
17254
18138
  if (!filename) return;
17255
18139
  const rel = filename.toString();
17256
18140
  if (isIgnored(rel)) return;
17257
- const abs = path9.resolve(projectRoot, rel);
18141
+ const abs = path10.resolve(projectRoot, rel);
17258
18142
  if (!isIndexableFile(abs)) return;
17259
18143
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
17260
18144
  });
@@ -17543,9 +18427,9 @@ async function setupSession(params) {
17543
18427
  const sessionRef = { current: session };
17544
18428
  await recoveryLock.write(session?.id).catch(() => void 0);
17545
18429
  const attachments = new DefaultAttachmentStore({
17546
- spoolDir: path9.join(wpaths.projectSessions, session?.id, "attachments")
18430
+ spoolDir: path10.join(wpaths.projectSessions, session?.id, "attachments")
17547
18431
  });
17548
- const queueStore = new QueueStore({ dir: path9.join(wpaths.projectSessions, session?.id) });
18432
+ const queueStore = new QueueStore({ dir: path10.join(wpaths.projectSessions, session?.id) });
17549
18433
  const ctxSignal = new AbortController().signal;
17550
18434
  const context = new Context({
17551
18435
  systemPrompt,
@@ -17558,7 +18442,7 @@ async function setupSession(params) {
17558
18442
  model: config.model
17559
18443
  });
17560
18444
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
17561
- const todosCheckpointPath = path9.join(wpaths.projectSessions, `${session?.id}.todos.json`);
18445
+ const todosCheckpointPath = path10.join(wpaths.projectSessions, `${session?.id}.todos.json`);
17562
18446
  if (resumeId) {
17563
18447
  try {
17564
18448
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -17576,15 +18460,15 @@ async function setupSession(params) {
17576
18460
  todosCheckpointPath,
17577
18461
  session?.id
17578
18462
  );
17579
- const planPath = path9.join(wpaths.projectSessions, `${session?.id}.plan.json`);
18463
+ const planPath = path10.join(wpaths.projectSessions, `${session?.id}.plan.json`);
17580
18464
  context.state.setMeta("plan.path", planPath);
17581
- const taskPath = path9.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
18465
+ const taskPath = path10.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
17582
18466
  context.state.setMeta("task.path", taskPath);
17583
18467
  let dirState;
17584
18468
  if (resumeId) {
17585
18469
  try {
17586
- const fleetRoot = path9.join(wpaths.projectSessions, session?.id);
17587
- dirState = await loadDirectorState(path9.join(fleetRoot, "director-state.json"));
18470
+ const fleetRoot = path10.join(wpaths.projectSessions, session?.id);
18471
+ dirState = await loadDirectorState(path10.join(fleetRoot, "director-state.json"));
17588
18472
  if (dirState) {
17589
18473
  const tCounts = {};
17590
18474
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -17624,7 +18508,7 @@ function resolveBundledSkillsDir2() {
17624
18508
  try {
17625
18509
  const req2 = createRequire(import.meta.url);
17626
18510
  const corePkg = req2.resolve("@wrongstack/core/package.json");
17627
- return path9.join(path9.dirname(corePkg), "skills");
18511
+ return path10.join(path10.dirname(corePkg), "skills");
17628
18512
  } catch {
17629
18513
  return void 0;
17630
18514
  }
@@ -17735,9 +18619,8 @@ async function main(argv) {
17735
18619
  updateInfo
17736
18620
  } = ctx;
17737
18621
  updateInfo = await printUpdateNotice(updateInfo);
17738
- const { setDebugStreamEnabled, setDebugStreamCallback, defaultDebugStreamCallback } = await import('@wrongstack/providers');
18622
+ const { setDebugStreamEnabled } = await import('@wrongstack/providers');
17739
18623
  if (config.debugStream) setDebugStreamEnabled(true);
17740
- setDebugStreamCallback(defaultDebugStreamCallback);
17741
18624
  const pathResolver = new DefaultPathResolver(cwd);
17742
18625
  const events = new EventBus();
17743
18626
  events.setLogger(logger);
@@ -17824,7 +18707,7 @@ async function main(argv) {
17824
18707
  modeId,
17825
18708
  modePrompt,
17826
18709
  modelCapabilities,
17827
- planPath: () => sessionRef.current ? path9.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
18710
+ planPath: () => sessionRef.current ? path10.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
17828
18711
  contributors: [
17829
18712
  // Injects the ETERNAL AUTONOMY block when the user has activated
17830
18713
  // a long-running autonomy engine. Without this, the per-iteration
@@ -18194,6 +19077,7 @@ async function main(argv) {
18194
19077
  })();
18195
19078
  autonomyModeRef.current = autonomyMode;
18196
19079
  let nextPredictEnabled = config.nextPrediction === true;
19080
+ let currentSuggestions = [];
18197
19081
  let eternalEngine = null;
18198
19082
  let parallelEngine = null;
18199
19083
  const eternalListeners = /* @__PURE__ */ new Set();
@@ -18214,12 +19098,12 @@ async function main(argv) {
18214
19098
  }
18215
19099
  }
18216
19100
  };
18217
- const fleetRoot = directorMode ? path9.join(wpaths.projectSessions, session.id) : void 0;
18218
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path9.join(expectDefined(fleetRoot), "fleet.json") : void 0;
18219
- const sharedScratchpadPath = directorMode ? path9.join(expectDefined(fleetRoot), "shared") : void 0;
18220
- const subagentSessionsRoot = directorMode ? path9.join(expectDefined(fleetRoot), "subagents") : void 0;
18221
- const stateCheckpointPath = directorMode ? path9.join(expectDefined(fleetRoot), "director-state.json") : void 0;
18222
- const fleetRootForPromotion = path9.join(wpaths.projectSessions, session.id);
19101
+ const fleetRoot = directorMode ? path10.join(wpaths.projectSessions, session.id) : void 0;
19102
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path10.join(expectDefined(fleetRoot), "fleet.json") : void 0;
19103
+ const sharedScratchpadPath = directorMode ? path10.join(expectDefined(fleetRoot), "shared") : void 0;
19104
+ const subagentSessionsRoot = directorMode ? path10.join(expectDefined(fleetRoot), "subagents") : void 0;
19105
+ const stateCheckpointPath = directorMode ? path10.join(expectDefined(fleetRoot), "director-state.json") : void 0;
19106
+ const fleetRootForPromotion = path10.join(wpaths.projectSessions, session.id);
18223
19107
  const brainQueue = new BrainDecisionQueue(events);
18224
19108
  const brain = new ObservableBrainArbiter(
18225
19109
  new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
@@ -18392,6 +19276,25 @@ async function main(argv) {
18392
19276
  const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
18393
19277
  return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
18394
19278
  },
19279
+ onSpawnAndWait: async (description, spawnOpts) => {
19280
+ const result = await multiAgentHost.spawnAndWait(description, spawnOpts);
19281
+ const tags = [];
19282
+ if (spawnOpts?.provider) tags.push(spawnOpts.provider);
19283
+ if (spawnOpts?.model) tags.push(spawnOpts.model);
19284
+ if (spawnOpts?.name) tags.push(spawnOpts.name);
19285
+ const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
19286
+ const secs = (result.durationMs / 1e3).toFixed(result.durationMs < 1e4 ? 1 : 0);
19287
+ const icon = result.status === "success" ? "\u2713" : result.status === "timeout" ? "\u23F1" : result.status === "stopped" ? "\u2298" : "\u2717";
19288
+ const resultPreview = typeof result.result === "string" && result.result.trim() ? `
19289
+ ${color.dim("\u2500".repeat(40))}
19290
+ ${result.result.trim().slice(0, 600)}${result.result.trim().length > 600 ? "\n\u2026" : ""}
19291
+ ${color.dim("\u2500".repeat(40))}` : "";
19292
+ return [
19293
+ `${icon} ${color.bold(tag ? tag.slice(1) : "subagent")} ${result.status} (${result.iterations} iter \xB7 ${result.toolCalls} tools \xB7 ${secs}s)`,
19294
+ resultPreview,
19295
+ result.error ? ` ${color.amber(`error: ${result.error.message}`)}` : ""
19296
+ ].filter(Boolean).join("\n");
19297
+ },
18395
19298
  onAgents: (subagentId) => {
18396
19299
  const s = multiAgentHost.status();
18397
19300
  if (subagentId) {
@@ -18584,7 +19487,7 @@ async function main(argv) {
18584
19487
  return director.spawn(cfg);
18585
19488
  },
18586
19489
  onFleetLog: async (subagentId, mode) => {
18587
- const subagentsRoot = path9.join(fleetRootForPromotion, "subagents");
19490
+ const subagentsRoot = path10.join(fleetRootForPromotion, "subagents");
18588
19491
  let runDirs;
18589
19492
  try {
18590
19493
  runDirs = await fsp4.readdir(subagentsRoot);
@@ -18593,7 +19496,7 @@ async function main(argv) {
18593
19496
  }
18594
19497
  const found = [];
18595
19498
  for (const runId of runDirs) {
18596
- const runDir = path9.join(subagentsRoot, runId);
19499
+ const runDir = path10.join(subagentsRoot, runId);
18597
19500
  let files;
18598
19501
  try {
18599
19502
  files = await fsp4.readdir(runDir);
@@ -18602,7 +19505,7 @@ async function main(argv) {
18602
19505
  }
18603
19506
  for (const f of files) {
18604
19507
  if (!f.endsWith(".jsonl")) continue;
18605
- const full = path9.join(runDir, f);
19508
+ const full = path10.join(runDir, f);
18606
19509
  try {
18607
19510
  const stat4 = await fsp4.stat(full);
18608
19511
  found.push({
@@ -18703,7 +19606,7 @@ async function main(argv) {
18703
19606
  }
18704
19607
  const dir = await multiAgentHost.ensureDirector();
18705
19608
  if (!dir) return "Director is not available.";
18706
- const dirStatePath = path9.join(fleetRootForPromotion, "director-state.json");
19609
+ const dirStatePath = path10.join(fleetRootForPromotion, "director-state.json");
18707
19610
  const prior = await loadDirectorState(dirStatePath);
18708
19611
  if (!prior) {
18709
19612
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -18772,9 +19675,9 @@ async function main(argv) {
18772
19675
  for (const tool of director2.tools(FLEET_ROSTER)) {
18773
19676
  toolRegistry.register(tool);
18774
19677
  }
18775
- const mp = path9.join(fleetRootForPromotion, "fleet.json");
18776
- const sp = path9.join(fleetRootForPromotion, "shared");
18777
- const ss = path9.join(fleetRootForPromotion, "subagents");
19678
+ const mp = path10.join(fleetRootForPromotion, "fleet.json");
19679
+ const sp = path10.join(fleetRootForPromotion, "shared");
19680
+ const ss = path10.join(fleetRootForPromotion, "subagents");
18778
19681
  const lines = [
18779
19682
  `${color.green("\u2713")} Promoted to director mode.`,
18780
19683
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -18848,6 +19751,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
18848
19751
  }
18849
19752
  return nextPredictEnabled;
18850
19753
  },
19754
+ onSuggestions: (suggestions) => {
19755
+ if (suggestions !== void 0) {
19756
+ currentSuggestions = suggestions;
19757
+ }
19758
+ return currentSuggestions;
19759
+ },
18851
19760
  onAutonomy: (setTo) => {
18852
19761
  if (setTo !== void 0) {
18853
19762
  autonomyMode = setTo;
@@ -19094,6 +20003,54 @@ Restart WrongStack to load or unload plugin code in this session.`;
19094
20003
  return autonomyMode;
19095
20004
  },
19096
20005
  getNextPredict: () => nextPredictEnabled,
20006
+ onSuggestionsParsed: (suggestions) => {
20007
+ currentSuggestions = suggestions ?? [];
20008
+ },
20009
+ getSuggestions: () => currentSuggestions,
20010
+ autoProceedDelayMs: config.autonomy?.autoProceedDelayMs ?? 45e3,
20011
+ autoProceedMaxIterations: config.autonomy?.autoProceedMaxIterations ?? 50,
20012
+ onValidateAutoProceed: async (suggestion, lastOutput) => {
20013
+ try {
20014
+ const resp = await context.provider.complete(
20015
+ {
20016
+ model: context.model,
20017
+ system: [
20018
+ {
20019
+ type: "text",
20020
+ text: "You are a safety validator for an autonomous coding agent. Your ONLY job is to decide whether the agent should auto-proceed with a suggested next step, or whether a human should review first. Reply with exactly one word: YES or NO."
20021
+ }
20022
+ ],
20023
+ messages: [
20024
+ {
20025
+ role: "user",
20026
+ content: [
20027
+ {
20028
+ type: "text",
20029
+ text: `The autonomous agent just completed a turn and generated this top-ranked next-step suggestion:
20030
+
20031
+ "${suggestion}"
20032
+
20033
+ ${lastOutput ? `Recent agent output:
20034
+ ${lastOutput.slice(0, 500)}
20035
+
20036
+ ` : ""}Should the agent auto-proceed with this suggestion, or should a human review first?
20037
+
20038
+ Reply YES to auto-proceed, NO to wait for human input.`
20039
+ }
20040
+ ]
20041
+ }
20042
+ ],
20043
+ maxTokens: 5,
20044
+ temperature: 0
20045
+ },
20046
+ { signal: AbortSignal.timeout(1e4) }
20047
+ );
20048
+ const text = resp.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("").trim().toUpperCase();
20049
+ return text.startsWith("YES");
20050
+ } catch {
20051
+ return false;
20052
+ }
20053
+ },
19097
20054
  getEternalEngine: () => eternalEngine,
19098
20055
  getParallelEngine: () => parallelEngine,
19099
20056
  subscribeEternalIteration: (fn) => {