reasonix 0.5.3 → 0.5.6

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
@@ -126,8 +126,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
126
126
  }
127
127
  function sleep(ms, signal) {
128
128
  if (ms <= 0) return Promise.resolve();
129
- return new Promise((resolve6, reject) => {
130
- const timer = setTimeout(resolve6, ms);
129
+ return new Promise((resolve7, reject) => {
130
+ const timer = setTimeout(resolve7, ms);
131
131
  if (signal) {
132
132
  const onAbort = () => {
133
133
  clearTimeout(timer);
@@ -599,7 +599,7 @@ function matchesTool(hook, toolName) {
599
599
  }
600
600
  }
601
601
  function defaultSpawner(input) {
602
- return new Promise((resolve6) => {
602
+ return new Promise((resolve7) => {
603
603
  const child = spawn(input.command, {
604
604
  cwd: input.cwd,
605
605
  shell: true,
@@ -626,7 +626,7 @@ function defaultSpawner(input) {
626
626
  });
627
627
  child.once("error", (err) => {
628
628
  clearTimeout(timer);
629
- resolve6({
629
+ resolve7({
630
630
  exitCode: null,
631
631
  stdout: stdout2,
632
632
  stderr,
@@ -636,7 +636,7 @@ function defaultSpawner(input) {
636
636
  });
637
637
  child.once("close", (code) => {
638
638
  clearTimeout(timer);
639
- resolve6({
639
+ resolve7({
640
640
  exitCode: code,
641
641
  stdout: stdout2.trim(),
642
642
  stderr: stderr.trim(),
@@ -697,7 +697,7 @@ async function runHooks(opts) {
697
697
  }
698
698
 
699
699
  // src/tokenizer.ts
700
- import { readFileSync as readFileSync3 } from "fs";
700
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
701
701
  import { createRequire } from "module";
702
702
  import { dirname as dirname2, join as join3 } from "path";
703
703
  import { fileURLToPath } from "url";
@@ -725,17 +725,24 @@ function buildByteToChar() {
725
725
  var cached = null;
726
726
  function resolveDataPath() {
727
727
  if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
728
+ const candidates = [];
728
729
  try {
729
730
  const here = dirname2(fileURLToPath(import.meta.url));
730
- return join3(here, "..", "data", "deepseek-tokenizer.json.gz");
731
+ candidates.push(join3(here, "..", "data", "deepseek-tokenizer.json.gz"));
732
+ candidates.push(join3(here, "..", "..", "data", "deepseek-tokenizer.json.gz"));
731
733
  } catch {
734
+ }
735
+ try {
732
736
  const req = createRequire(import.meta.url);
733
- return join3(
734
- dirname2(req.resolve("reasonix/package.json")),
735
- "data",
736
- "deepseek-tokenizer.json.gz"
737
+ candidates.push(
738
+ join3(dirname2(req.resolve("reasonix/package.json")), "data", "deepseek-tokenizer.json.gz")
737
739
  );
740
+ } catch {
741
+ }
742
+ for (const p of candidates) {
743
+ if (existsSync2(p)) return p;
738
744
  }
745
+ return candidates[0] ?? join3(process.cwd(), "data", "deepseek-tokenizer.json.gz");
739
746
  }
740
747
  function loadTokenizer() {
741
748
  if (cached) return cached;
@@ -859,6 +866,25 @@ function encode(text) {
859
866
  function countTokens(text) {
860
867
  return encode(text).length;
861
868
  }
869
+ function estimateConversationTokens(messages) {
870
+ let total = 0;
871
+ for (const m of messages) {
872
+ if (typeof m.content === "string" && m.content) {
873
+ total += countTokens(m.content);
874
+ }
875
+ if (m.tool_calls && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
876
+ total += countTokens(JSON.stringify(m.tool_calls));
877
+ }
878
+ }
879
+ return total;
880
+ }
881
+ function estimateRequestTokens(messages, toolSpecs) {
882
+ let total = estimateConversationTokens(messages);
883
+ if (toolSpecs && toolSpecs.length > 0) {
884
+ total += countTokens(JSON.stringify(toolSpecs));
885
+ }
886
+ return total;
887
+ }
862
888
 
863
889
  // src/repair/flatten.ts
864
890
  function analyzeSchema(schema) {
@@ -1546,7 +1572,7 @@ function signature2(call) {
1546
1572
  import {
1547
1573
  appendFileSync,
1548
1574
  chmodSync as chmodSync2,
1549
- existsSync as existsSync2,
1575
+ existsSync as existsSync3,
1550
1576
  mkdirSync as mkdirSync2,
1551
1577
  readFileSync as readFileSync4,
1552
1578
  readdirSync,
@@ -1568,7 +1594,7 @@ function sanitizeName(name) {
1568
1594
  }
1569
1595
  function loadSessionMessages(name) {
1570
1596
  const path = sessionPath(name);
1571
- if (!existsSync2(path)) return [];
1597
+ if (!existsSync3(path)) return [];
1572
1598
  try {
1573
1599
  const raw = readFileSync4(path, "utf8");
1574
1600
  const out = [];
@@ -1598,7 +1624,7 @@ function appendSessionMessage(name, message) {
1598
1624
  }
1599
1625
  function listSessions() {
1600
1626
  const dir = sessionsDir();
1601
- if (!existsSync2(dir)) return [];
1627
+ if (!existsSync3(dir)) return [];
1602
1628
  try {
1603
1629
  const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1604
1630
  return files.map((file) => {
@@ -1794,9 +1820,9 @@ var CacheFirstLoop = class {
1794
1820
  this.sessionName = opts.session ?? null;
1795
1821
  if (this.sessionName) {
1796
1822
  const prior = loadSessionMessages(this.sessionName);
1797
- const { messages, healedCount, healedFrom } = healLoadedMessages(
1823
+ const { messages, healedCount, tokensSaved } = healLoadedMessagesByTokens(
1798
1824
  prior,
1799
- DEFAULT_MAX_RESULT_CHARS
1825
+ DEFAULT_MAX_RESULT_TOKENS
1800
1826
  );
1801
1827
  for (const msg of messages) this.log.append(msg);
1802
1828
  this.resumedMessageCount = messages.length;
@@ -1806,7 +1832,7 @@ var CacheFirstLoop = class {
1806
1832
  } catch {
1807
1833
  }
1808
1834
  process.stderr.write(
1809
- `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${healedFrom > 0 ? ` (was ${healedFrom.toLocaleString()} chars oversized)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
1835
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
1810
1836
  `
1811
1837
  );
1812
1838
  }
@@ -2002,7 +2028,32 @@ var CacheFirstLoop = class {
2002
2028
  content: `${iter}/${this.maxToolIters} tool calls used \u2014 approaching budget. Press Esc to force a summary now.`
2003
2029
  };
2004
2030
  }
2005
- const messages = this.buildMessages(pendingUser);
2031
+ let messages = this.buildMessages(pendingUser);
2032
+ {
2033
+ const ctxMax2 = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
2034
+ const estimate = estimateRequestTokens(messages, this.prefix.toolSpecs);
2035
+ if (estimate / ctxMax2 > 0.95) {
2036
+ const result = this.compact(1e3);
2037
+ if (result.healedCount > 0) {
2038
+ yield {
2039
+ turn: this._turn,
2040
+ role: "warning",
2041
+ content: `preflight: request ~${estimate.toLocaleString()}/${ctxMax2.toLocaleString()} tokens (${Math.round(
2042
+ estimate / ctxMax2 * 100
2043
+ )}%) \u2014 pre-compacted ${result.healedCount} tool result(s), saved ${result.tokensSaved.toLocaleString()} tokens. Sending.`
2044
+ };
2045
+ messages = this.buildMessages(pendingUser);
2046
+ } else {
2047
+ yield {
2048
+ turn: this._turn,
2049
+ role: "warning",
2050
+ content: `preflight: request ~${estimate.toLocaleString()}/${ctxMax2.toLocaleString()} tokens (${Math.round(
2051
+ estimate / ctxMax2 * 100
2052
+ )}%) and nothing to auto-compact \u2014 DeepSeek will likely 400. Run /forget or /clear to start fresh.`
2053
+ };
2054
+ }
2055
+ }
2056
+ }
2006
2057
  let assistantContent = "";
2007
2058
  let reasoningContent = "";
2008
2059
  let toolCalls = [];
@@ -2050,8 +2101,8 @@ var CacheFirstLoop = class {
2050
2101
  }
2051
2102
  );
2052
2103
  for (let k = 0; k < budget; k++) {
2053
- const sample = queue.shift() ?? await new Promise((resolve6) => {
2054
- waiter = resolve6;
2104
+ const sample = queue.shift() ?? await new Promise((resolve7) => {
2105
+ waiter = resolve7;
2055
2106
  });
2056
2107
  yield {
2057
2108
  turn: this._turn,
@@ -2450,15 +2501,12 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
2450
2501
  });
2451
2502
  return { messages: out, healedCount, tokensSaved, charsSaved };
2452
2503
  }
2453
- function healLoadedMessages(messages, maxChars) {
2454
- const shrunk = shrinkOversizedToolResults(messages, maxChars);
2455
- let healedCount = shrunk.healedCount;
2504
+ function fixToolCallPairing(messages) {
2456
2505
  const out = [];
2457
- const openCallIds = /* @__PURE__ */ new Set();
2458
2506
  let droppedAssistantCalls = 0;
2459
2507
  let droppedStrayTools = 0;
2460
- for (let i = 0; i < shrunk.messages.length; i++) {
2461
- const msg = shrunk.messages[i];
2508
+ for (let i = 0; i < messages.length; i++) {
2509
+ const msg = messages[i];
2462
2510
  if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
2463
2511
  const needed = /* @__PURE__ */ new Set();
2464
2512
  for (const call of msg.tool_calls) {
@@ -2466,8 +2514,8 @@ function healLoadedMessages(messages, maxChars) {
2466
2514
  }
2467
2515
  const candidates = [];
2468
2516
  let j = i + 1;
2469
- while (j < shrunk.messages.length && needed.size > 0) {
2470
- const nxt = shrunk.messages[j];
2517
+ while (j < messages.length && needed.size > 0) {
2518
+ const nxt = messages[j];
2471
2519
  if (nxt.role !== "tool") break;
2472
2520
  const id = nxt.tool_call_id ?? "";
2473
2521
  if (!needed.has(id)) break;
@@ -2492,8 +2540,24 @@ function healLoadedMessages(messages, maxChars) {
2492
2540
  }
2493
2541
  out.push(msg);
2494
2542
  }
2495
- healedCount += droppedAssistantCalls + droppedStrayTools;
2496
- return { messages: out, healedCount, healedFrom: shrunk.healedFrom };
2543
+ return { messages: out, droppedAssistantCalls, droppedStrayTools };
2544
+ }
2545
+ function healLoadedMessages(messages, maxChars) {
2546
+ const shrunk = shrinkOversizedToolResults(messages, maxChars);
2547
+ const paired = fixToolCallPairing(shrunk.messages);
2548
+ const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
2549
+ return { messages: paired.messages, healedCount, healedFrom: shrunk.healedFrom };
2550
+ }
2551
+ function healLoadedMessagesByTokens(messages, maxTokens) {
2552
+ const shrunk = shrinkOversizedToolResultsByTokens(messages, maxTokens);
2553
+ const paired = fixToolCallPairing(shrunk.messages);
2554
+ const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
2555
+ return {
2556
+ messages: paired.messages,
2557
+ healedCount,
2558
+ tokensSaved: shrunk.tokensSaved,
2559
+ charsSaved: shrunk.charsSaved
2560
+ };
2497
2561
  }
2498
2562
  function formatLoopError(err) {
2499
2563
  const msg = err.message ?? "";
@@ -2505,6 +2569,93 @@ function formatLoopError(err) {
2505
2569
  return msg;
2506
2570
  }
2507
2571
 
2572
+ // src/at-mentions.ts
2573
+ import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
2574
+ import { isAbsolute, relative, resolve } from "path";
2575
+ var DEFAULT_AT_MENTION_MAX_BYTES = 64 * 1024;
2576
+ var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
2577
+ function expandAtMentions(text, rootDir, opts = {}) {
2578
+ const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
2579
+ const fs2 = opts.fs ?? defaultFs;
2580
+ const root = resolve(rootDir);
2581
+ const seen = /* @__PURE__ */ new Map();
2582
+ const expansions = [];
2583
+ for (const match of text.matchAll(AT_MENTION_PATTERN)) {
2584
+ const rawPath = match[1] ?? "";
2585
+ const cleaned = rawPath.replace(/\.+$/, "");
2586
+ if (!cleaned) continue;
2587
+ const token = `@${cleaned}`;
2588
+ if (seen.has(token)) continue;
2589
+ const expansion = resolveMention(cleaned, root, maxBytes, fs2);
2590
+ seen.set(token, expansion);
2591
+ expansions.push(expansion);
2592
+ }
2593
+ if (expansions.length === 0) return { text, expansions };
2594
+ const blocks = [];
2595
+ for (const ex of expansions) {
2596
+ if (ex.ok) {
2597
+ const content = readSafe(root, ex.path, fs2);
2598
+ blocks.push(`<file path="${ex.path}">
2599
+ ${content}
2600
+ </file>`);
2601
+ } else {
2602
+ blocks.push(`<file path="${ex.path}" skipped="${ex.skip}" />`);
2603
+ }
2604
+ }
2605
+ const augmented = `${text}
2606
+
2607
+ [Referenced files]
2608
+ ${blocks.join("\n\n")}`;
2609
+ return { text: augmented, expansions };
2610
+ }
2611
+ function resolveMention(rawPath, root, maxBytes, fs2) {
2612
+ if (isAbsolute(rawPath)) {
2613
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
2614
+ }
2615
+ const resolved = resolve(root, rawPath);
2616
+ const rel = relative(root, resolved);
2617
+ if (rel.startsWith("..") || isAbsolute(rel)) {
2618
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
2619
+ }
2620
+ if (!fs2.exists(resolved)) {
2621
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "missing" };
2622
+ }
2623
+ if (!fs2.isFile(resolved)) {
2624
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
2625
+ }
2626
+ const size = fs2.size(resolved);
2627
+ if (size > maxBytes) {
2628
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "too-large", bytes: size };
2629
+ }
2630
+ return { token: `@${rawPath}`, path: rawPath, ok: true, bytes: size };
2631
+ }
2632
+ function readSafe(root, rawPath, fs2) {
2633
+ const resolved = resolve(root, rawPath);
2634
+ try {
2635
+ return fs2.read(resolved);
2636
+ } catch {
2637
+ return "(read failed)";
2638
+ }
2639
+ }
2640
+ var defaultFs = {
2641
+ exists: (p) => existsSync4(p),
2642
+ isFile: (p) => {
2643
+ try {
2644
+ return statSync2(p).isFile();
2645
+ } catch {
2646
+ return false;
2647
+ }
2648
+ },
2649
+ size: (p) => {
2650
+ try {
2651
+ return statSync2(p).size;
2652
+ } catch {
2653
+ return 0;
2654
+ }
2655
+ },
2656
+ read: (p) => readFileSync5(p, "utf8")
2657
+ };
2658
+
2508
2659
  // src/tools/filesystem.ts
2509
2660
  import { promises as fs } from "fs";
2510
2661
  import * as pathMod from "path";
@@ -3315,7 +3466,7 @@ function forkRegistryExcluding(parent, exclude) {
3315
3466
 
3316
3467
  // src/tools/shell.ts
3317
3468
  import { spawn as spawn2 } from "child_process";
3318
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
3469
+ import { existsSync as existsSync5, statSync as statSync3 } from "fs";
3319
3470
  import * as pathMod2 from "path";
3320
3471
  var DEFAULT_TIMEOUT_SEC = 60;
3321
3472
  var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
@@ -3485,7 +3636,7 @@ async function runCommand(cmd, opts) {
3485
3636
  };
3486
3637
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
3487
3638
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
3488
- return await new Promise((resolve6, reject) => {
3639
+ return await new Promise((resolve7, reject) => {
3489
3640
  let child;
3490
3641
  try {
3491
3642
  child = spawn2(bin, args, effectiveSpawnOpts);
@@ -3518,7 +3669,7 @@ async function runCommand(cmd, opts) {
3518
3669
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
3519
3670
 
3520
3671
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
3521
- resolve6({ exitCode: code, output, timedOut });
3672
+ resolve7({ exitCode: code, output, timedOut });
3522
3673
  });
3523
3674
  });
3524
3675
  }
@@ -3543,7 +3694,7 @@ function resolveExecutable(cmd, opts = {}) {
3543
3694
  }
3544
3695
  function defaultIsFile(full) {
3545
3696
  try {
3546
- return existsSync3(full) && statSync2(full).isFile();
3697
+ return existsSync5(full) && statSync3(full).isFile();
3547
3698
  } catch {
3548
3699
  return false;
3549
3700
  }
@@ -3870,12 +4021,12 @@ ${i + 1}. ${r.title}`);
3870
4021
  }
3871
4022
 
3872
4023
  // src/env.ts
3873
- import { readFileSync as readFileSync5 } from "fs";
3874
- import { resolve as resolve3 } from "path";
4024
+ import { readFileSync as readFileSync6 } from "fs";
4025
+ import { resolve as resolve4 } from "path";
3875
4026
  function loadDotenv(path = ".env") {
3876
4027
  let raw;
3877
4028
  try {
3878
- raw = readFileSync5(resolve3(process.cwd(), path), "utf8");
4029
+ raw = readFileSync6(resolve4(process.cwd(), path), "utf8");
3879
4030
  } catch {
3880
4031
  return;
3881
4032
  }
@@ -3894,7 +4045,7 @@ function loadDotenv(path = ".env") {
3894
4045
  }
3895
4046
 
3896
4047
  // src/transcript.ts
3897
- import { createWriteStream, readFileSync as readFileSync6 } from "fs";
4048
+ import { createWriteStream, readFileSync as readFileSync7 } from "fs";
3898
4049
  function recordFromLoopEvent(ev, extra) {
3899
4050
  const rec = {
3900
4051
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3945,7 +4096,7 @@ function openTranscriptFile(path, meta) {
3945
4096
  return stream;
3946
4097
  }
3947
4098
  function readTranscript(path) {
3948
- const raw = readFileSync6(path, "utf8");
4099
+ const raw = readFileSync7(path, "utf8");
3949
4100
  return parseTranscript(raw);
3950
4101
  }
3951
4102
  function isPlanStateEmptyShape(s) {
@@ -4588,7 +4739,7 @@ var McpClient = class {
4588
4739
  const id = this.nextId++;
4589
4740
  const frame = { jsonrpc: "2.0", id, method, params };
4590
4741
  let abortHandler = null;
4591
- const promise = new Promise((resolve6, reject) => {
4742
+ const promise = new Promise((resolve7, reject) => {
4592
4743
  const timeout = setTimeout(() => {
4593
4744
  this.pending.delete(id);
4594
4745
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -4597,7 +4748,7 @@ var McpClient = class {
4597
4748
  );
4598
4749
  }, this.requestTimeoutMs);
4599
4750
  this.pending.set(id, {
4600
- resolve: resolve6,
4751
+ resolve: resolve7,
4601
4752
  reject,
4602
4753
  timeout
4603
4754
  });
@@ -4720,12 +4871,12 @@ var StdioTransport = class {
4720
4871
  }
4721
4872
  async send(message) {
4722
4873
  if (this.closed) throw new Error("MCP transport is closed");
4723
- return new Promise((resolve6, reject) => {
4874
+ return new Promise((resolve7, reject) => {
4724
4875
  const line = `${JSON.stringify(message)}
4725
4876
  `;
4726
4877
  this.child.stdin.write(line, "utf8", (err) => {
4727
4878
  if (err) reject(err);
4728
- else resolve6();
4879
+ else resolve7();
4729
4880
  });
4730
4881
  });
4731
4882
  }
@@ -4736,8 +4887,8 @@ var StdioTransport = class {
4736
4887
  continue;
4737
4888
  }
4738
4889
  if (this.closed) return;
4739
- const next = await new Promise((resolve6) => {
4740
- this.waiters.push(resolve6);
4890
+ const next = await new Promise((resolve7) => {
4891
+ this.waiters.push(resolve7);
4741
4892
  });
4742
4893
  if (next === null) return;
4743
4894
  yield next;
@@ -4803,8 +4954,8 @@ var SseTransport = class {
4803
4954
  constructor(opts) {
4804
4955
  this.url = opts.url;
4805
4956
  this.headers = opts.headers ?? {};
4806
- this.endpointReady = new Promise((resolve6, reject) => {
4807
- this.resolveEndpoint = resolve6;
4957
+ this.endpointReady = new Promise((resolve7, reject) => {
4958
+ this.resolveEndpoint = resolve7;
4808
4959
  this.rejectEndpoint = reject;
4809
4960
  });
4810
4961
  this.endpointReady.catch(() => void 0);
@@ -4831,8 +4982,8 @@ var SseTransport = class {
4831
4982
  continue;
4832
4983
  }
4833
4984
  if (this.closed) return;
4834
- const next = await new Promise((resolve6) => {
4835
- this.waiters.push(resolve6);
4985
+ const next = await new Promise((resolve7) => {
4986
+ this.waiters.push(resolve7);
4836
4987
  });
4837
4988
  if (next === null) return;
4838
4989
  yield next;
@@ -5031,8 +5182,8 @@ async function trySection(load) {
5031
5182
  }
5032
5183
 
5033
5184
  // src/code/edit-blocks.ts
5034
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
5035
- import { dirname as dirname5, resolve as resolve4 } from "path";
5185
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
5186
+ import { dirname as dirname5, resolve as resolve5 } from "path";
5036
5187
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
5037
5188
  function parseEditBlocks(text) {
5038
5189
  const out = [];
@@ -5050,8 +5201,8 @@ function parseEditBlocks(text) {
5050
5201
  return out;
5051
5202
  }
5052
5203
  function applyEditBlock(block, rootDir) {
5053
- const absRoot = resolve4(rootDir);
5054
- const absTarget = resolve4(absRoot, block.path);
5204
+ const absRoot = resolve5(rootDir);
5205
+ const absTarget = resolve5(absRoot, block.path);
5055
5206
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
5056
5207
  return {
5057
5208
  path: block.path,
@@ -5060,7 +5211,7 @@ function applyEditBlock(block, rootDir) {
5060
5211
  };
5061
5212
  }
5062
5213
  const searchEmpty = block.search.length === 0;
5063
- const exists = existsSync4(absTarget);
5214
+ const exists = existsSync6(absTarget);
5064
5215
  try {
5065
5216
  if (!exists) {
5066
5217
  if (!searchEmpty) {
@@ -5074,7 +5225,7 @@ function applyEditBlock(block, rootDir) {
5074
5225
  writeFileSync3(absTarget, block.replace, "utf8");
5075
5226
  return { path: block.path, status: "created" };
5076
5227
  }
5077
- const content = readFileSync7(absTarget, "utf8");
5228
+ const content = readFileSync8(absTarget, "utf8");
5078
5229
  if (searchEmpty) {
5079
5230
  return {
5080
5231
  path: block.path,
@@ -5101,19 +5252,19 @@ function applyEditBlocks(blocks, rootDir) {
5101
5252
  return blocks.map((b) => applyEditBlock(b, rootDir));
5102
5253
  }
5103
5254
  function snapshotBeforeEdits(blocks, rootDir) {
5104
- const absRoot = resolve4(rootDir);
5255
+ const absRoot = resolve5(rootDir);
5105
5256
  const seen = /* @__PURE__ */ new Set();
5106
5257
  const snapshots = [];
5107
5258
  for (const b of blocks) {
5108
5259
  if (seen.has(b.path)) continue;
5109
5260
  seen.add(b.path);
5110
- const abs = resolve4(absRoot, b.path);
5111
- if (!existsSync4(abs)) {
5261
+ const abs = resolve5(absRoot, b.path);
5262
+ if (!existsSync6(abs)) {
5112
5263
  snapshots.push({ path: b.path, prevContent: null });
5113
5264
  continue;
5114
5265
  }
5115
5266
  try {
5116
- snapshots.push({ path: b.path, prevContent: readFileSync7(abs, "utf8") });
5267
+ snapshots.push({ path: b.path, prevContent: readFileSync8(abs, "utf8") });
5117
5268
  } catch {
5118
5269
  snapshots.push({ path: b.path, prevContent: null });
5119
5270
  }
@@ -5121,9 +5272,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
5121
5272
  return snapshots;
5122
5273
  }
5123
5274
  function restoreSnapshots(snapshots, rootDir) {
5124
- const absRoot = resolve4(rootDir);
5275
+ const absRoot = resolve5(rootDir);
5125
5276
  return snapshots.map((snap) => {
5126
- const abs = resolve4(absRoot, snap.path);
5277
+ const abs = resolve5(absRoot, snap.path);
5127
5278
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
5128
5279
  return {
5129
5280
  path: snap.path,
@@ -5133,7 +5284,7 @@ function restoreSnapshots(snapshots, rootDir) {
5133
5284
  }
5134
5285
  try {
5135
5286
  if (snap.prevContent === null) {
5136
- if (existsSync4(abs)) unlinkSync2(abs);
5287
+ if (existsSync6(abs)) unlinkSync2(abs);
5137
5288
  return {
5138
5289
  path: snap.path,
5139
5290
  status: "applied",
@@ -5156,7 +5307,7 @@ function sep() {
5156
5307
  }
5157
5308
 
5158
5309
  // src/version.ts
5159
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
5310
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
5160
5311
  import { homedir as homedir4 } from "os";
5161
5312
  import { dirname as dirname6, join as join6 } from "path";
5162
5313
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5168,8 +5319,8 @@ function readPackageVersion() {
5168
5319
  let dir = dirname6(fileURLToPath2(import.meta.url));
5169
5320
  for (let i = 0; i < 6; i++) {
5170
5321
  const p = join6(dir, "package.json");
5171
- if (existsSync5(p)) {
5172
- const pkg = JSON.parse(readFileSync8(p, "utf8"));
5322
+ if (existsSync7(p)) {
5323
+ const pkg = JSON.parse(readFileSync9(p, "utf8"));
5173
5324
  if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
5174
5325
  return pkg.version;
5175
5326
  }
@@ -5188,7 +5339,7 @@ function cachePath(homeDirOverride) {
5188
5339
  }
5189
5340
  function readCache(homeDirOverride) {
5190
5341
  try {
5191
- const raw = readFileSync8(cachePath(homeDirOverride), "utf8");
5342
+ const raw = readFileSync9(cachePath(homeDirOverride), "utf8");
5192
5343
  const parsed = JSON.parse(raw);
5193
5344
  if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
5194
5345
  return parsed;
@@ -5257,7 +5408,7 @@ function isNpxInstall() {
5257
5408
  }
5258
5409
 
5259
5410
  // src/usage.ts
5260
- import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
5411
+ import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
5261
5412
  import { homedir as homedir5 } from "os";
5262
5413
  import { dirname as dirname7, join as join7 } from "path";
5263
5414
  function defaultUsageLogPath(homeDirOverride) {
@@ -5285,10 +5436,10 @@ function appendUsage(input) {
5285
5436
  return record;
5286
5437
  }
5287
5438
  function readUsageLog(path = defaultUsageLogPath()) {
5288
- if (!existsSync6(path)) return [];
5439
+ if (!existsSync8(path)) return [];
5289
5440
  let raw;
5290
5441
  try {
5291
- raw = readFileSync9(path, "utf8");
5442
+ raw = readFileSync10(path, "utf8");
5292
5443
  } catch {
5293
5444
  return [];
5294
5445
  }
@@ -5370,9 +5521,9 @@ function aggregateUsage(records, opts = {}) {
5370
5521
  };
5371
5522
  }
5372
5523
  function formatLogSize(path = defaultUsageLogPath()) {
5373
- if (!existsSync6(path)) return "";
5524
+ if (!existsSync8(path)) return "";
5374
5525
  try {
5375
- const s = statSync3(path);
5526
+ const s = statSync4(path);
5376
5527
  const bytes = s.size;
5377
5528
  if (bytes < 1024) return `${bytes} B`;
5378
5529
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -5383,7 +5534,7 @@ function formatLogSize(path = defaultUsageLogPath()) {
5383
5534
  }
5384
5535
 
5385
5536
  // src/cli/commands/chat.tsx
5386
- import { existsSync as existsSync8, statSync as statSync5 } from "fs";
5537
+ import { existsSync as existsSync10, statSync as statSync6 } from "fs";
5387
5538
  import { render } from "ink";
5388
5539
  import React15, { useState as useState7 } from "react";
5389
5540
 
@@ -5481,8 +5632,8 @@ function PlanStateBlock({ planState }) {
5481
5632
  }
5482
5633
 
5483
5634
  // src/cli/ui/markdown.tsx
5484
- import { readFileSync as readFileSync10, statSync as statSync4 } from "fs";
5485
- import { isAbsolute as isAbsolute3, join as join8 } from "path";
5635
+ import { readFileSync as readFileSync11, statSync as statSync5 } from "fs";
5636
+ import { isAbsolute as isAbsolute4, join as join8 } from "path";
5486
5637
  import { Box as Box2, Text as Text2 } from "ink";
5487
5638
  import React2 from "react";
5488
5639
  var SUPERSCRIPT = {
@@ -5560,10 +5711,10 @@ function parseCitationUrl(url) {
5560
5711
  function validateCitation(url, projectRoot) {
5561
5712
  const parts = parseCitationUrl(url);
5562
5713
  if (!parts || !parts.path) return { ok: false, reason: "empty path" };
5563
- const fullPath = isAbsolute3(parts.path) ? parts.path : join8(projectRoot, parts.path);
5714
+ const fullPath = isAbsolute4(parts.path) ? parts.path : join8(projectRoot, parts.path);
5564
5715
  let stat;
5565
5716
  try {
5566
- stat = statSync4(fullPath);
5717
+ stat = statSync5(fullPath);
5567
5718
  } catch {
5568
5719
  return { ok: false, reason: "file not found" };
5569
5720
  }
@@ -5571,7 +5722,7 @@ function validateCitation(url, projectRoot) {
5571
5722
  if (parts.startLine === void 0) return { ok: true };
5572
5723
  let lineCount;
5573
5724
  try {
5574
- lineCount = readFileSync10(fullPath, "utf8").split("\n").length;
5725
+ lineCount = readFileSync11(fullPath, "utf8").split("\n").length;
5575
5726
  } catch {
5576
5727
  return { ok: false, reason: "unreadable" };
5577
5728
  }
@@ -6751,7 +6902,7 @@ function formatTokens(n) {
6751
6902
  import { spawnSync } from "child_process";
6752
6903
 
6753
6904
  // src/cli/commands/stats.ts
6754
- import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
6905
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
6755
6906
  function statsCommand(opts) {
6756
6907
  if (opts.transcript) {
6757
6908
  transcriptSummary(opts.transcript);
@@ -6760,11 +6911,11 @@ function statsCommand(opts) {
6760
6911
  dashboard(opts);
6761
6912
  }
6762
6913
  function transcriptSummary(path) {
6763
- if (!existsSync7(path)) {
6914
+ if (!existsSync9(path)) {
6764
6915
  console.error(`no such transcript: ${path}`);
6765
6916
  process.exit(1);
6766
6917
  }
6767
- const lines = readFileSync11(path, "utf8").split(/\r?\n/).filter(Boolean);
6918
+ const lines = readFileSync12(path, "utf8").split(/\r?\n/).filter(Boolean);
6768
6919
  let assistantTurns = 0;
6769
6920
  let toolCalls = 0;
6770
6921
  let lastTurn = 0;
@@ -8298,8 +8449,26 @@ function App({
8298
8449
  });
8299
8450
  };
8300
8451
  const timer = PLAIN_UI ? null : setInterval(flush, FLUSH_INTERVAL_MS);
8452
+ let modelInput = text;
8453
+ if (codeMode?.rootDir) {
8454
+ const expanded = expandAtMentions(text, codeMode.rootDir);
8455
+ if (expanded.expansions.length > 0) {
8456
+ modelInput = expanded.text;
8457
+ const inlined = expanded.expansions.filter((ex) => ex.ok).map((ex) => `${ex.path} (${(ex.bytes ?? 0).toLocaleString()} bytes)`);
8458
+ const skipped = expanded.expansions.filter((ex) => !ex.ok).map((ex) => `${ex.path} (${ex.skip})`);
8459
+ const parts = [];
8460
+ if (inlined.length > 0) parts.push(`inlined ${inlined.join(", ")}`);
8461
+ if (skipped.length > 0) parts.push(`skipped ${skipped.join(", ")}`);
8462
+ if (parts.length > 0) {
8463
+ setHistorical((prev) => [
8464
+ ...prev,
8465
+ { id: `at-${Date.now()}`, role: "info", text: `\u25B8 @mentions: ${parts.join("; ")}` }
8466
+ ]);
8467
+ }
8468
+ }
8469
+ }
8301
8470
  try {
8302
- for await (const ev of loop.step(text)) {
8471
+ for await (const ev of loop.step(modelInput)) {
8303
8472
  writeTranscript(ev);
8304
8473
  if (ev.role !== "status") {
8305
8474
  setStatusLine((cur) => cur ? null : cur);
@@ -9014,7 +9183,7 @@ async function chatCommand(opts) {
9014
9183
  const prior = loadSessionMessages(opts.session);
9015
9184
  if (prior.length > 0) {
9016
9185
  const p = sessionPath(opts.session);
9017
- const mtime = existsSync8(p) ? statSync5(p).mtime : /* @__PURE__ */ new Date();
9186
+ const mtime = existsSync10(p) ? statSync6(p).mtime : /* @__PURE__ */ new Date();
9018
9187
  sessionPreview = { messageCount: prior.length, lastActive: mtime };
9019
9188
  }
9020
9189
  } else if (opts.session && opts.forceNew) {
@@ -9045,10 +9214,10 @@ async function chatCommand(opts) {
9045
9214
  }
9046
9215
 
9047
9216
  // src/cli/commands/code.tsx
9048
- import { basename, resolve as resolve5 } from "path";
9217
+ import { basename, resolve as resolve6 } from "path";
9049
9218
  async function codeCommand(opts = {}) {
9050
9219
  const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-75XLIUTO.js");
9051
- const rootDir = resolve5(opts.dir ?? process.cwd());
9220
+ const rootDir = resolve6(opts.dir ?? process.cwd());
9052
9221
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
9053
9222
  const tools = new ToolRegistry();
9054
9223
  registerFilesystemTools(tools, { rootDir });
@@ -10067,13 +10236,13 @@ function planUpdate(input) {
10067
10236
  };
10068
10237
  }
10069
10238
  function defaultSpawn(argv) {
10070
- return new Promise((resolve6, reject) => {
10239
+ return new Promise((resolve7, reject) => {
10071
10240
  const child = spawn4(argv[0], argv.slice(1), {
10072
10241
  stdio: "inherit",
10073
10242
  shell: process.platform === "win32"
10074
10243
  });
10075
10244
  child.once("error", reject);
10076
- child.once("exit", (code) => resolve6(code ?? 1));
10245
+ child.once("exit", (code) => resolve7(code ?? 1));
10077
10246
  });
10078
10247
  }
10079
10248
  async function updateCommand(opts = {}) {