@wrongstack/cli 0.305.0 → 0.306.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.
@@ -44,6 +44,27 @@ import {
44
44
  PLUGIN_AUDIT_ENTRIES,
45
45
  runPluginManagementCommand
46
46
  } from "./chunk-RK7E3IXT.js";
47
+ import {
48
+ configureSimpleUiRuntimeContext,
49
+ detectProjectFacts,
50
+ makeConfirmAwaiter,
51
+ renderAgentsTemplate
52
+ } from "./chunk-YKBRFSGL.js";
53
+ import "./chunk-QD544J2B.js";
54
+ import {
55
+ fmtTaskResultLine,
56
+ fmtTok,
57
+ patchConfig
58
+ } from "./chunk-TYO2OVAD.js";
59
+ import {
60
+ CLI_VERSION
61
+ } from "./chunk-XJXDOF63.js";
62
+ import {
63
+ buildWin32CmdShimInvocation
64
+ } from "./chunk-U3SS4NRH.js";
65
+ import {
66
+ checkForUpdate
67
+ } from "./chunk-NUIHRGPY.js";
47
68
  import {
48
69
  createGracefulShutdown,
49
70
  loadCachedAcpRegistry,
@@ -59,30 +80,18 @@ import {
59
80
  runClaudeOAuthLogin,
60
81
  runCopilotOAuthLogin,
61
82
  validateFamily
62
- } from "./chunk-FJVCVZIV.js";
63
- import {
64
- runCodexOAuthLogin
65
- } from "./chunk-SSSJQVUY.js";
66
- import {
67
- configureSimpleUiRuntimeContext,
68
- detectProjectFacts,
69
- makeConfirmAwaiter,
70
- renderAgentsTemplate
71
- } from "./chunk-YKBRFSGL.js";
72
- import "./chunk-QD544J2B.js";
73
- import {
74
- fmtTaskResultLine,
75
- fmtTok,
76
- patchConfig
77
- } from "./chunk-TYO2OVAD.js";
83
+ } from "./chunk-WC3DK6BI.js";
78
84
  import {
79
85
  LOCAL_LLM_PRESETS,
80
86
  parseSpawnFlags
81
- } from "./chunk-C5NXM2SI.js";
87
+ } from "./chunk-5JW3XCRA.js";
82
88
  import {
83
89
  buildPickableProviders,
84
90
  visibleModelIds
85
91
  } from "./chunk-3IC4IEZC.js";
92
+ import {
93
+ runCodexOAuthLogin
94
+ } from "./chunk-SSSJQVUY.js";
86
95
  import {
87
96
  activeLabel,
88
97
  loadConfigProviders,
@@ -97,15 +106,6 @@ import {
97
106
  import {
98
107
  activeProfileConfigPath
99
108
  } from "./chunk-YMXXOOFN.js";
100
- import {
101
- buildWin32CmdShimInvocation
102
- } from "./chunk-U3SS4NRH.js";
103
- import {
104
- checkForUpdate
105
- } from "./chunk-NUIHRGPY.js";
106
- import {
107
- CLI_VERSION
108
- } from "./chunk-XJXDOF63.js";
109
109
  import {
110
110
  __require
111
111
  } from "./chunk-7OCVIDC7.js";
@@ -8033,6 +8033,12 @@ function registerProviderUtilityTools(input) {
8033
8033
  fallbackProfileManager: input.fallbackProfileManager,
8034
8034
  defaultProvider: config.provider,
8035
8035
  defaultModel: config.model,
8036
+ // Same live router the council gets below. Without it the `role` input
8037
+ // was inert (the orchestrator's pickForTask path no-ops), and without the
8038
+ // tracker no llm-tool call ever recorded provider health, so blocked
8039
+ // entries were neither skipped nor reported for this tool's traffic.
8040
+ modelRouter: createLiveModelRouter(input.getConfig),
8041
+ statusTracker: input.statusTracker,
8036
8042
  wrapProviderCall: input.wrapProviderCall
8037
8043
  });
8038
8044
  registerOrOverride(input.toolRegistry, "llm", llmTool);
@@ -8362,27 +8368,40 @@ async function setupLifecycleAndPlugins(deps) {
8362
8368
  }
8363
8369
 
8364
8370
  // src/wiring/management-tools.ts
8365
- import * as fs5 from "node:fs/promises";
8366
8371
  import {
8367
8372
  createFallbackManageTools,
8368
8373
  createPluginManagerTool
8369
8374
  } from "@wrongstack/core/tools";
8375
+ import { updateJsonObjectFile } from "@wrongstack/core/utils";
8370
8376
  function registerCliManagementTools({
8371
8377
  toolRegistry,
8372
8378
  configStore,
8373
8379
  profileConfigPath,
8374
8380
  stdinInteractive,
8375
- getHookRunner
8381
+ getHookRunner,
8382
+ getSwitchProviderAndModel
8376
8383
  }) {
8377
8384
  const fallbackManageTools = createFallbackManageTools({
8378
8385
  getConfig: () => configStore.get(),
8386
+ // updateJsonObjectFile is the same atomic read-mutate-write helper
8387
+ // mcp_control uses on this very file — the previous bare
8388
+ // readFile→JSON.parse→writeFile raced it and could drop concurrent
8389
+ // updates (and tore the file on a crash mid-write).
8379
8390
  updateConfig: async (mutate) => {
8380
- const raw = await fs5.readFile(profileConfigPath, "utf8").catch(() => "{}");
8381
- const parsed = JSON.parse(raw);
8382
- mutate(parsed);
8383
- await fs5.writeFile(profileConfigPath, JSON.stringify(parsed, null, 2), { mode: 384 });
8384
- configStore.update(parsed);
8391
+ const next = await updateJsonObjectFile(profileConfigPath, (cfg) => {
8392
+ mutate(cfg);
8393
+ });
8394
+ configStore.update(next);
8385
8395
  },
8396
+ ...getSwitchProviderAndModel ? {
8397
+ switchProviderAndModel: async (providerId, modelId) => {
8398
+ const switchFn = getSwitchProviderAndModel();
8399
+ if (!switchFn) {
8400
+ return "the live model switch is not ready yet (still booting) \u2014 try again shortly";
8401
+ }
8402
+ return switchFn(providerId, modelId);
8403
+ }
8404
+ } : {},
8386
8405
  // Interactive key entry for REPL mode reads a line from stdin without echo.
8387
8406
  ...stdinInteractive ? {
8388
8407
  requestInput: async (prompt) => {
@@ -8739,14 +8758,14 @@ function setupProviderRuntime(deps) {
8739
8758
  }
8740
8759
 
8741
8760
  // src/wiring/provider-status.ts
8742
- import * as fs6 from "node:fs/promises";
8761
+ import * as fs5 from "node:fs/promises";
8743
8762
  import { ProviderModelStatusTracker } from "@wrongstack/core/coordination";
8744
8763
  import { atomicWrite as atomicWrite2, withFileLock } from "@wrongstack/core/utils";
8745
8764
  async function setupProviderStatus(input) {
8746
8765
  const tracker = new ProviderModelStatusTracker({ events: input.events });
8747
8766
  const statusFile = input.paths.profileProviderStatus(input.paths.profileName);
8748
8767
  try {
8749
- const saved = JSON.parse(await fs6.readFile(statusFile, "utf8"));
8768
+ const saved = JSON.parse(await fs5.readFile(statusFile, "utf8"));
8750
8769
  const restored = tracker.restoreSnapshot(saved);
8751
8770
  if (restored > 0) input.logger.info(`Restored ${restored} provider waiting-room entries`);
8752
8771
  } catch (error) {
@@ -8758,7 +8777,7 @@ async function setupProviderStatus(input) {
8758
8777
  tracker.sweepExpired();
8759
8778
  if (syncRunning) return;
8760
8779
  syncRunning = true;
8761
- void fs6.readFile(statusFile, "utf8").then((raw) => tracker.restoreSnapshot(JSON.parse(raw))).catch((error) => warnUnlessMissing(input.logger, "sync", error)).finally(() => {
8780
+ void fs5.readFile(statusFile, "utf8").then((raw) => tracker.restoreSnapshot(JSON.parse(raw))).catch((error) => warnUnlessMissing(input.logger, "sync", error)).finally(() => {
8762
8781
  syncRunning = false;
8763
8782
  });
8764
8783
  }, 3e4);
@@ -8773,7 +8792,7 @@ async function setupProviderStatus(input) {
8773
8792
  await withFileLock(statusFile, async () => {
8774
8793
  let statuses = [];
8775
8794
  try {
8776
- const current = JSON.parse(await fs6.readFile(statusFile, "utf8"));
8795
+ const current = JSON.parse(await fs5.readFile(statusFile, "utf8"));
8777
8796
  if (Array.isArray(current.statuses)) statuses = current.statuses;
8778
8797
  } catch (error) {
8779
8798
  if (error.code !== "ENOENT") throw error;
@@ -9542,7 +9561,11 @@ import {
9542
9561
  sessionScopedPath as sessionScopedPath3,
9543
9562
  toErrorMessage as toErrorMessage5
9544
9563
  } from "@wrongstack/core/utils";
9545
- import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
9564
+ import {
9565
+ attachSessionKanbanMirror,
9566
+ hydrateSessionKanban,
9567
+ sessionKanbanDegradation
9568
+ } from "@wrongstack/tools/session-kanban";
9546
9569
  async function setupSession(params) {
9547
9570
  const {
9548
9571
  config,
@@ -9706,6 +9729,12 @@ async function setupSession(params) {
9706
9729
  const taskPath = sessionScopedPath3(wpaths.projectSessions, sessionId, ".tasks.json");
9707
9730
  context.state.setMeta("task.path", taskPath);
9708
9731
  await hydrateSessionKanban(context);
9732
+ const kanbanDown = sessionKanbanDegradation();
9733
+ if (kanbanDown) {
9734
+ renderer.writeInfo(
9735
+ `Kanban board sync unavailable \u2014 continuing without it (${kanbanDown}). Run \`wstack doctor --daemons\` for detail.`
9736
+ );
9737
+ }
9709
9738
  const detachSessionKanbanMirror = attachSessionKanbanMirror(context);
9710
9739
  const detachTodosCheckpoint = async () => {
9711
9740
  detachSessionKanbanMirror();
@@ -9804,7 +9833,7 @@ import {
9804
9833
  readJsonObjectFile,
9805
9834
  removeJsonPath,
9806
9835
  setJsonPath,
9807
- updateJsonObjectFile
9836
+ updateJsonObjectFile as updateJsonObjectFile2
9808
9837
  } from "@wrongstack/core/utils";
9809
9838
  function parseMcpArgs(args) {
9810
9839
  const trimmed = args.trim();
@@ -9902,7 +9931,7 @@ async function runAdd(name, enable, configured, configPath, mcpRegistry, all) {
9902
9931
  }
9903
9932
  const existing = configured[name];
9904
9933
  const nextCfg = existing ? { ...preset, ...existing, enabled: enable } : { ...preset, enabled: enable };
9905
- await updateJsonObjectFile(configPath, (full) => {
9934
+ await updateJsonObjectFile2(configPath, (full) => {
9906
9935
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
9907
9936
  setJsonPath(full, ["mcpServers", name], { ...current[name], ...nextCfg });
9908
9937
  });
@@ -9941,7 +9970,7 @@ async function runRemove(name, configured, configPath, mcpRegistry) {
9941
9970
  );
9942
9971
  }
9943
9972
  if (typeof mcpRegistry.forget === "function") mcpRegistry.forget(name);
9944
- await updateJsonObjectFile(configPath, (full) => {
9973
+ await updateJsonObjectFile2(configPath, (full) => {
9945
9974
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : configured;
9946
9975
  setJsonPath(full, ["mcpServers"], { ...current });
9947
9976
  removeJsonPath(full, ["mcpServers", name]);
@@ -9960,7 +9989,7 @@ async function runEnable(name, configured, configPath, mcpRegistry) {
9960
9989
  return `${color8.green("Enabled")} "${name}" and started.`;
9961
9990
  }
9962
9991
  }
9963
- await updateJsonObjectFile(configPath, (full) => {
9992
+ await updateJsonObjectFile2(configPath, (full) => {
9964
9993
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
9965
9994
  setJsonPath(full, ["mcpServers", name], { ...cfg, ...current[name], enabled: true });
9966
9995
  });
@@ -9988,7 +10017,7 @@ async function runDisable(name, configured, configPath, mcpRegistry) {
9988
10017
  })
9989
10018
  );
9990
10019
  }
9991
- await updateJsonObjectFile(configPath, (full) => {
10020
+ await updateJsonObjectFile2(configPath, (full) => {
9992
10021
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
9993
10022
  setJsonPath(full, ["mcpServers", name], { ...cfg, ...current[name], enabled: false });
9994
10023
  });
@@ -11332,7 +11361,7 @@ ${color13.dim("YOLO enabled; tool calls run without approval unless an explicit
11332
11361
 
11333
11362
  // src/slash-commands/brain.ts
11334
11363
  import { randomUUID as randomUUID5 } from "node:crypto";
11335
- import { readFile as readFile7 } from "node:fs/promises";
11364
+ import { readFile as readFile6 } from "node:fs/promises";
11336
11365
  import { parseModelRef } from "@wrongstack/core/agent";
11337
11366
  import { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
11338
11367
  import { color as color14 } from "@wrongstack/core/utils";
@@ -11778,7 +11807,7 @@ function buildBrainCommand(opts) {
11778
11807
  const n = Math.max(1, Math.min(100, Number.parseInt(rest[0] ?? "15", 10) || 15));
11779
11808
  let raw;
11780
11809
  try {
11781
- raw = await readFile7(ledgerPath, "utf8");
11810
+ raw = await readFile6(ledgerPath, "utf8");
11782
11811
  } catch {
11783
11812
  const msg3 = `No ledger entries yet (${ledgerPath}).`;
11784
11813
  opts.renderer.write(msg3);
@@ -12657,7 +12686,7 @@ function buildCompactCommand(opts) {
12657
12686
  }
12658
12687
 
12659
12688
  // src/slash-commands/context.ts
12660
- import * as fs7 from "node:fs/promises";
12689
+ import * as fs6 from "node:fs/promises";
12661
12690
  import {
12662
12691
  formatContextWindowModeList,
12663
12692
  getContextWindowMode,
@@ -12971,6 +13000,7 @@ var SOURCE_LABELS = {
12971
13000
  contributor: "contributor",
12972
13001
  ledger: "completed-work ledger",
12973
13002
  glossary: "project jargon dictionary",
13003
+ peers: "fleet peer awareness",
12974
13004
  nextsteps: "next-steps gate",
12975
13005
  other: "other (untagged)"
12976
13006
  };
@@ -13019,7 +13049,7 @@ async function persistContextConfig(opts, patch) {
13019
13049
  const configPath = activeProfileConfigPath(opts.paths, opts.configStore.get());
13020
13050
  let raw = "{}";
13021
13051
  try {
13022
- raw = await fs7.readFile(configPath, "utf8");
13052
+ raw = await fs6.readFile(configPath, "utf8");
13023
13053
  } catch (err) {
13024
13054
  if (err.code !== "ENOENT") {
13025
13055
  return `Could not read ${configPath}: ${err.message}`;
@@ -13424,7 +13454,7 @@ function listRoles() {
13424
13454
  }
13425
13455
 
13426
13456
  // src/slash-commands/design.ts
13427
- import * as fs8 from "node:fs/promises";
13457
+ import * as fs7 from "node:fs/promises";
13428
13458
  import * as path19 from "node:path";
13429
13459
  import {
13430
13460
  applyTokenOverrides,
@@ -13584,8 +13614,8 @@ ${menu}` };
13584
13614
  return { message: e.message };
13585
13615
  }
13586
13616
  try {
13587
- await fs8.mkdir(path19.dirname(abs), { recursive: true });
13588
- await fs8.writeFile(abs, result.content);
13617
+ await fs7.mkdir(path19.dirname(abs), { recursive: true });
13618
+ await fs7.writeFile(abs, result.content);
13589
13619
  } catch (e) {
13590
13620
  return { message: `Failed to write ${result.path}: ${e.message}` };
13591
13621
  }
@@ -13752,7 +13782,7 @@ function buildStatsCommand(opts) {
13752
13782
  }
13753
13783
 
13754
13784
  // src/slash-commands/doctor.ts
13755
- import * as fs9 from "node:fs/promises";
13785
+ import * as fs8 from "node:fs/promises";
13756
13786
  import * as path20 from "node:path";
13757
13787
  import { atomicWrite as atomicWrite4, color as color22, toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
13758
13788
 
@@ -14317,7 +14347,7 @@ function buildDoctorCommand(opts) {
14317
14347
  const base = path20.basename(file);
14318
14348
  const candidates = [`${base}.last`];
14319
14349
  try {
14320
- const siblings = await fs9.readdir(dir);
14350
+ const siblings = await fs8.readdir(dir);
14321
14351
  candidates.push(
14322
14352
  ...siblings.filter((f) => f.startsWith(`${base}.`) && f.endsWith(".bak")).sort().reverse()
14323
14353
  );
@@ -14325,7 +14355,7 @@ function buildDoctorCommand(opts) {
14325
14355
  }
14326
14356
  for (const name of candidates) {
14327
14357
  try {
14328
- const raw = await fs9.readFile(path20.join(dir, name), "utf8");
14358
+ const raw = await fs8.readFile(path20.join(dir, name), "utf8");
14329
14359
  JSON.parse(raw);
14330
14360
  return { name, raw };
14331
14361
  } catch {
@@ -14378,7 +14408,7 @@ function buildDoctorCommand(opts) {
14378
14408
  for (const target of targets) {
14379
14409
  let raw;
14380
14410
  try {
14381
- raw = await fs9.readFile(target.file, "utf8");
14411
+ raw = await fs8.readFile(target.file, "utf8");
14382
14412
  } catch {
14383
14413
  if (!target.isProject) {
14384
14414
  lines.push(
@@ -14693,7 +14723,7 @@ function buildFKeyAliasCommands(opts) {
14693
14723
  }
14694
14724
 
14695
14725
  // src/slash-commands/fallback.ts
14696
- import * as fs10 from "node:fs/promises";
14726
+ import * as fs9 from "node:fs/promises";
14697
14727
  import {
14698
14728
  normalizeModelRef,
14699
14729
  parseModelRef as parseModelRef2,
@@ -14737,7 +14767,7 @@ async function patchGlobalConfig(globalConfigPath, mutate) {
14737
14767
  let raw = "{}";
14738
14768
  let fileExists2 = true;
14739
14769
  try {
14740
- raw = await fs10.readFile(globalConfigPath, "utf8");
14770
+ raw = await fs9.readFile(globalConfigPath, "utf8");
14741
14771
  } catch (err) {
14742
14772
  if (err.code !== "ENOENT") throw err;
14743
14773
  fileExists2 = false;
@@ -16798,7 +16828,7 @@ ${color27.dim("(dry-run)")}`
16798
16828
  }
16799
16829
 
16800
16830
  // src/slash-commands/gitid.ts
16801
- import * as fs11 from "node:fs/promises";
16831
+ import * as fs10 from "node:fs/promises";
16802
16832
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2, noOpVault as noOpVault3 } from "@wrongstack/core/security";
16803
16833
  import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
16804
16834
  import {
@@ -16815,7 +16845,7 @@ async function patchGlobalConfig2(globalConfigPath, mutate) {
16815
16845
  let raw = "{}";
16816
16846
  let fileExists2 = true;
16817
16847
  try {
16818
- raw = await fs11.readFile(globalConfigPath, "utf8");
16848
+ raw = await fs10.readFile(globalConfigPath, "utf8");
16819
16849
  } catch (err) {
16820
16850
  if (err.code !== "ENOENT") throw err;
16821
16851
  fileExists2 = false;
@@ -17272,7 +17302,7 @@ function buildHelpCommand(opts) {
17272
17302
  }
17273
17303
 
17274
17304
  // src/slash-commands/init.ts
17275
- import * as fs12 from "node:fs/promises";
17305
+ import * as fs11 from "node:fs/promises";
17276
17306
  import * as path22 from "node:path";
17277
17307
  import { color as color30 } from "@wrongstack/core/utils";
17278
17308
  function buildInitCommand(opts) {
@@ -17287,19 +17317,19 @@ function buildInitCommand(opts) {
17287
17317
  const isFirstInit = !await fileExists(file);
17288
17318
  const detected = await detectProjectFacts(root);
17289
17319
  const body = renderAgentsTemplate(detected);
17290
- await fs12.mkdir(dir, { recursive: true });
17320
+ await fs11.mkdir(dir, { recursive: true });
17291
17321
  let backedUp = false;
17292
17322
  if (!isFirstInit) {
17293
17323
  try {
17294
- await fs12.copyFile(file, `${file}.bak`);
17324
+ await fs11.copyFile(file, `${file}.bak`);
17295
17325
  backedUp = true;
17296
17326
  } catch {
17297
17327
  }
17298
17328
  }
17299
- await fs12.writeFile(file, body, "utf8");
17329
+ await fs11.writeFile(file, body, "utf8");
17300
17330
  let nodePkg = false;
17301
17331
  try {
17302
- await fs12.access(path22.join(root, "package.json"));
17332
+ await fs11.access(path22.join(root, "package.json"));
17303
17333
  nodePkg = true;
17304
17334
  } catch {
17305
17335
  }
@@ -17337,7 +17367,7 @@ function buildInitCommand(opts) {
17337
17367
  }
17338
17368
  async function fileExists(filePath) {
17339
17369
  try {
17340
- await fs12.access(filePath);
17370
+ await fs11.access(filePath);
17341
17371
  return true;
17342
17372
  } catch {
17343
17373
  return false;
@@ -17456,6 +17486,7 @@ import {
17456
17486
  updateTask,
17457
17487
  updateTaskAssignment
17458
17488
  } from "@wrongstack/kanban";
17489
+ import { preflightManagedTransition } from "@wrongstack/kanban/manager/lifecycle";
17459
17490
  import { TaskGraphStore } from "@wrongstack/sdd";
17460
17491
 
17461
17492
  // src/slash-commands/kanban-agent-helpers.ts
@@ -18227,9 +18258,9 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18227
18258
  try {
18228
18259
  const result = await transitionTask(projectRoot, boardId, taskId, {
18229
18260
  to,
18230
- actor: "kanban-slash",
18231
- action: `Moved to ${resolvedColumnId} via /kanban task move`,
18232
- comment: moveNote ? `${moveNote} (move \u2192 ${resolvedColumnId})` : `/kanban task move ${resolvedColumnId} (was ${taskId} on board ${boardId})`,
18261
+ actor: "kanban-slash:move",
18262
+ action: moveNote ? `${moveNote} (move \u2192 ${resolvedColumnId})` : `Moved to ${resolvedColumnId} via /kanban task move`,
18263
+ comment: moveNote ? `${moveNote} (move \u2192 ${resolvedColumnId})` : `Moved to ${resolvedColumnId} via /kanban task move (board=${boardId}, task=${taskId})`,
18233
18264
  // Reviewers require a non-empty attachment URL to advance into review.
18234
18265
  // Forward the caller's `--attachment`; if missing, the move must fail at
18235
18266
  // `validateReviewEvidence` so the audit ledger never records a fabricated
@@ -18263,7 +18294,7 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18263
18294
  )
18264
18295
  };
18265
18296
  }
18266
- const { attachment, note, positional, warnings } = parseTaskEvidenceFlags(rest.slice(1));
18297
+ const { attachment, note, tickChecks, positional, warnings } = parseTaskEvidenceFlags(rest.slice(1));
18267
18298
  if (positional.length > 0) {
18268
18299
  return {
18269
18300
  message: color32.red(
@@ -18292,73 +18323,36 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18292
18323
  )
18293
18324
  };
18294
18325
  }
18295
- const preflightIssues = [];
18296
- const needsRunningEvidence = path32.includes("running");
18297
- const needsReviewEvidence = path32.includes("review");
18298
- const needsDoneEvidence = path32.includes("done");
18299
18326
  const currentTask = board.tasks.find((t) => t.id === taskId);
18300
18327
  if (!currentTask) {
18301
18328
  return { message: color32.red("Task not found") };
18302
18329
  }
18303
- if (needsRunningEvidence) {
18304
- const assignment = currentTask.assignment;
18305
- const hasLease = assignment?.status === "running" && [
18306
- assignment.leaseId,
18307
- assignment.claimedAt,
18308
- assignment.heartbeatAt,
18309
- assignment.leaseExpiresAt
18310
- ].every((value) => typeof value === "string" && value.trim().length > 0);
18311
- if (!hasLease) {
18312
- preflightIssues.push(
18313
- "The /kanban task done path needs the card to already be Running with lease metadata; move it into Running first via /kanban task assign or /kanban task dispatch."
18314
- );
18315
- }
18316
- }
18317
- if (needsReviewEvidence && !attachment) {
18318
- preflightIssues.push(
18319
- 'Review evidence requires --attachment <url>; re-run `/kanban task done <boardId> <taskId> --attachment https://example.test/review --note "..."`.'
18320
- );
18321
- } else if (needsReviewEvidence && !currentTask.assignment?.lastResult) {
18322
- preflightIssues.push(
18323
- "Review evidence requires the card to carry an assignment.lastResult; dispatch the worker through the agentic supervisor so it records implementation output."
18324
- );
18330
+ const preflightIssues = [];
18331
+ if (!note || !note.trim()) {
18332
+ return {
18333
+ message: color32.red(
18334
+ `Refusing /kanban task ${currentStage ?? "unknown"} \u2192 done without a reviewer note. Re-run with --note "<what proves the work is ready>".`
18335
+ )
18336
+ };
18325
18337
  }
18326
- if (needsDoneEvidence) {
18327
- if (!attachment) {
18328
- preflightIssues.push(
18329
- 'Done requires --attachment <url>; re-run with --attachment https://example.test/review --note "verified".'
18330
- );
18331
- }
18332
- const criteria = currentTask.successCriteria ?? [];
18333
- if (criteria.length === 0 || criteria.some((check) => check.status !== "passed")) {
18334
- preflightIssues.push(
18335
- "Done requires every acceptance criterion to be explicitly passed; update them on the card before re-running."
18336
- );
18337
- }
18338
- if (currentTask.atomic && currentTask.verificationReport?.verdict !== "passed") {
18339
- preflightIssues.push(
18340
- "Atomic tasks require a passed verification report; run `kanban.verify_completion` on the task before `/kanban task done`."
18341
- );
18342
- }
18343
- if (currentTask.atomic && currentTask.childTaskIds && currentTask.childTaskIds.length > 0) {
18344
- const childTasks = board.tasks.filter(
18345
- (entry) => currentTask.childTaskIds.includes(entry.id)
18346
- );
18347
- const missingChildren = currentTask.childTaskIds.filter(
18348
- (childId) => !childTasks.some((entry) => entry.id === childId)
18349
- );
18350
- if (missingChildren.length > 0) {
18351
- preflightIssues.push(
18352
- `Atomic parent references unresolved children (${missingChildren.join(", ")}); resolve them via the kanban tool before retrying.`
18353
- );
18354
- }
18355
- const incompleteChildren = childTasks.filter((child) => child.status !== "completed");
18356
- if (incompleteChildren.length > 0) {
18357
- const ids = incompleteChildren.map((child) => child.id).join(", ");
18358
- preflightIssues.push(
18359
- `Atomic parent's children must be completed before the parent can advance to Done (pending: ${ids}).`
18360
- );
18361
- }
18338
+ const noteText = note.trim();
18339
+ const sharedAttachment = attachment ? {
18340
+ url: attachment,
18341
+ type: "url",
18342
+ title: "Reviewer evidence (kanban-slash:done)"
18343
+ } : void 0;
18344
+ for (const to of path32) {
18345
+ const transitionInput = {
18346
+ to,
18347
+ actor: "kanban-slash:done",
18348
+ action: `${noteText} (${to})`,
18349
+ comment: `${noteText} (${to})`,
18350
+ ...sharedAttachment ? { attachment: sharedAttachment } : {},
18351
+ ...tickChecks ? { tickChecks } : {}
18352
+ };
18353
+ const issues = preflightManagedTransition(board, currentTask, transitionInput);
18354
+ for (const issue of issues) {
18355
+ preflightIssues.push(issue.message);
18362
18356
  }
18363
18357
  }
18364
18358
  if (preflightIssues.length > 0) {
@@ -18373,16 +18367,17 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18373
18367
  for (const to of path32) {
18374
18368
  await transitionTask(projectRoot, boardId, taskId, {
18375
18369
  to,
18376
- actor: "kanban-slash",
18377
- action: note ?? `Marked done via /kanban task done (${to})`,
18378
- comment: note ?? `Marked done via /kanban task done (${to})`,
18370
+ actor: "kanban-slash:done",
18371
+ action: noteText ? `${noteText} (${to})` : "",
18372
+ comment: noteText ? `${noteText} (${to})` : "",
18379
18373
  ...attachment ? {
18380
18374
  attachment: {
18381
18375
  url: attachment,
18382
18376
  type: "url",
18383
- title: "Reviewer evidence (kanban-slash)"
18377
+ title: "Reviewer evidence (kanban-slash:done)"
18384
18378
  }
18385
- } : {}
18379
+ } : {},
18380
+ ...tickChecks.length > 0 ? { tickChecks } : {}
18386
18381
  });
18387
18382
  }
18388
18383
  return { message: color32.green("\u2705 Task marked completed.") };
@@ -18805,14 +18800,17 @@ function resolveColumnReference(board, requested) {
18805
18800
  function parseTaskEvidenceFlags(tokens) {
18806
18801
  let attachment;
18807
18802
  let note;
18803
+ const tickChecks = [];
18808
18804
  const positional = [];
18809
18805
  const warnings = [];
18810
18806
  const ATTACHMENT_KEYS = /* @__PURE__ */ new Set(["--attachment", "--evidence", "--link"]);
18811
18807
  const NOTE_KEYS = /* @__PURE__ */ new Set(["--note", "--comment", "--action"]);
18808
+ const TICK_CHECK_KEYS = /* @__PURE__ */ new Set(["--tick-check", "--tick-checks"]);
18809
+ const VALID_TICK_STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped"]);
18812
18810
  for (let i = 0; i < tokens.length; i++) {
18813
18811
  const token = tokens[i];
18814
18812
  const eq = token.indexOf("=");
18815
- const inline = eq > 0 && (ATTACHMENT_KEYS.has(token.slice(0, eq)) || NOTE_KEYS.has(token.slice(0, eq)));
18813
+ const inline = eq > 0 && (ATTACHMENT_KEYS.has(token.slice(0, eq)) || NOTE_KEYS.has(token.slice(0, eq)) || TICK_CHECK_KEYS.has(token.slice(0, eq)));
18816
18814
  const key = inline ? token.slice(0, eq) : token;
18817
18815
  if (ATTACHMENT_KEYS.has(key)) {
18818
18816
  const value = inline ? token.slice(eq + 1) : tokens[i + 1];
@@ -18830,6 +18828,32 @@ function parseTaskEvidenceFlags(tokens) {
18830
18828
  attachment = value;
18831
18829
  continue;
18832
18830
  }
18831
+ if (TICK_CHECK_KEYS.has(key)) {
18832
+ const raw = inline ? token.slice(eq + 1) : tokens[i + 1];
18833
+ if (!raw?.trim()) {
18834
+ warnings.push(`${key} expects <checkId>=<status> but none was provided`);
18835
+ i = inline ? i : i + 1;
18836
+ continue;
18837
+ }
18838
+ if (!inline && raw.startsWith("-")) {
18839
+ warnings.push(`${key} expects <checkId>=<status> but none was provided`);
18840
+ i++;
18841
+ continue;
18842
+ }
18843
+ const sep2 = raw.lastIndexOf("=");
18844
+ const checkId = sep2 >= 0 ? raw.slice(0, sep2).trim() : "";
18845
+ const status = sep2 >= 0 ? raw.slice(sep2 + 1).trim() : "";
18846
+ if (!checkId || !status || !VALID_TICK_STATUSES.has(status)) {
18847
+ warnings.push(
18848
+ `${key} expects <checkId>=<status> where status is passed|failed|skipped (got "${raw}")`
18849
+ );
18850
+ i = inline ? i : i + 1;
18851
+ continue;
18852
+ }
18853
+ tickChecks.push({ checkId, checkStatus: status });
18854
+ i = inline ? i : i + 1;
18855
+ continue;
18856
+ }
18833
18857
  if (NOTE_KEYS.has(key)) {
18834
18858
  const inlineValue = inline ? token.slice(eq + 1) : void 0;
18835
18859
  if (inlineValue !== void 0) {
@@ -18857,7 +18881,7 @@ function parseTaskEvidenceFlags(tokens) {
18857
18881
  }
18858
18882
  positional.push(token);
18859
18883
  }
18860
- return { attachment, note, positional, warnings };
18884
+ return { attachment, note, tickChecks, positional, warnings };
18861
18885
  }
18862
18886
 
18863
18887
  // src/slash-commands/mailbox.ts
@@ -19385,7 +19409,7 @@ function buildMailboxDemoCommand(opts) {
19385
19409
 
19386
19410
  // src/slash-commands/mailbox-serve.ts
19387
19411
  import { spawn as spawn6 } from "node:child_process";
19388
- import * as fs13 from "node:fs/promises";
19412
+ import * as fs12 from "node:fs/promises";
19389
19413
  import * as os2 from "node:os";
19390
19414
  import * as path23 from "node:path";
19391
19415
  import { resolveProjectDir as resolveProjectDir5 } from "@wrongstack/core/coordination";
@@ -19460,7 +19484,7 @@ function buildMailboxServeCommand(opts) {
19460
19484
  });
19461
19485
  child.unref();
19462
19486
  try {
19463
- await fs13.writeFile(pidFile, String(child.pid ?? ""), { mode: 384 });
19487
+ await fs12.writeFile(pidFile, String(child.pid ?? ""), { mode: 384 });
19464
19488
  } catch {
19465
19489
  }
19466
19490
  const head = [];
@@ -21713,7 +21737,7 @@ ${targetMode.description}`
21713
21737
  }
21714
21738
 
21715
21739
  // src/slash-commands/modelcaps.ts
21716
- import * as fs14 from "node:fs/promises";
21740
+ import * as fs13 from "node:fs/promises";
21717
21741
  import { hasProviderCredential } from "@wrongstack/core/models";
21718
21742
  import { color as color36 } from "@wrongstack/core/utils";
21719
21743
  function fmtTokens(n) {
@@ -21786,7 +21810,7 @@ function buildModelCapsCommand(opts) {
21786
21810
  }
21787
21811
  let providers;
21788
21812
  try {
21789
- const raw = await fs14.readFile(cachePath, "utf8");
21813
+ const raw = await fs13.readFile(cachePath, "utf8");
21790
21814
  const parsed = JSON.parse(raw);
21791
21815
  const payload = parsed.payload ?? parsed;
21792
21816
  providers = Object.entries(payload).map(([id, p]) => ({
@@ -21863,7 +21887,7 @@ function buildModelCapsCommand(opts) {
21863
21887
  }
21864
21888
 
21865
21889
  // src/slash-commands/models.ts
21866
- import * as fs15 from "node:fs/promises";
21890
+ import * as fs14 from "node:fs/promises";
21867
21891
  import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets3, noOpVault as noOpVault4 } from "@wrongstack/core/security";
21868
21892
  import {
21869
21893
  ConfigError as ConfigError4,
@@ -21875,7 +21899,7 @@ async function patchProfileConfig(mutate, profileConfigPath) {
21875
21899
  let raw = "{}";
21876
21900
  let fileExists2 = true;
21877
21901
  try {
21878
- raw = await fs15.readFile(targetPath, "utf8");
21902
+ raw = await fs14.readFile(targetPath, "utf8");
21879
21903
  } catch (err) {
21880
21904
  if (err.code !== "ENOENT") throw err;
21881
21905
  fileExists2 = false;
@@ -24771,7 +24795,7 @@ async function killSession(sessionId, confirm) {
24771
24795
  }
24772
24796
 
24773
24797
  // src/slash-commands/setmodel.ts
24774
- import * as fs16 from "node:fs/promises";
24798
+ import * as fs15 from "node:fs/promises";
24775
24799
  import { fallbackProfileChain, parseModelRef as parseModelRef3 } from "@wrongstack/core/agent";
24776
24800
  import { AGENT_CATALOG as AGENT_CATALOG2, AGENTS_BY_PHASE as AGENTS_BY_PHASE3 } from "@wrongstack/core/agent-catalog";
24777
24801
  import {
@@ -24883,7 +24907,7 @@ async function patchProfileConfig2(mutate, profileConfigPath) {
24883
24907
  let raw = "{}";
24884
24908
  let fileExists2 = true;
24885
24909
  try {
24886
- raw = await fs16.readFile(targetPath, "utf8");
24910
+ raw = await fs15.readFile(targetPath, "utf8");
24887
24911
  } catch (err) {
24888
24912
  if (err.code !== "ENOENT") throw err;
24889
24913
  fileExists2 = false;
@@ -25507,7 +25531,7 @@ function parseSuggestions(raw) {
25507
25531
  if (/^none\b/i.test(trimmed) || /no (?:pending actions|further steps)/i.test(trimmed)) {
25508
25532
  return [];
25509
25533
  }
25510
- const { texts } = parseNextSteps(raw, false, false);
25534
+ const { texts } = parseNextSteps(raw, false);
25511
25535
  if (texts.length > 0) return texts;
25512
25536
  return raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 10 && !l.startsWith("#") && !l.startsWith("```")).slice(0, 5);
25513
25537
  }
@@ -25646,24 +25670,78 @@ function buildWebuiCommand() {
25646
25670
  }
25647
25671
 
25648
25672
  // src/slash-commands/theme.ts
25673
+ import { THEME_PRESET_IDS } from "@wrongstack/core/types";
25649
25674
  import { color as color46, writeOut as writeOut3 } from "@wrongstack/core/utils";
25650
- var THEME_OPTIONS = [
25651
- { id: "catppuccin", name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25652
- { id: "tokyo-night", name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25653
- { id: "nord", name: "Nord", desc: "Cool arctic blue & slate pastels" },
25654
- { id: "cyberpunk", name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25655
- { id: "dracula", name: "Dracula", desc: "Vibrant purple, pink & green" },
25656
- { id: "gruvbox-dark", name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25657
- { id: "solarized-dark", name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25658
- { id: "one-dark", name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25659
- { id: "monokai", name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25660
- { id: "rose-pine", name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25661
- { id: "kanagawa", name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25662
- { id: "ayu-dark", name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25663
- { id: "everforest", name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25664
- { id: "night-owl", name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25665
- { id: "synthwave", name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" }
25666
- ];
25675
+ var THEME_META = {
25676
+ catppuccin: { name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25677
+ "tokyo-night": { name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25678
+ nord: { name: "Nord", desc: "Cool arctic blue & slate pastels" },
25679
+ cyberpunk: { name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25680
+ dracula: { name: "Dracula", desc: "Vibrant purple, pink & green" },
25681
+ "gruvbox-dark": { name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25682
+ "solarized-dark": { name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25683
+ "one-dark": { name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25684
+ monokai: { name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25685
+ "rose-pine": { name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25686
+ kanagawa: { name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25687
+ "ayu-dark": { name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25688
+ everforest: { name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25689
+ "night-owl": { name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25690
+ synthwave: { name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" },
25691
+ "github-dark": { name: "GitHub Dark", desc: "GitHub's default dark \u2014 crisp blue on near-black" },
25692
+ "material-ocean": {
25693
+ name: "Material Ocean",
25694
+ desc: "Deepest Material variant \u2014 ink blue with pastel accents"
25695
+ },
25696
+ nightfox: { name: "Nightfox", desc: "Balanced slate blue with muted sage and rose" },
25697
+ oxocarbon: {
25698
+ name: "Oxocarbon",
25699
+ desc: "IBM Carbon-derived \u2014 neutral greys, electric blue & pink"
25700
+ },
25701
+ "catppuccin-macchiato": {
25702
+ name: "Catppuccin Macchiato",
25703
+ desc: "Warmer, one shade lighter than Mocha"
25704
+ },
25705
+ "catppuccin-frappe": {
25706
+ name: "Catppuccin Frapp\xE9",
25707
+ desc: "The lightest Catppuccin dark \u2014 gentle midday contrast"
25708
+ },
25709
+ "gruvbox-material": {
25710
+ name: "Gruvbox Material",
25711
+ desc: "Softened Gruvbox \u2014 same warmth, lower eye strain"
25712
+ },
25713
+ "tokyo-night-storm": {
25714
+ name: "Tokyo Night Storm",
25715
+ desc: "Tokyo Night on a lifted blue-grey base"
25716
+ },
25717
+ "rose-pine-moon": {
25718
+ name: "Ros\xE9 Pine Moon",
25719
+ desc: "Ros\xE9 Pine at dusk \u2014 deeper base, same soft accents"
25720
+ },
25721
+ zenburn: { name: "Zenburn", desc: "The classic low-contrast grey \u2014 desaturated and calm" },
25722
+ palenight: { name: "Palenight", desc: "Material Palenight \u2014 indigo base, candy accents" },
25723
+ horizon: { name: "Horizon", desc: "Warm coral and mint on charcoal \u2014 sunset gradient" },
25724
+ sonokai: { name: "Sonokai", desc: "Monokai Pro descendant \u2014 punchy on warm graphite" },
25725
+ "edge-dark": { name: "Edge Dark", desc: "Clean, evenly-weighted palette on desaturated navy" },
25726
+ moonfly: { name: "Moonfly", desc: "Near-black base with high-chroma accents \u2014 max contrast" },
25727
+ melange: { name: "Melange", desc: "Warm sepia and clay \u2014 the least blue dark theme here" },
25728
+ poimandres: { name: "Poimandres", desc: "Teal-forward, low-saturation \u2014 mint on deep indigo" },
25729
+ "vitesse-dark": {
25730
+ name: "Vitesse Dark",
25731
+ desc: "Anthony Fu's minimal palette \u2014 muted, print-like"
25732
+ },
25733
+ aura: { name: "Aura Dark", desc: "Vivid purple and spring green on near-black violet" },
25734
+ "dark-plus": { name: "VS Code Dark+", desc: "VS Code's default \u2014 familiar blue/orange/teal" }
25735
+ };
25736
+ var THEME_OPTIONS = THEME_PRESET_IDS.map((id) => ({ id, ...THEME_META[id] }));
25737
+ function presetHelpLines(perLine = 4) {
25738
+ const lines = [];
25739
+ for (let i = 0; i < THEME_PRESET_IDS.length; i += perLine) {
25740
+ const chunk = THEME_PRESET_IDS.slice(i, i + perLine).join(", ");
25741
+ lines.push(` ${chunk}${i + perLine < THEME_PRESET_IDS.length ? "," : ""}`);
25742
+ }
25743
+ return lines;
25744
+ }
25667
25745
  async function runThemePicker(reader, activeId) {
25668
25746
  let cursor = THEME_OPTIONS.findIndex((p) => p.id === activeId);
25669
25747
  if (cursor < 0) cursor = 0;
@@ -25678,7 +25756,7 @@ ${color46.bold(color46.amber("WrongStack") + color46.dim(" \u2014 TUI Theme Sele
25678
25756
  const mark = p.id === activeId ? color46.green(" [active]") : "";
25679
25757
  const prefix = i === currentCursor ? color46.bold("\u276F ") : " ";
25680
25758
  const name = i === currentCursor ? color46.bold(p.name) : p.name;
25681
- lines.push(` ${prefix}${name.padEnd(18)} ${color46.dim(p.desc)}${mark}`);
25759
+ lines.push(` ${prefix}${name.padEnd(21)} ${color46.dim(p.desc)}${mark}`);
25682
25760
  }
25683
25761
  lines.push("");
25684
25762
  return lines.join("\n");
@@ -25708,16 +25786,14 @@ function buildThemeCommand(opts) {
25708
25786
  name: "theme",
25709
25787
  category: "Config",
25710
25788
  description: "Switch or select the TUI color theme preset interactively",
25711
- argsHint: "[catppuccin | tokyo-night | nord | cyberpunk | dracula | gruvbox-dark | solarized-dark | one-dark | monokai | rose-pine | kanagawa | ayu-dark | everforest | night-owl | synthwave]",
25789
+ argsHint: `[<preset>] (${THEME_PRESET_IDS.length} available \u2014 run /theme to pick)`,
25712
25790
  help: [
25713
25791
  "Usage:",
25714
25792
  " /theme Interactive menu selection or view available theme presets",
25715
25793
  " /theme <preset> Switch directly to a theme preset",
25716
25794
  "",
25717
- "Available presets:",
25718
- " catppuccin, tokyo-night, nord, cyberpunk, dracula,",
25719
- " gruvbox-dark, solarized-dark, one-dark, monokai, rose-pine,",
25720
- " kanagawa, ayu-dark, everforest, night-owl, synthwave"
25795
+ `Available presets (${THEME_PRESET_IDS.length}):`,
25796
+ ...presetHelpLines()
25721
25797
  ].join("\n"),
25722
25798
  async run(args) {
25723
25799
  const validPresets = THEME_OPTIONS.map((t) => t.id);
@@ -25747,7 +25823,7 @@ function buildThemeCommand(opts) {
25747
25823
  ];
25748
25824
  for (const t of THEME_OPTIONS) {
25749
25825
  const mark = t.id === activePreset ? color46.green(" [active]") : "";
25750
- lines.push(` \u2022 ${color46.bold(t.id.padEnd(14))} ${t.desc}${mark}`);
25826
+ lines.push(` \u2022 ${color46.bold(t.id.padEnd(21))} ${t.desc}${mark}`);
25751
25827
  }
25752
25828
  lines.push("");
25753
25829
  lines.push(color46.dim("Usage: /theme <preset>"));
@@ -26244,7 +26320,7 @@ function buildMouseCommand(_opts) {
26244
26320
 
26245
26321
  // src/slash-commands/project.ts
26246
26322
  import { spawn as spawn7 } from "node:child_process";
26247
- import * as fs17 from "node:fs/promises";
26323
+ import * as fs16 from "node:fs/promises";
26248
26324
  import { createRequire } from "node:module";
26249
26325
  import * as path26 from "node:path";
26250
26326
  import {
@@ -26458,11 +26534,11 @@ async function listProjectsCommand(opts, ctx) {
26458
26534
  async function addProjectCommand(opts, ctx, targetPath, displayName) {
26459
26535
  const resolved = path26.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), targetPath);
26460
26536
  try {
26461
- await fs17.access(resolved);
26537
+ await fs16.access(resolved);
26462
26538
  } catch {
26463
26539
  return { message: color48.red(`Directory not found: ${resolved}`) };
26464
26540
  }
26465
- const stat5 = await fs17.stat(resolved);
26541
+ const stat5 = await fs16.stat(resolved);
26466
26542
  if (!stat5.isDirectory()) {
26467
26543
  return { message: color48.red(`Not a directory: ${resolved}`) };
26468
26544
  }
@@ -26528,11 +26604,11 @@ async function removeProjectCommand(opts, _ctx, slugOrName) {
26528
26604
  async function switchProjectCommand(opts, ctx, target, displayName) {
26529
26605
  const resolved = path26.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), target);
26530
26606
  try {
26531
- await fs17.access(resolved);
26607
+ await fs16.access(resolved);
26532
26608
  } catch {
26533
26609
  return { message: color48.red(`Directory not found: ${resolved}`) };
26534
26610
  }
26535
- const stat5 = await fs17.stat(resolved);
26611
+ const stat5 = await fs16.stat(resolved);
26536
26612
  if (!stat5.isDirectory()) {
26537
26613
  return { message: color48.red(`Not a directory: ${resolved}`) };
26538
26614
  }
@@ -26542,7 +26618,7 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
26542
26618
  const pkgPath = req.resolve("@wrongstack/cli/package.json");
26543
26619
  const pkgDir = path26.dirname(pkgPath);
26544
26620
  cliPath = path26.join(pkgDir, "dist", "index.js");
26545
- await fs17.access(cliPath);
26621
+ await fs16.access(cliPath);
26546
26622
  } catch {
26547
26623
  cliPath = process.argv[1] ?? "";
26548
26624
  if (!cliPath) {
@@ -26687,7 +26763,7 @@ async function spawnInProject(opts, _ctx, root, projectName) {
26687
26763
  const pkgPath = req.resolve("@wrongstack/cli/package.json");
26688
26764
  const pkgDir = path26.dirname(pkgPath);
26689
26765
  cliPath = path26.join(pkgDir, "dist", "index.js");
26690
- await fs17.access(cliPath);
26766
+ await fs16.access(cliPath);
26691
26767
  } catch {
26692
26768
  cliPath = process.argv[1] ?? "";
26693
26769
  if (!cliPath) {
@@ -26742,7 +26818,7 @@ async function handleNewSession(_opts, _ctx) {
26742
26818
  const pkgPath = req.resolve("@wrongstack/cli/package.json");
26743
26819
  const pkgDir = path26.dirname(pkgPath);
26744
26820
  cliPath = path26.join(pkgDir, "dist", "index.js");
26745
- await fs17.access(cliPath);
26821
+ await fs16.access(cliPath);
26746
26822
  } catch {
26747
26823
  cliPath = process.argv[1] ?? "";
26748
26824
  if (!cliPath) {
@@ -26905,7 +26981,9 @@ function buildReviewCommand(opts) {
26905
26981
  maxFiles: limit,
26906
26982
  autoFix: "off",
26907
26983
  cascadeOn: "off",
26908
- maxCascadeDepth: 0
26984
+ maxCascadeDepth: 0,
26985
+ fallbackModels: [],
26986
+ fallbackProfile: void 0
26909
26987
  },
26910
26988
  cwd,
26911
26989
  files: filesWithContent
@@ -28948,21 +29026,21 @@ ${formatTaskProgress(file.tasks)}`;
28948
29026
  }
28949
29027
 
28950
29028
  // src/slash-commands/techstack.ts
28951
- import * as fs18 from "node:fs/promises";
29029
+ import * as fs17 from "node:fs/promises";
28952
29030
  import * as path28 from "node:path";
28953
29031
  import { color as color52, toErrorMessage as toErrorMessage26 } from "@wrongstack/core/utils";
28954
29032
  async function discoverPackageFiles(projectRoot) {
28955
29033
  const files = [];
28956
29034
  const rootPkg = path28.join(projectRoot, "package.json");
28957
29035
  try {
28958
- await fs18.access(rootPkg);
29036
+ await fs17.access(rootPkg);
28959
29037
  files.push(rootPkg);
28960
29038
  } catch {
28961
29039
  }
28962
29040
  const workspaceFile = path28.join(projectRoot, "pnpm-workspace.yaml");
28963
29041
  try {
28964
- await fs18.access(workspaceFile);
28965
- const content = await fs18.readFile(workspaceFile, "utf8");
29042
+ await fs17.access(workspaceFile);
29043
+ const content = await fs17.readFile(workspaceFile, "utf8");
28966
29044
  const globMatch = /packages?:\s*\[([^\]]+)\]/s.exec(content);
28967
29045
  const rawGlobs = globMatch?.[1];
28968
29046
  if (!rawGlobs) return files;
@@ -28971,12 +29049,12 @@ async function discoverPackageFiles(projectRoot) {
28971
29049
  const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
28972
29050
  const dir = path28.join(projectRoot, dirPrefix);
28973
29051
  try {
28974
- const entries = await fs18.readdir(dir, { withFileTypes: true });
29052
+ const entries = await fs17.readdir(dir, { withFileTypes: true });
28975
29053
  for (const e of entries) {
28976
29054
  if (!e.isDirectory()) continue;
28977
29055
  const subPkg = path28.join(dir, e.name, "package.json");
28978
29056
  try {
28979
- await fs18.access(subPkg);
29057
+ await fs17.access(subPkg);
28980
29058
  files.push(subPkg);
28981
29059
  } catch {
28982
29060
  }
@@ -30288,7 +30366,7 @@ ${lines.join("\n")}${extra}
30288
30366
  }
30289
30367
 
30290
30368
  // src/slash-commands/tuneup.ts
30291
- import * as fs19 from "node:fs/promises";
30369
+ import * as fs18 from "node:fs/promises";
30292
30370
  import * as os3 from "node:os";
30293
30371
  import * as path29 from "node:path";
30294
30372
  import { atomicWrite as atomicWrite10, color as color57 } from "@wrongstack/core/utils";
@@ -30898,7 +30976,7 @@ async function gatherMemoryFiles(opts) {
30898
30976
  const out = [];
30899
30977
  for (const c of candidates) {
30900
30978
  try {
30901
- const content = await fs19.readFile(c.file, "utf8");
30979
+ const content = await fs18.readFile(c.file, "utf8");
30902
30980
  out.push({ label: c.label, path: c.file, content, committed: c.committed });
30903
30981
  } catch {
30904
30982
  }
@@ -30916,7 +30994,7 @@ async function gatherTrust(opts) {
30916
30994
  const file = opts.paths?.projectTrust;
30917
30995
  if (!file) return void 0;
30918
30996
  try {
30919
- const raw = await fs19.readFile(file, "utf8");
30997
+ const raw = await fs18.readFile(file, "utf8");
30920
30998
  const parsed = JSON.parse(raw);
30921
30999
  if (parsed && typeof parsed === "object") return parsed;
30922
31000
  } catch {
@@ -30927,7 +31005,7 @@ async function gatherConfigIssues(opts) {
30927
31005
  if (!opts.paths) return 0;
30928
31006
  const file = activeProfileConfigPath(opts.paths, opts.configStore.get());
30929
31007
  try {
30930
- const raw = await fs19.readFile(file, "utf8");
31008
+ const raw = await fs18.readFile(file, "utf8");
30931
31009
  const parsed = JSON.parse(raw);
30932
31010
  return diagnoseConfig(parsed).findings.length;
30933
31011
  } catch {
@@ -30945,14 +31023,14 @@ async function gatherSessionBytes(opts) {
30945
31023
  }
30946
31024
  async function dirSize(dir) {
30947
31025
  let total = 0;
30948
- const entries = await fs19.readdir(dir, { withFileTypes: true });
31026
+ const entries = await fs18.readdir(dir, { withFileTypes: true });
30949
31027
  for (const e of entries) {
30950
31028
  const full = path29.join(dir, e.name);
30951
31029
  if (e.isDirectory()) {
30952
31030
  total += await dirSize(full);
30953
31031
  } else if (e.isFile()) {
30954
31032
  try {
30955
- total += (await fs19.stat(full)).size;
31033
+ total += (await fs18.stat(full)).size;
30956
31034
  } catch {
30957
31035
  }
30958
31036
  }
@@ -30965,7 +31043,7 @@ async function applyActions(actions, opts) {
30965
31043
  const file = activeProfileConfigPath(opts.paths, opts.configStore.get());
30966
31044
  let raw = "{}";
30967
31045
  try {
30968
- raw = await fs19.readFile(file, "utf8");
31046
+ raw = await fs18.readFile(file, "utf8");
30969
31047
  } catch {
30970
31048
  }
30971
31049
  let parsed;
@@ -31121,7 +31199,7 @@ function summaryLine(findings, fixable, handoffs, power) {
31121
31199
  }
31122
31200
 
31123
31201
  // src/slash-commands/working-dir.ts
31124
- import * as fs20 from "node:fs/promises";
31202
+ import * as fs19 from "node:fs/promises";
31125
31203
  import * as path30 from "node:path";
31126
31204
  import { color as color58, toErrorMessage as toErrorMessage29 } from "@wrongstack/core/utils";
31127
31205
  function buildWorkingDirCommand(_opts) {
@@ -31168,7 +31246,7 @@ function buildWorkingDirCommand(_opts) {
31168
31246
  };
31169
31247
  }
31170
31248
  try {
31171
- const stat5 = await fs20.stat(resolved);
31249
+ const stat5 = await fs19.stat(resolved);
31172
31250
  if (!stat5.isDirectory()) {
31173
31251
  return { message: color58.red(`Not a directory: ${resolved}`) };
31174
31252
  }
@@ -31523,12 +31601,14 @@ async function runInteractive(cliCtx) {
31523
31601
  });
31524
31602
  const stdinInteractive = process.stdin.isTTY;
31525
31603
  const hookRunnerRef = { current: null };
31604
+ const switchProviderAndModelRef = { current: null };
31526
31605
  registerCliManagementTools({
31527
31606
  toolRegistry,
31528
31607
  configStore,
31529
31608
  profileConfigPath,
31530
31609
  stdinInteractive,
31531
- getHookRunner: () => hookRunnerRef.current
31610
+ getHookRunner: () => hookRunnerRef.current,
31611
+ getSwitchProviderAndModel: () => switchProviderAndModelRef.current
31532
31612
  });
31533
31613
  const { metricsSink, healthRegistry, metricsStatus } = (() => {
31534
31614
  const ms = setupMetrics({
@@ -31773,6 +31853,7 @@ async function runInteractive(cliCtx) {
31773
31853
  buildProviderForIdRuntime: buildProviderForId,
31774
31854
  statusTracker
31775
31855
  });
31856
+ switchProviderAndModelRef.current = switchProviderAndModel;
31776
31857
  await adoptResumedProvider({
31777
31858
  resumedProvider: sessResult.resumedProvider,
31778
31859
  resumedModel: sessResult.resumedModel,
@@ -32161,7 +32242,7 @@ async function runInteractive(cliCtx) {
32161
32242
  onEvent: evOn
32162
32243
  });
32163
32244
  const savedProviderCfg = config.providers?.[config.provider];
32164
- const { execute } = await import("./execution-YMBD7YWC.js");
32245
+ const { execute } = await import("./execution-TTE7R5UY.js");
32165
32246
  const stopHeapWatchdog = startSharedHeapWatchdog({
32166
32247
  collectStats: () => {
32167
32248
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -32399,4 +32480,4 @@ export {
32399
32480
  CLI_VERSION,
32400
32481
  runInteractive
32401
32482
  };
32402
- //# sourceMappingURL=cli-main-W3T56YCY.js.map
32483
+ //# sourceMappingURL=cli-main-DHNFGP3Z.js.map