reasonix 0.12.20 → 0.12.22

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/cli/index.js CHANGED
@@ -181,8 +181,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
181
181
  }
182
182
  function sleep(ms, signal) {
183
183
  if (ms <= 0) return Promise.resolve();
184
- return new Promise((resolve13, reject) => {
185
- const timer = setTimeout(resolve13, ms);
184
+ return new Promise((resolve14, reject) => {
185
+ const timer = setTimeout(resolve14, ms);
186
186
  if (signal) {
187
187
  const onAbort = () => {
188
188
  clearTimeout(timer);
@@ -668,7 +668,7 @@ function matchesTool(hook, toolName) {
668
668
  }
669
669
  var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
670
670
  function defaultSpawner(input) {
671
- return new Promise((resolve13) => {
671
+ return new Promise((resolve14) => {
672
672
  const child = spawn(input.command, {
673
673
  cwd: input.cwd,
674
674
  shell: true,
@@ -713,7 +713,7 @@ function defaultSpawner(input) {
713
713
  child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
714
714
  child.once("error", (err) => {
715
715
  clearTimeout(timer);
716
- resolve13({
716
+ resolve14({
717
717
  exitCode: null,
718
718
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
719
719
  stderr: Buffer.concat(stderrChunks).toString("utf8"),
@@ -724,7 +724,7 @@ function defaultSpawner(input) {
724
724
  });
725
725
  child.once("close", (code) => {
726
726
  clearTimeout(timer);
727
- resolve13({
727
+ resolve14({
728
728
  exitCode: code,
729
729
  stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
730
730
  stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
@@ -762,13 +762,13 @@ async function runHooks(opts) {
762
762
  const matching = opts.hooks.filter((h) => h.event === event && matchesTool(h, toolName));
763
763
  const outcomes = [];
764
764
  let blocked = false;
765
- const stdin4 = `${JSON.stringify(opts.payload)}
765
+ const stdin5 = `${JSON.stringify(opts.payload)}
766
766
  `;
767
767
  for (const hook of matching) {
768
768
  const start = Date.now();
769
769
  const timeoutMs = hook.timeout ?? DEFAULT_TIMEOUTS_MS[event];
770
770
  const cwd2 = hook.cwd ?? opts.payload.cwd;
771
- const raw = await spawner({ command: hook.command, cwd: cwd2, stdin: stdin4, timeoutMs });
771
+ const raw = await spawner({ command: hook.command, cwd: cwd2, stdin: stdin5, timeoutMs });
772
772
  const decision = decideOutcome(event, raw);
773
773
  outcomes.push({
774
774
  hook,
@@ -2646,8 +2646,8 @@ var CacheFirstLoop = class {
2646
2646
  }
2647
2647
  );
2648
2648
  for (let k = 0; k < budget2; k++) {
2649
- const sample = queue.shift() ?? await new Promise((resolve13) => {
2650
- waiter = resolve13;
2649
+ const sample = queue.shift() ?? await new Promise((resolve14) => {
2650
+ waiter = resolve14;
2651
2651
  });
2652
2652
  yield {
2653
2653
  turn: this._turn,
@@ -5444,7 +5444,7 @@ async function runCommand(cmd, opts) {
5444
5444
  };
5445
5445
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
5446
5446
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
5447
- return await new Promise((resolve13, reject) => {
5447
+ return await new Promise((resolve14, reject) => {
5448
5448
  let child;
5449
5449
  try {
5450
5450
  child = spawn3(bin, args, effectiveSpawnOpts);
@@ -5489,7 +5489,7 @@ async function runCommand(cmd, opts) {
5489
5489
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
5490
5490
 
5491
5491
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
5492
- resolve13({ exitCode: code, output, timedOut });
5492
+ resolve14({ exitCode: code, output, timedOut });
5493
5493
  });
5494
5494
  });
5495
5495
  }
@@ -6832,7 +6832,7 @@ var McpClient = class {
6832
6832
  const id = this.nextId++;
6833
6833
  const frame = { jsonrpc: "2.0", id, method, params };
6834
6834
  let abortHandler = null;
6835
- const promise = new Promise((resolve13, reject) => {
6835
+ const promise = new Promise((resolve14, reject) => {
6836
6836
  const timeout = setTimeout(() => {
6837
6837
  this.pending.delete(id);
6838
6838
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -6841,7 +6841,7 @@ var McpClient = class {
6841
6841
  );
6842
6842
  }, this.requestTimeoutMs);
6843
6843
  this.pending.set(id, {
6844
- resolve: resolve13,
6844
+ resolve: resolve14,
6845
6845
  reject,
6846
6846
  timeout
6847
6847
  });
@@ -6964,12 +6964,12 @@ var StdioTransport = class {
6964
6964
  }
6965
6965
  async send(message) {
6966
6966
  if (this.closed) throw new Error("MCP transport is closed");
6967
- return new Promise((resolve13, reject) => {
6967
+ return new Promise((resolve14, reject) => {
6968
6968
  const line = `${JSON.stringify(message)}
6969
6969
  `;
6970
6970
  this.child.stdin.write(line, "utf8", (err) => {
6971
6971
  if (err) reject(err);
6972
- else resolve13();
6972
+ else resolve14();
6973
6973
  });
6974
6974
  });
6975
6975
  }
@@ -6980,8 +6980,8 @@ var StdioTransport = class {
6980
6980
  continue;
6981
6981
  }
6982
6982
  if (this.closed) return;
6983
- const next = await new Promise((resolve13) => {
6984
- this.waiters.push(resolve13);
6983
+ const next = await new Promise((resolve14) => {
6984
+ this.waiters.push(resolve14);
6985
6985
  });
6986
6986
  if (next === null) return;
6987
6987
  yield next;
@@ -7047,8 +7047,8 @@ var SseTransport = class {
7047
7047
  constructor(opts) {
7048
7048
  this.url = opts.url;
7049
7049
  this.headers = opts.headers ?? {};
7050
- this.endpointReady = new Promise((resolve13, reject) => {
7051
- this.resolveEndpoint = resolve13;
7050
+ this.endpointReady = new Promise((resolve14, reject) => {
7051
+ this.resolveEndpoint = resolve14;
7052
7052
  this.rejectEndpoint = reject;
7053
7053
  });
7054
7054
  this.endpointReady.catch(() => void 0);
@@ -7075,8 +7075,8 @@ var SseTransport = class {
7075
7075
  continue;
7076
7076
  }
7077
7077
  if (this.closed) return;
7078
- const next = await new Promise((resolve13) => {
7079
- this.waiters.push(resolve13);
7078
+ const next = await new Promise((resolve14) => {
7079
+ this.waiters.push(resolve14);
7080
7080
  });
7081
7081
  if (next === null) return;
7082
7082
  yield next;
@@ -7263,8 +7263,8 @@ var StreamableHttpTransport = class {
7263
7263
  continue;
7264
7264
  }
7265
7265
  if (this.closed) return;
7266
- const next = await new Promise((resolve13) => {
7267
- this.waiters.push(resolve13);
7266
+ const next = await new Promise((resolve14) => {
7267
+ this.waiters.push(resolve14);
7268
7268
  });
7269
7269
  if (next === null) return;
7270
7270
  yield next;
@@ -9732,7 +9732,7 @@ async function startOllamaDaemon(opts = {}) {
9732
9732
  return { ready: false, pid };
9733
9733
  }
9734
9734
  async function pullOllamaModel(modelName, opts = {}) {
9735
- return new Promise((resolve13) => {
9735
+ return new Promise((resolve14) => {
9736
9736
  const child = spawn5("ollama", ["pull", modelName], {
9737
9737
  stdio: ["ignore", "pipe", "pipe"],
9738
9738
  windowsHide: true
@@ -9744,8 +9744,8 @@ async function pullOllamaModel(modelName, opts = {}) {
9744
9744
  }
9745
9745
  streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
9746
9746
  streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
9747
- child.once("exit", (code) => resolve13(code ?? -1));
9748
- child.once("error", () => resolve13(-1));
9747
+ child.once("exit", (code) => resolve14(code ?? -1));
9748
+ child.once("error", () => resolve14(-1));
9749
9749
  });
9750
9750
  }
9751
9751
  function streamLines(stream, cb) {
@@ -10590,7 +10590,7 @@ var MAX_BODY_BYTES = 256 * 1024;
10590
10590
  async function readBody(req) {
10591
10591
  let total = 0;
10592
10592
  const chunks = [];
10593
- return new Promise((resolve13, reject) => {
10593
+ return new Promise((resolve14, reject) => {
10594
10594
  req.on("data", (chunk) => {
10595
10595
  total += chunk.length;
10596
10596
  if (total > MAX_BODY_BYTES) {
@@ -10600,7 +10600,7 @@ async function readBody(req) {
10600
10600
  }
10601
10601
  chunks.push(chunk);
10602
10602
  });
10603
- req.on("end", () => resolve13(Buffer.concat(chunks).toString("utf8")));
10603
+ req.on("end", () => resolve14(Buffer.concat(chunks).toString("utf8")));
10604
10604
  req.on("error", reject);
10605
10605
  });
10606
10606
  }
@@ -10677,7 +10677,7 @@ function startDashboardServer(ctx, opts = {}) {
10677
10677
  const token = opts.token ?? mintToken();
10678
10678
  const host = opts.host ?? "127.0.0.1";
10679
10679
  const port = opts.port ?? 0;
10680
- return new Promise((resolve13, reject) => {
10680
+ return new Promise((resolve14, reject) => {
10681
10681
  const server = createServer((req, res) => {
10682
10682
  dispatch(req, res, ctx, token).catch((err) => {
10683
10683
  if (!res.headersSent) {
@@ -10698,7 +10698,7 @@ function startDashboardServer(ctx, opts = {}) {
10698
10698
  server.close(() => doneResolve());
10699
10699
  setTimeout(() => server.closeAllConnections?.(), 1e3).unref();
10700
10700
  });
10701
- resolve13({ url, token, port: finalPort, close });
10701
+ resolve14({ url, token, port: finalPort, close });
10702
10702
  });
10703
10703
  });
10704
10704
  }
@@ -10879,8 +10879,8 @@ function ModalCard({
10879
10879
  icon,
10880
10880
  children
10881
10881
  }) {
10882
- const { stdout: stdout3 } = useStdout();
10883
- const cols = stdout3?.columns ?? 80;
10882
+ const { stdout: stdout4 } = useStdout();
10883
+ const cols = stdout4?.columns ?? 80;
10884
10884
  const ruleWidth = Math.min(80, Math.max(28, cols - 4));
10885
10885
  const titleText = icon ? ` ${icon} ${title} ` : ` ${title} `;
10886
10886
  return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: accent }, "\u2594".repeat(ruleWidth))), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: accent, color: "black", bold: true }, titleText), subtitle ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` ${subtitle}`) : null), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, children), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: accent, dimColor: true }, "\u2581".repeat(ruleWidth))));
@@ -11314,8 +11314,8 @@ function SelectRow({
11314
11314
  active,
11315
11315
  marker
11316
11316
  }) {
11317
- const color = item.disabled ? "gray" : active ? "cyan" : void 0;
11318
- return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
11317
+ const color2 = item.disabled ? "gray" : active ? "cyan" : void 0;
11318
+ return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color: color2 }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
11319
11319
  }
11320
11320
  function findNextEnabled(items, from, step) {
11321
11321
  if (items.length === 0) return 0;
@@ -11443,8 +11443,8 @@ function capLines(lines, maxLines, indent) {
11443
11443
  var MODAL_OVERHEAD_ROWS = 18;
11444
11444
  var MIN_DIFF_ROWS = 8;
11445
11445
  function EditConfirm({ block, onChoose }) {
11446
- const { stdout: stdout3 } = useStdout2();
11447
- const rows = stdout3?.rows ?? 40;
11446
+ const { stdout: stdout4 } = useStdout2();
11447
+ const rows = stdout4?.rows ?? 40;
11448
11448
  const budget2 = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
11449
11449
  const allLines = useMemo(
11450
11450
  () => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
@@ -11530,13 +11530,13 @@ function EditConfirm({ block, onChoose }) {
11530
11530
  ) : null,
11531
11531
  /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, visibleLines.map((line, i) => {
11532
11532
  const trimmed = line.trimStart();
11533
- const color = trimmed.startsWith("+") ? "#4ade80" : trimmed.startsWith("-") ? "#f87171" : void 0;
11534
- const dim = !color;
11533
+ const color2 = trimmed.startsWith("+") ? "#4ade80" : trimmed.startsWith("-") ? "#f87171" : void 0;
11534
+ const dim = !color2;
11535
11535
  return /* @__PURE__ */ React6.createElement(
11536
11536
  Text4,
11537
11537
  {
11538
11538
  key: `diff-${effectiveScroll}-${i}`,
11539
- color,
11539
+ color: color2,
11540
11540
  dimColor: dim
11541
11541
  },
11542
11542
  line
@@ -11570,7 +11570,7 @@ function PlanStateBlock({ planState }) {
11570
11570
  if (planState.rejectedPaths.length)
11571
11571
  fields.push(["rejected", planState.rejectedPaths, "#94a3b8", true]);
11572
11572
  if (fields.length === 0) return null;
11573
- return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color, dim]) => /* @__PURE__ */ React7.createElement(Box6, { key: label }, /* @__PURE__ */ React7.createElement(Text5, { backgroundColor: color, color: "black", bold: true, dimColor: dim }, ` ${label} ${items.length} `), /* @__PURE__ */ React7.createElement(Text5, null, " "), /* @__PURE__ */ React7.createElement(Text5, { dimColor: dim }, items.join(" \xB7 ")))));
11573
+ return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color2, dim]) => /* @__PURE__ */ React7.createElement(Box6, { key: label }, /* @__PURE__ */ React7.createElement(Text5, { backgroundColor: color2, color: "black", bold: true, dimColor: dim }, ` ${label} ${items.length} `), /* @__PURE__ */ React7.createElement(Text5, null, " "), /* @__PURE__ */ React7.createElement(Text5, { dimColor: dim }, items.join(" \xB7 ")))));
11574
11574
  }
11575
11575
 
11576
11576
  // src/cli/ui/PlanStepList.tsx
@@ -12481,8 +12481,8 @@ function gradientCells(width, glyph = GLYPH.block) {
12481
12481
  const t2 = width === 1 ? 0 : i * last / (width - 1);
12482
12482
  const lo = Math.floor(t2);
12483
12483
  const hi = Math.min(last, lo + 1);
12484
- const color = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
12485
- cells.push({ ch: glyph, color });
12484
+ const color2 = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
12485
+ cells.push({ ch: glyph, color: color2 });
12486
12486
  }
12487
12487
  return cells;
12488
12488
  }
@@ -12659,9 +12659,9 @@ var ROLE_GLYPH = {
12659
12659
  };
12660
12660
  function RoleGlyph({
12661
12661
  glyph,
12662
- color
12662
+ color: color2
12663
12663
  }) {
12664
- return /* @__PURE__ */ React11.createElement(Text8, { color, bold: true }, glyph);
12664
+ return /* @__PURE__ */ React11.createElement(Text8, { color: color2, bold: true }, glyph);
12665
12665
  }
12666
12666
  function ToolPill({ label, status: status2 }) {
12667
12667
  const bg = status2 === "err" ? "red" : "yellow";
@@ -12794,8 +12794,8 @@ var EventRow = React11.memo(function EventRow2({
12794
12794
  return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, null, event.text));
12795
12795
  });
12796
12796
  function TurnSeparator() {
12797
- const { stdout: stdout3 } = useStdout3();
12798
- const cols = stdout3?.columns ?? 80;
12797
+ const { stdout: stdout4 } = useStdout3();
12798
+ const cols = stdout4?.columns ?? 80;
12799
12799
  const width = Math.max(16, cols - 2);
12800
12800
  const sideWidth = Math.max(2, Math.floor((width - 5) / 2));
12801
12801
  const leftCells = gradientCells(sideWidth, "\u2500");
@@ -12954,8 +12954,8 @@ function ModeStatusBar({
12954
12954
  return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label, bg, flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 Shift+Tab to flip`), jobsTag);
12955
12955
  }
12956
12956
  function ModeBarFrame({ children }) {
12957
- const { stdout: stdout3 } = useStdout4();
12958
- const cols = stdout3?.columns ?? 80;
12957
+ const { stdout: stdout4 } = useStdout4();
12958
+ const cols = stdout4?.columns ?? 80;
12959
12959
  const ruleWidth = Math.max(20, cols - 2);
12960
12960
  return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text9, { color: "#475569", dimColor: true }, "\u254C".repeat(ruleWidth))), /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, children));
12961
12961
  }
@@ -13756,8 +13756,8 @@ function PromptInput({
13756
13756
  if (action.historyHandoff === "prev") onHistoryPrev?.();
13757
13757
  if (action.historyHandoff === "next") onHistoryNext?.();
13758
13758
  }, !disabled);
13759
- const { stdout: stdout3 } = useStdout5();
13760
- const cols = stdout3?.columns ?? 80;
13759
+ const { stdout: stdout4 } = useStdout5();
13760
+ const cols = stdout4?.columns ?? 80;
13761
13761
  const narrow = cols <= 90;
13762
13762
  const promptBody = narrow ? "\u203A " : "you \u203A ";
13763
13763
  const promptPrefix = BAR + promptBody;
@@ -14130,8 +14130,8 @@ function StatsPanel({
14130
14130
  const branchOn = (branchBudget ?? 1) > 1;
14131
14131
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
14132
14132
  const ctxRatio = summary.lastPromptTokens / ctxMax;
14133
- const { stdout: stdout3 } = useStdout6();
14134
- const columns = stdout3?.columns ?? 80;
14133
+ const { stdout: stdout4 } = useStdout6();
14134
+ const columns = stdout4?.columns ?? 80;
14135
14135
  const narrow = columns < NARROW_BREAKPOINT;
14136
14136
  const coldStart = summary.turns <= COLD_START_TURNS;
14137
14137
  return /* @__PURE__ */ React21.createElement(Box19, { flexDirection: "column", paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
@@ -14175,8 +14175,8 @@ function StatsPanel({
14175
14175
  }
14176
14176
  function BudgetRow({ spent, cap }) {
14177
14177
  const pct2 = Math.max(0, spent / cap * 100);
14178
- const color = pct2 >= 100 ? "#f87171" : pct2 >= 80 ? "#fbbf24" : "#94a3b8";
14179
- return /* @__PURE__ */ React21.createElement(Box19, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " budget "), /* @__PURE__ */ React21.createElement(Text17, { color }, `$${spent.toFixed(4)} / $${cap.toFixed(2)}`, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, ` (${pct2.toFixed(0)}%)`)));
14178
+ const color2 = pct2 >= 100 ? "#f87171" : pct2 >= 80 ? "#fbbf24" : "#94a3b8";
14179
+ return /* @__PURE__ */ React21.createElement(Box19, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " budget "), /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, `$${spent.toFixed(4)} / $${cap.toFixed(2)}`, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, ` (${pct2.toFixed(0)}%)`)));
14180
14180
  }
14181
14181
  function Header({
14182
14182
  model: model2,
@@ -14246,9 +14246,9 @@ function ContextCell({
14246
14246
  if (promptTokens === 0) {
14247
14247
  return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2014 (no turns yet)"));
14248
14248
  }
14249
- const color = ratio >= 0.8 ? COLOR.err : ratio >= 0.6 ? COLOR.warn : COLOR.ok;
14249
+ const color2 = ratio >= 0.8 ? COLOR.err : ratio >= 0.6 ? COLOR.warn : COLOR.ok;
14250
14250
  const pct2 = Math.round(ratio * 100);
14251
- return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
14251
+ return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color: color2, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
14252
14252
  }
14253
14253
  function CacheCell({
14254
14254
  hitRatio,
@@ -14262,8 +14262,8 @@ function CacheCell({
14262
14262
  if (coldStart) {
14263
14263
  return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true, italic: true }, "(cold start)"));
14264
14264
  }
14265
- const color = hitRatio >= 0.7 ? COLOR.ok : hitRatio >= 0.4 ? COLOR.warn : COLOR.err;
14266
- return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, pct2, "%"));
14265
+ const color2 = hitRatio >= 0.7 ? COLOR.ok : hitRatio >= 0.4 ? COLOR.warn : COLOR.err;
14266
+ return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, pct2, "%"));
14267
14267
  }
14268
14268
  function turnCostColor(cost) {
14269
14269
  if (cost <= 0) return void 0;
@@ -14289,16 +14289,16 @@ function CostCell({
14289
14289
  return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25F4 turn "), /* @__PURE__ */ React21.createElement(Text17, { color: turnColor, bold: !coldStart, dimColor: coldStart }, "$", summary.lastTurnCostUsd.toFixed(4)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " \xB7 session "), /* @__PURE__ */ React21.createElement(Text17, { color: sessionColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(4)));
14290
14290
  }
14291
14291
  function BalanceCell({ balance }) {
14292
- const color = balance.total < 1 ? COLOR.err : balance.total < 5 ? COLOR.warn : COLOR.ok;
14293
- return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
14292
+ const color2 = balance.total < 1 ? COLOR.err : balance.total < 5 ? COLOR.warn : COLOR.ok;
14293
+ return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
14294
14294
  }
14295
14295
  function Bar({
14296
14296
  ratio,
14297
- color,
14297
+ color: color2,
14298
14298
  cells = 14
14299
14299
  }) {
14300
14300
  const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
14301
- return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
14301
+ return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
14302
14302
  }
14303
14303
  function formatTokens(n) {
14304
14304
  if (n < 1024) return String(n);
@@ -14310,8 +14310,8 @@ function formatTokens(n) {
14310
14310
  import { Box as Box20, Text as Text18, useStdout as useStdout7 } from "ink";
14311
14311
  import React22 from "react";
14312
14312
  function WelcomeBanner({ inCodeMode }) {
14313
- const { stdout: stdout3 } = useStdout7();
14314
- const cols = stdout3?.columns ?? 80;
14313
+ const { stdout: stdout4 } = useStdout7();
14314
+ const cols = stdout4?.columns ?? 80;
14315
14315
  const ruleWidth = Math.min(60, Math.max(28, cols - 4));
14316
14316
  return /* @__PURE__ */ React22.createElement(Box20, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth }), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.brand }, "\u25C8 welcome"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " \xB7 type a message to start")), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.primary }, "quick start")), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/help", desc: "every command + keyboard shortcut" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/skill", desc: "invoke a stored playbook" }), inCodeMode ? /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "@path", desc: "inline a file in your message" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "!cmd", desc: "run a shell command, output goes to context" })) : null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "/exit", desc: "quit (Ctrl+C also works)" }), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true, italic: true }, "tip:"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " Ctrl+J inserts a newline \xB7 trailing \\ also continues")), /* @__PURE__ */ React22.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth, thin: true })));
14317
14317
  }
@@ -15718,8 +15718,8 @@ ${gitTail(commit2)}` };
15718
15718
  }
15719
15719
  function gitTail(res) {
15720
15720
  const stderr = res.stderr ?? "";
15721
- const stdout3 = res.stdout ?? "";
15722
- const body = stderr.trim() || stdout3.trim();
15721
+ const stdout4 = res.stdout ?? "";
15722
+ const body = stderr.trim() || stdout4.trim();
15723
15723
  if (body) return body;
15724
15724
  if (res.error) return res.error.message;
15725
15725
  return "(no output from git)";
@@ -17689,19 +17689,19 @@ function App({
17689
17689
  }, [busy]);
17690
17690
  const [ongoingTool, setOngoingTool] = useState10(null);
17691
17691
  const [toolProgress, setToolProgress] = useState10(null);
17692
- const { stdout: stdout3 } = useStdout8();
17692
+ const { stdout: stdout4 } = useStdout8();
17693
17693
  useEffect6(() => {
17694
- if (!stdout3 || !stdout3.isTTY) return;
17695
- stdout3.write("\x1B[?2004h");
17696
- stdout3.write("\x1B[>4;2m");
17694
+ if (!stdout4 || !stdout4.isTTY) return;
17695
+ stdout4.write("\x1B[?2004h");
17696
+ stdout4.write("\x1B[>4;2m");
17697
17697
  return () => {
17698
- stdout3.write("\x1B[?2004l");
17699
- stdout3.write("\x1B[>4m");
17698
+ stdout4.write("\x1B[?2004l");
17699
+ stdout4.write("\x1B[>4m");
17700
17700
  };
17701
- }, [stdout3]);
17701
+ }, [stdout4]);
17702
17702
  const [isResizing, setIsResizing] = useState10(false);
17703
17703
  useEffect6(() => {
17704
- if (!stdout3 || !stdout3.isTTY) return;
17704
+ if (!stdout4 || !stdout4.isTTY) return;
17705
17705
  let timer = null;
17706
17706
  const onResize = () => {
17707
17707
  setIsResizing(true);
@@ -17711,12 +17711,12 @@ function App({
17711
17711
  timer = null;
17712
17712
  }, 400);
17713
17713
  };
17714
- stdout3.on("resize", onResize);
17714
+ stdout4.on("resize", onResize);
17715
17715
  return () => {
17716
- stdout3.off("resize", onResize);
17716
+ stdout4.off("resize", onResize);
17717
17717
  if (timer) clearTimeout(timer);
17718
17718
  };
17719
- }, [stdout3]);
17719
+ }, [stdout4]);
17720
17720
  const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
17721
17721
  session,
17722
17722
  setHistorical
@@ -18224,11 +18224,11 @@ function App({
18224
18224
  if (key.escape && busy) {
18225
18225
  if (abortedThisTurn.current) return;
18226
18226
  abortedThisTurn.current = true;
18227
- const resolve13 = editReviewResolveRef.current;
18228
- if (resolve13) {
18227
+ const resolve14 = editReviewResolveRef.current;
18228
+ if (resolve14) {
18229
18229
  editReviewResolveRef.current = null;
18230
18230
  setPendingEditReview(null);
18231
- resolve13("reject");
18231
+ resolve14("reject");
18232
18232
  }
18233
18233
  if (activeLoopRef.current) stopLoop();
18234
18234
  loop2.abort();
@@ -18694,11 +18694,11 @@ function App({
18694
18694
  handleStagedInputSubmitRef.current(text ?? "", { plan: plan2, mode: choice }).catch(() => void 0);
18695
18695
  },
18696
18696
  resolveEditReview: (choice) => {
18697
- const resolve13 = editReviewResolveRef.current;
18698
- if (resolve13) {
18697
+ const resolve14 = editReviewResolveRef.current;
18698
+ if (resolve14) {
18699
18699
  editReviewResolveRef.current = null;
18700
18700
  setPendingEditReview(null);
18701
- resolve13(choice);
18701
+ resolve14(choice);
18702
18702
  }
18703
18703
  },
18704
18704
  resolveWorkspaceConfirm: (choice) => {
@@ -19001,7 +19001,7 @@ function App({
19001
19001
  return;
19002
19002
  }
19003
19003
  if (result.clear && result.info) {
19004
- stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
19004
+ stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
19005
19005
  setHistorical([
19006
19006
  {
19007
19007
  id: `sys-${Date.now()}`,
@@ -19018,7 +19018,7 @@ function App({
19018
19018
  return;
19019
19019
  }
19020
19020
  if (result.clear) {
19021
- stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
19021
+ stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
19022
19022
  setHistorical([]);
19023
19023
  if (codeMode) {
19024
19024
  pendingEdits.current = [];
@@ -19601,7 +19601,7 @@ function App({
19601
19601
  refreshModels,
19602
19602
  proArmed,
19603
19603
  persistPlanState,
19604
- stdout3,
19604
+ stdout4,
19605
19605
  stopLoop,
19606
19606
  startLoop,
19607
19607
  getLoopStatus,
@@ -20224,10 +20224,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
20224
20224
  {
20225
20225
  block: pendingEditReview,
20226
20226
  onChoose: (choice) => {
20227
- const resolve13 = editReviewResolveRef.current;
20228
- if (resolve13) {
20227
+ const resolve14 = editReviewResolveRef.current;
20228
+ if (resolve14) {
20229
20229
  editReviewResolveRef.current = null;
20230
- resolve13(choice);
20230
+ resolve14(choice);
20231
20231
  }
20232
20232
  }
20233
20233
  }
@@ -20586,8 +20586,282 @@ async function codeCommand(opts = {}) {
20586
20586
  });
20587
20587
  }
20588
20588
 
20589
+ // src/cli/commands/commit.ts
20590
+ import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
20591
+ import { mkdtempSync, readFileSync as readFileSync22, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "fs";
20592
+ import { tmpdir } from "os";
20593
+ import { join as join21 } from "path";
20594
+ import { stdin as stdin2, stdout } from "process";
20595
+ import { createInterface } from "readline/promises";
20596
+ var DEFAULT_MODEL = "deepseek-v4-flash";
20597
+ var DIFF_BYTE_CAP = 80 * 1024;
20598
+ var LOG_COUNT = 10;
20599
+ var SYSTEM_PROMPT2 = `You draft git commit messages.
20600
+
20601
+ Output ONLY the commit message \u2014 no preamble, no \`\`\` fences, no "Here's a commit message:" lead-in. The first line of your output IS the commit subject.
20602
+
20603
+ Match the project's existing style:
20604
+ - Look at the recent commits provided. Mirror their voice, conventional-commit prefix usage (or absence), tense, length, body structure.
20605
+ - If recent commits use a "type(scope): summary" prefix, use it. If they don't, don't invent one.
20606
+ - Subject line: one line, \u226472 chars, imperative mood, no trailing period.
20607
+ - Body (optional): explain WHY when the diff isn't self-evident. Wrap at ~72 chars. Skip the body for trivial changes \u2014 repeating the subject in the body is noise.
20608
+
20609
+ The diff is the source of truth for what changed; describe THAT, not your guesses about the broader project. If the diff includes a deletion you can't explain from the surrounding context, name it but don't speculate about why.
20610
+
20611
+ No emojis unless the recent commits use them.
20612
+ No co-author trailers, no "Generated with X" footers.`;
20613
+ function runGit(args, opts = {}) {
20614
+ const result = spawnSync3("git", args, {
20615
+ encoding: "utf8",
20616
+ stdio: ["pipe", "pipe", "pipe"],
20617
+ input: opts.input,
20618
+ maxBuffer: 32 * 1024 * 1024
20619
+ });
20620
+ return {
20621
+ stdout: result.stdout ?? "",
20622
+ stderr: result.stderr ?? "",
20623
+ status: result.status
20624
+ };
20625
+ }
20626
+ function dieIfNotGitRepo() {
20627
+ const r = runGit(["rev-parse", "--is-inside-work-tree"]);
20628
+ if (r.status !== 0) {
20629
+ process.stderr.write("reasonix commit: not inside a git repository.\n");
20630
+ process.exit(1);
20631
+ }
20632
+ }
20633
+ function readDiff() {
20634
+ const staged = runGit(["diff", "--staged", "--no-color"]);
20635
+ if (staged.status !== 0) {
20636
+ process.stderr.write(`reasonix commit: git diff --staged failed: ${staged.stderr.trim()}
20637
+ `);
20638
+ process.exit(1);
20639
+ }
20640
+ if (staged.stdout.trim().length > 0) {
20641
+ return capDiff(staged.stdout, "staged");
20642
+ }
20643
+ const wt = runGit(["diff", "--no-color"]);
20644
+ if (wt.stdout.trim().length === 0) {
20645
+ return null;
20646
+ }
20647
+ return capDiff(wt.stdout, "working-tree");
20648
+ }
20649
+ function capDiff(raw, source) {
20650
+ if (raw.length <= DIFF_BYTE_CAP) {
20651
+ return { diff: raw, source, truncated: false };
20652
+ }
20653
+ const head = raw.slice(0, Math.floor(DIFF_BYTE_CAP * 0.7));
20654
+ const tail = raw.slice(-Math.floor(DIFF_BYTE_CAP * 0.3));
20655
+ return {
20656
+ diff: `${head}
20657
+
20658
+ [\u2026 ${raw.length - DIFF_BYTE_CAP} bytes of diff truncated \u2026]
20659
+
20660
+ ${tail}`,
20661
+ source,
20662
+ truncated: true
20663
+ };
20664
+ }
20665
+ function readRecentCommits() {
20666
+ const r = runGit(["log", `-${LOG_COUNT}`, "--no-merges", "--format=%s%n%b%n---END---"]);
20667
+ if (r.status !== 0) {
20668
+ return "";
20669
+ }
20670
+ return r.stdout.trim();
20671
+ }
20672
+ async function draftMessage(client, model2, diff, recentCommits) {
20673
+ const userParts = [];
20674
+ if (recentCommits) {
20675
+ userParts.push(`Recent commits (style reference):
20676
+
20677
+ ${recentCommits}`);
20678
+ }
20679
+ if (diff.source === "working-tree") {
20680
+ userParts.push(
20681
+ "(NOTE: diff is from the working tree, not the staging area \u2014 nothing is staged yet. The user will stage selectively after seeing the draft.)"
20682
+ );
20683
+ }
20684
+ userParts.push(`Diff to summarize:
20685
+
20686
+ ${diff.diff}`);
20687
+ const resp = await client.chat({
20688
+ model: model2,
20689
+ messages: [
20690
+ { role: "system", content: SYSTEM_PROMPT2 },
20691
+ { role: "user", content: userParts.join("\n\n") }
20692
+ ],
20693
+ temperature: 0.2
20694
+ });
20695
+ return stripCodeFences(resp.content.trim());
20696
+ }
20697
+ function stripCodeFences(s) {
20698
+ const trimmed = s.trim();
20699
+ const fenceOpen = /^```[a-zA-Z]*\n/;
20700
+ const fenceClose = /\n?```$/;
20701
+ if (fenceOpen.test(trimmed) && fenceClose.test(trimmed)) {
20702
+ return trimmed.replace(fenceOpen, "").replace(fenceClose, "").trim();
20703
+ }
20704
+ return trimmed;
20705
+ }
20706
+ function printDraft(message) {
20707
+ const sep3 = "\u2500".repeat(60);
20708
+ process.stdout.write(`
20709
+ ${sep3}
20710
+ ${message}
20711
+ ${sep3}
20712
+
20713
+ `);
20714
+ }
20715
+ async function promptChoice() {
20716
+ const rl = createInterface({ input: stdin2, output: stdout });
20717
+ try {
20718
+ const answer = await rl.question("[a]ccept / [r]egenerate / [e]dit / [c]ancel: ");
20719
+ const k = answer.trim().toLowerCase();
20720
+ if (k === "" || k === "a" || k === "y" || k === "yes") return "accept";
20721
+ if (k === "r" || k === "regen" || k === "regenerate") return "regen";
20722
+ if (k === "e" || k === "edit") return "edit";
20723
+ return "cancel";
20724
+ } finally {
20725
+ rl.close();
20726
+ }
20727
+ }
20728
+ function editInExternal(initial) {
20729
+ const editor = process.env.GIT_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
20730
+ if (!editor) {
20731
+ process.stderr.write(
20732
+ "reasonix commit: no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 can't open editor. Pick [a]ccept and `git commit --amend` afterwards.\n"
20733
+ );
20734
+ return null;
20735
+ }
20736
+ const dir = mkdtempSync(join21(tmpdir(), "reasonix-commit-"));
20737
+ const path5 = join21(dir, "COMMIT_EDITMSG");
20738
+ writeFileSync13(path5, initial, "utf8");
20739
+ const result = spawnSync3(`${editor} "${path5}"`, {
20740
+ stdio: "inherit",
20741
+ shell: true
20742
+ });
20743
+ if (result.status !== 0) {
20744
+ try {
20745
+ unlinkSync6(path5);
20746
+ } catch {
20747
+ }
20748
+ process.stderr.write(
20749
+ `reasonix commit: editor exited ${result.status} \u2014 keeping prior draft.
20750
+ `
20751
+ );
20752
+ return null;
20753
+ }
20754
+ let edited;
20755
+ try {
20756
+ edited = readFileSync22(path5, "utf8");
20757
+ } catch {
20758
+ return null;
20759
+ } finally {
20760
+ try {
20761
+ unlinkSync6(path5);
20762
+ } catch {
20763
+ }
20764
+ }
20765
+ const cleaned = edited.split(/\r?\n/).filter((line) => !/^\s*#/.test(line)).join("\n").trim();
20766
+ return cleaned || null;
20767
+ }
20768
+ function commitWithMessage(message) {
20769
+ const child = spawn6("git", ["commit", "-F", "-"], {
20770
+ stdio: ["pipe", "inherit", "inherit"]
20771
+ });
20772
+ child.stdin.write(message);
20773
+ child.stdin.end();
20774
+ child.on("close", (code) => {
20775
+ if (code !== 0) {
20776
+ process.stderr.write(`reasonix commit: git commit exited ${code}.
20777
+ `);
20778
+ process.exit(code ?? 1);
20779
+ }
20780
+ });
20781
+ }
20782
+ async function commitCommand(opts = {}) {
20783
+ loadDotenv();
20784
+ dieIfNotGitRepo();
20785
+ const apiKey = loadApiKey() ?? process.env.DEEPSEEK_API_KEY;
20786
+ if (!apiKey) {
20787
+ process.stderr.write(
20788
+ "reasonix commit: DEEPSEEK_API_KEY not set. Run `reasonix setup` to save one, or export it.\n"
20789
+ );
20790
+ process.exit(1);
20791
+ }
20792
+ const diff = readDiff();
20793
+ if (!diff) {
20794
+ process.stderr.write(
20795
+ "reasonix commit: no staged changes and working tree is clean \u2014 nothing to commit.\n"
20796
+ );
20797
+ process.exit(1);
20798
+ }
20799
+ if (diff.source === "working-tree") {
20800
+ process.stderr.write(
20801
+ "reasonix commit: nothing staged \u2014 drafting from working-tree diff. Stage your changes and re-run, or use the draft as a starting point.\n"
20802
+ );
20803
+ }
20804
+ if (diff.truncated) {
20805
+ process.stderr.write(
20806
+ "reasonix commit: diff exceeded 80KB; head + tail sent to the model. Large diffs often produce vague drafts \u2014 consider committing in smaller chunks.\n"
20807
+ );
20808
+ }
20809
+ const client = new DeepSeekClient({ apiKey });
20810
+ const model2 = opts.model ?? DEFAULT_MODEL;
20811
+ const recentCommits = readRecentCommits();
20812
+ let message = "";
20813
+ let firstPass = true;
20814
+ while (true) {
20815
+ if (firstPass) {
20816
+ process.stdout.write("Drafting commit message\u2026\n");
20817
+ } else {
20818
+ process.stdout.write("Regenerating\u2026\n");
20819
+ }
20820
+ firstPass = false;
20821
+ try {
20822
+ message = await draftMessage(client, model2, diff, recentCommits);
20823
+ } catch (err) {
20824
+ process.stderr.write(`reasonix commit: model call failed \u2014 ${err.message}
20825
+ `);
20826
+ process.exit(1);
20827
+ }
20828
+ if (!message) {
20829
+ process.stderr.write("reasonix commit: model returned an empty draft. Try again.\n");
20830
+ process.exit(1);
20831
+ }
20832
+ printDraft(message);
20833
+ if (opts.yes) break;
20834
+ if (diff.source === "working-tree") {
20835
+ process.stdout.write(
20836
+ "(no staged changes \u2014 draft printed above for you to copy. Stage with `git add` and re-run to commit.)\n"
20837
+ );
20838
+ return;
20839
+ }
20840
+ const choice = await promptChoice();
20841
+ if (choice === "accept") break;
20842
+ if (choice === "cancel") {
20843
+ process.stderr.write("commit cancelled.\n");
20844
+ return;
20845
+ }
20846
+ if (choice === "edit") {
20847
+ const edited = editInExternal(message);
20848
+ if (edited) {
20849
+ message = edited;
20850
+ printDraft(message);
20851
+ const next = await promptChoice();
20852
+ if (next === "accept") break;
20853
+ if (next === "cancel") {
20854
+ process.stderr.write("commit cancelled.\n");
20855
+ return;
20856
+ }
20857
+ }
20858
+ }
20859
+ }
20860
+ commitWithMessage(message);
20861
+ }
20862
+
20589
20863
  // src/cli/commands/diff.ts
20590
- import { writeFileSync as writeFileSync13 } from "fs";
20864
+ import { writeFileSync as writeFileSync14 } from "fs";
20591
20865
  import { basename as basename3 } from "path";
20592
20866
  import { render as render2 } from "ink";
20593
20867
  import React30 from "react";
@@ -20626,8 +20900,8 @@ function CacheBadge({ usage }) {
20626
20900
  const total = hit + miss;
20627
20901
  if (total === 0) return null;
20628
20902
  const pct2 = hit / total * 100;
20629
- const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
20630
- return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color }, pct2.toFixed(1), "%"));
20903
+ const color2 = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
20904
+ return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color: color2 }, pct2.toFixed(1), "%"));
20631
20905
  }
20632
20906
  function truncate2(s, max) {
20633
20907
  return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
@@ -20734,7 +21008,7 @@ async function diffCommand(opts) {
20734
21008
  if (wantMarkdown) {
20735
21009
  console.log(renderSummaryTable(report));
20736
21010
  const md = renderMarkdown(report);
20737
- writeFileSync13(opts.mdPath, md, "utf8");
21011
+ writeFileSync14(opts.mdPath, md, "utf8");
20738
21012
  console.log(`
20739
21013
  markdown report written to ${opts.mdPath}`);
20740
21014
  return;
@@ -20750,12 +21024,317 @@ markdown report written to ${opts.mdPath}`);
20750
21024
  console.log(renderSummaryTable(report));
20751
21025
  }
20752
21026
 
21027
+ // src/cli/commands/doctor.ts
21028
+ import { existsSync as existsSync23, statSync as statSync14 } from "fs";
21029
+ import { homedir as homedir10 } from "os";
21030
+ import { dirname as dirname16, join as join22, resolve as resolve12 } from "path";
21031
+ var TTY = process.stdout.isTTY && process.env.TERM !== "dumb";
21032
+ function color(text, code) {
21033
+ if (!TTY) return text;
21034
+ return `\x1B[${code}m${text}\x1B[0m`;
21035
+ }
21036
+ function badge(level) {
21037
+ if (level === "ok") return color("\u2713", "32");
21038
+ if (level === "warn") return color("\u26A0", "33");
21039
+ return color("\u2717", "31");
21040
+ }
21041
+ function tail4(s) {
21042
+ return s.length <= 4 ? s : `\u2026${s.slice(-4)}`;
21043
+ }
21044
+ function fmtBytes(n) {
21045
+ if (n < 1024) return `${n} B`;
21046
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
21047
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
21048
+ }
21049
+ async function checkApiKey() {
21050
+ const fromEnv = process.env.DEEPSEEK_API_KEY;
21051
+ if (fromEnv) {
21052
+ return {
21053
+ label: "api key ",
21054
+ level: "ok",
21055
+ detail: `set via env DEEPSEEK_API_KEY (${tail4(fromEnv)})`
21056
+ };
21057
+ }
21058
+ try {
21059
+ const cfg = readConfig();
21060
+ if (cfg.apiKey) {
21061
+ return {
21062
+ label: "api key ",
21063
+ level: "ok",
21064
+ detail: `from ${defaultConfigPath()} (${tail4(cfg.apiKey)})`
21065
+ };
21066
+ }
21067
+ } catch {
21068
+ }
21069
+ return {
21070
+ label: "api key ",
21071
+ level: "fail",
21072
+ detail: "not set \u2014 `reasonix setup` to save one, or export DEEPSEEK_API_KEY. Get a key at https://platform.deepseek.com/api_keys"
21073
+ };
21074
+ }
21075
+ async function checkConfig() {
21076
+ const path5 = defaultConfigPath();
21077
+ if (!existsSync23(path5)) {
21078
+ return {
21079
+ label: "config ",
21080
+ level: "warn",
21081
+ detail: "missing \u2014 running with library defaults. `reasonix setup` writes one."
21082
+ };
21083
+ }
21084
+ try {
21085
+ const cfg = readConfig(path5);
21086
+ const parts = [];
21087
+ if (cfg.preset) parts.push(`preset=${cfg.preset}`);
21088
+ if (cfg.editMode) parts.push(`editMode=${cfg.editMode}`);
21089
+ if (cfg.mcp && cfg.mcp.length > 0) parts.push(`mcp=${cfg.mcp.length}`);
21090
+ return {
21091
+ label: "config ",
21092
+ level: "ok",
21093
+ detail: `${path5}${parts.length ? ` (${parts.join(", ")})` : ""}`
21094
+ };
21095
+ } catch (err) {
21096
+ return {
21097
+ label: "config ",
21098
+ level: "fail",
21099
+ detail: `${path5} unreadable \u2014 ${err.message}`
21100
+ };
21101
+ }
21102
+ }
21103
+ async function checkApiReach() {
21104
+ const key = process.env.DEEPSEEK_API_KEY ?? readConfig().apiKey;
21105
+ if (!key) {
21106
+ return {
21107
+ label: "api reach ",
21108
+ level: "warn",
21109
+ detail: "skipped \u2014 no api key to test with"
21110
+ };
21111
+ }
21112
+ try {
21113
+ const client = new DeepSeekClient({ apiKey: key });
21114
+ const ctl = new AbortController();
21115
+ const timer = setTimeout(() => ctl.abort(), 8e3);
21116
+ let balance;
21117
+ try {
21118
+ balance = await client.getBalance({ signal: ctl.signal });
21119
+ } finally {
21120
+ clearTimeout(timer);
21121
+ }
21122
+ if (!balance) {
21123
+ return {
21124
+ label: "api reach ",
21125
+ level: "fail",
21126
+ detail: "/user/balance returned null \u2014 auth failed or network blocked"
21127
+ };
21128
+ }
21129
+ if (!balance.is_available) {
21130
+ const info2 = balance.balance_infos[0];
21131
+ return {
21132
+ label: "api reach ",
21133
+ level: "warn",
21134
+ detail: `account flagged not-available${info2 ? ` (${info2.total_balance} ${info2.currency})` : ""} \u2014 top up or check your dashboard`
21135
+ };
21136
+ }
21137
+ const info = balance.balance_infos[0];
21138
+ return {
21139
+ label: "api reach ",
21140
+ level: "ok",
21141
+ detail: info ? `/user/balance ok \u2014 ${info.total_balance} ${info.currency}` : "/user/balance ok"
21142
+ };
21143
+ } catch (err) {
21144
+ return {
21145
+ label: "api reach ",
21146
+ level: "fail",
21147
+ detail: `${err.message}`
21148
+ };
21149
+ }
21150
+ }
21151
+ async function checkTokenizer() {
21152
+ const candidates = [
21153
+ join22(
21154
+ dirname16(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")),
21155
+ "..",
21156
+ "..",
21157
+ "..",
21158
+ "data",
21159
+ "deepseek-tokenizer.json.gz"
21160
+ ),
21161
+ join22(process.cwd(), "data", "deepseek-tokenizer.json.gz")
21162
+ ];
21163
+ for (const p of candidates) {
21164
+ if (existsSync23(p)) {
21165
+ try {
21166
+ const stat2 = statSync14(p);
21167
+ return {
21168
+ label: "tokenizer ",
21169
+ level: "ok",
21170
+ detail: `${p} (${fmtBytes(stat2.size)})`
21171
+ };
21172
+ } catch {
21173
+ }
21174
+ }
21175
+ }
21176
+ return {
21177
+ label: "tokenizer ",
21178
+ level: "warn",
21179
+ detail: "data/deepseek-tokenizer.json.gz not found \u2014 token counts will fall back to char heuristics"
21180
+ };
21181
+ }
21182
+ async function checkSessions() {
21183
+ try {
21184
+ const list = listSessions();
21185
+ if (list.length === 0) {
21186
+ return {
21187
+ label: "sessions ",
21188
+ level: "ok",
21189
+ detail: "0 saved"
21190
+ };
21191
+ }
21192
+ const totalBytes = list.reduce((s, e) => s + e.size, 0);
21193
+ const oldest = list[list.length - 1];
21194
+ const ageDays = Math.floor((Date.now() - oldest.mtime.getTime()) / (24 * 60 * 60 * 1e3));
21195
+ const stale = list.filter(
21196
+ (e) => Date.now() - e.mtime.getTime() >= 90 * 24 * 60 * 60 * 1e3
21197
+ ).length;
21198
+ const detail = `${list.length} saved \xB7 ${fmtBytes(totalBytes)} \xB7 oldest ${ageDays}d`;
21199
+ if (stale > 0) {
21200
+ return {
21201
+ label: "sessions ",
21202
+ level: "warn",
21203
+ detail: `${detail} \xB7 ${stale} idle \u226590d (run /prune-sessions)`
21204
+ };
21205
+ }
21206
+ return { label: "sessions ", level: "ok", detail };
21207
+ } catch (err) {
21208
+ return {
21209
+ label: "sessions ",
21210
+ level: "warn",
21211
+ detail: `cannot list \u2014 ${err.message}`
21212
+ };
21213
+ }
21214
+ }
21215
+ async function checkHooks(projectRoot) {
21216
+ try {
21217
+ const all = loadHooks({ projectRoot });
21218
+ const global = all.filter((h) => h.scope === "global").length;
21219
+ const project = all.filter((h) => h.scope === "project").length;
21220
+ return {
21221
+ label: "hooks ",
21222
+ level: "ok",
21223
+ detail: `${global} global, ${project} project`
21224
+ };
21225
+ } catch (err) {
21226
+ return {
21227
+ label: "hooks ",
21228
+ level: "warn",
21229
+ detail: `couldn't parse settings.json \u2014 ${err.message}`
21230
+ };
21231
+ }
21232
+ }
21233
+ async function checkOllama(projectRoot) {
21234
+ let exists = false;
21235
+ try {
21236
+ exists = await indexExists(projectRoot);
21237
+ } catch {
21238
+ }
21239
+ if (!exists) {
21240
+ return {
21241
+ label: "ollama ",
21242
+ level: "ok",
21243
+ detail: "not in use (no semantic index built; `reasonix index` to enable)"
21244
+ };
21245
+ }
21246
+ try {
21247
+ const status2 = await checkOllamaStatus(process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text");
21248
+ if (!status2.binaryFound) {
21249
+ return {
21250
+ label: "ollama ",
21251
+ level: "warn",
21252
+ detail: "binary not on PATH \u2014 semantic_search will fail; install from https://ollama.com"
21253
+ };
21254
+ }
21255
+ if (!status2.daemonRunning) {
21256
+ return {
21257
+ label: "ollama ",
21258
+ level: "warn",
21259
+ detail: "daemon not running \u2014 `ollama serve` (or just call /semantic in TUI to auto-start)"
21260
+ };
21261
+ }
21262
+ if (!status2.modelPulled) {
21263
+ return {
21264
+ label: "ollama ",
21265
+ level: "warn",
21266
+ detail: `model ${status2.modelName} not pulled \u2014 \`ollama pull ${status2.modelName}\``
21267
+ };
21268
+ }
21269
+ return {
21270
+ label: "ollama ",
21271
+ level: "ok",
21272
+ detail: `daemon up \xB7 model ${status2.modelName} ready`
21273
+ };
21274
+ } catch (err) {
21275
+ return {
21276
+ label: "ollama ",
21277
+ level: "warn",
21278
+ detail: `probe failed \u2014 ${err.message}`
21279
+ };
21280
+ }
21281
+ }
21282
+ async function checkProject(projectRoot) {
21283
+ const markers = [".git", "REASONIX.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
21284
+ const found = markers.filter((m) => existsSync23(join22(projectRoot, m)));
21285
+ if (found.length === 0) {
21286
+ return {
21287
+ label: "project ",
21288
+ level: "warn",
21289
+ detail: `${projectRoot} has none of: ${markers.slice(0, 3).join(", ")} \u2026 \u2014 \`reasonix code\` will still run, but @-mentions and project memory have nothing to anchor`
21290
+ };
21291
+ }
21292
+ return {
21293
+ label: "project ",
21294
+ level: "ok",
21295
+ detail: `${projectRoot} (${found.join(", ")})`
21296
+ };
21297
+ }
21298
+ async function doctorCommand() {
21299
+ loadDotenv();
21300
+ const projectRoot = resolve12(process.cwd());
21301
+ console.log(`${color(`reasonix ${VERSION} \xB7 doctor`, "1")} (cwd: ${projectRoot})`);
21302
+ console.log(` home: ${homedir10()}`);
21303
+ console.log("");
21304
+ const checks = await Promise.all([
21305
+ checkApiKey(),
21306
+ checkConfig(),
21307
+ checkApiReach(),
21308
+ checkTokenizer(),
21309
+ checkSessions(),
21310
+ checkHooks(projectRoot),
21311
+ checkOllama(projectRoot),
21312
+ checkProject(projectRoot)
21313
+ ]);
21314
+ for (const c of checks) {
21315
+ console.log(` ${badge(c.level)} ${c.label} ${c.detail}`);
21316
+ }
21317
+ const ok = checks.filter((c) => c.level === "ok").length;
21318
+ const warn = checks.filter((c) => c.level === "warn").length;
21319
+ const fail = checks.filter((c) => c.level === "fail").length;
21320
+ console.log("");
21321
+ const summary = `${ok} ok \xB7 ${warn} warn \xB7 ${fail} fail`;
21322
+ if (fail > 0) {
21323
+ console.log(color(summary, "31"));
21324
+ process.exit(1);
21325
+ } else if (warn > 0) {
21326
+ console.log(color(summary, "33"));
21327
+ } else {
21328
+ console.log(color(summary, "32"));
21329
+ }
21330
+ }
21331
+
20753
21332
  // src/cli/commands/index.ts
20754
- import { resolve as resolve12 } from "path";
21333
+ import { resolve as resolve13 } from "path";
20755
21334
 
20756
21335
  // src/index/semantic/preflight.ts
20757
- import { stdin as stdin2, stdout } from "process";
20758
- import { createInterface } from "readline/promises";
21336
+ import { stdin as stdin3, stdout as stdout2 } from "process";
21337
+ import { createInterface as createInterface2 } from "readline/promises";
20759
21338
  async function ollamaPreflight(opts) {
20760
21339
  const log = opts.log ?? ((line) => process.stderr.write(line));
20761
21340
  const status2 = await checkOllamaStatus(opts.model, opts.baseUrl);
@@ -20813,7 +21392,7 @@ async function ollamaPreflight(opts) {
20813
21392
  }
20814
21393
  async function confirm(question, defaultYes) {
20815
21394
  const suffix = defaultYes ? "[Y/n]" : "[y/N]";
20816
- const rl = createInterface({ input: stdin2, output: stdout });
21395
+ const rl = createInterface2({ input: stdin3, output: stdout2 });
20817
21396
  try {
20818
21397
  const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
20819
21398
  if (raw === "") return defaultYes;
@@ -20825,7 +21404,7 @@ async function confirm(question, defaultYes) {
20825
21404
 
20826
21405
  // src/cli/commands/index.ts
20827
21406
  async function indexCommand(opts = {}) {
20828
- const root = resolve12(opts.dir ?? process.cwd());
21407
+ const root = resolve13(opts.dir ?? process.cwd());
20829
21408
  const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
20830
21409
  const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
20831
21410
  const preflightOk = await ollamaPreflight({
@@ -21232,12 +21811,12 @@ function oneLine2(s, max = 200) {
21232
21811
  }
21233
21812
 
21234
21813
  // src/cli/commands/run.ts
21235
- import { stdin as stdin3, stdout as stdout2 } from "process";
21236
- import { createInterface as createInterface2 } from "readline/promises";
21814
+ import { stdin as stdin4, stdout as stdout3 } from "process";
21815
+ import { createInterface as createInterface3 } from "readline/promises";
21237
21816
  async function ensureApiKey() {
21238
21817
  const existing = loadApiKey();
21239
21818
  if (existing) return existing;
21240
- if (!stdin3.isTTY) {
21819
+ if (!stdin4.isTTY) {
21241
21820
  process.stderr.write(
21242
21821
  "DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
21243
21822
  );
@@ -21246,7 +21825,7 @@ async function ensureApiKey() {
21246
21825
  process.stdout.write(
21247
21826
  "DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
21248
21827
  );
21249
- const rl = createInterface2({ input: stdin3, output: stdout2 });
21828
+ const rl = createInterface3({ input: stdin4, output: stdout3 });
21250
21829
  try {
21251
21830
  while (true) {
21252
21831
  const answer = (await rl.question("API key \u203A ")).trim();
@@ -21717,7 +22296,7 @@ async function setupCommand(_opts = {}) {
21717
22296
  }
21718
22297
 
21719
22298
  // src/cli/commands/update.ts
21720
- import { spawn as spawn6 } from "child_process";
22299
+ import { spawn as spawn7 } from "child_process";
21721
22300
  function planUpdate(input) {
21722
22301
  const diff = compareVersions(input.current, input.latest);
21723
22302
  if (diff > 0) {
@@ -21747,13 +22326,13 @@ function planUpdate(input) {
21747
22326
  };
21748
22327
  }
21749
22328
  function defaultSpawn(argv) {
21750
- return new Promise((resolve13, reject) => {
21751
- const child = spawn6(argv[0], argv.slice(1), {
22329
+ return new Promise((resolve14, reject) => {
22330
+ const child = spawn7(argv[0], argv.slice(1), {
21752
22331
  stdio: "inherit",
21753
22332
  shell: process.platform === "win32"
21754
22333
  });
21755
22334
  child.once("error", reject);
21756
- child.once("exit", (code) => resolve13(code ?? 1));
22335
+ child.once("exit", (code) => resolve14(code ?? 1));
21757
22336
  });
21758
22337
  }
21759
22338
  async function updateCommand(opts = {}) {
@@ -22038,6 +22617,19 @@ program.command("stats [transcript]").description(
22038
22617
  ).action((transcript) => {
22039
22618
  statsCommand({ transcript });
22040
22619
  });
22620
+ program.command("doctor").description(
22621
+ "One-command health check \u2014 API key, config, /user/balance reachability, tokenizer, sessions, hooks, Ollama (if used), project markers. Exit 1 on any fail; 0 on warn / clean."
22622
+ ).action(async () => {
22623
+ await doctorCommand();
22624
+ });
22625
+ program.command("commit").description(
22626
+ "Draft a commit message from the staged diff (or working tree, if nothing staged), matching your repo's recent commit style. Review interactively before it lands."
22627
+ ).option("-m, --model <id>", "Override the default model (deepseek-v4-flash)").option(
22628
+ "-y, --yes",
22629
+ "Skip the [a]ccept / [r]egenerate prompt and commit the first draft. Useful in scripts."
22630
+ ).action(async (opts) => {
22631
+ await commitCommand({ model: opts.model, yes: !!opts.yes });
22632
+ });
22041
22633
  program.command("sessions [name]").description("List saved chat sessions, or inspect one by name.").option("-v, --verbose", "Include system prompts + tool-call metadata when inspecting").action((name, opts) => {
22042
22634
  sessionsCommand({ name, verbose: !!opts.verbose });
22043
22635
  });