reasonix 0.5.0 → 0.5.2

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
@@ -696,6 +696,170 @@ async function runHooks(opts) {
696
696
  return { event, outcomes, blocked };
697
697
  }
698
698
 
699
+ // src/tokenizer.ts
700
+ import { readFileSync as readFileSync3 } from "fs";
701
+ import { createRequire } from "module";
702
+ import { dirname as dirname2, join as join3 } from "path";
703
+ import { fileURLToPath } from "url";
704
+ import { gunzipSync } from "zlib";
705
+ function buildByteToChar() {
706
+ const result = new Array(256);
707
+ const bs = [];
708
+ for (let b = 33; b <= 126; b++) bs.push(b);
709
+ for (let b = 161; b <= 172; b++) bs.push(b);
710
+ for (let b = 174; b <= 255; b++) bs.push(b);
711
+ const cs = bs.slice();
712
+ let n = 0;
713
+ for (let b = 0; b < 256; b++) {
714
+ if (!bs.includes(b)) {
715
+ bs.push(b);
716
+ cs.push(256 + n);
717
+ n++;
718
+ }
719
+ }
720
+ for (let i = 0; i < bs.length; i++) {
721
+ result[bs[i]] = String.fromCodePoint(cs[i]);
722
+ }
723
+ return result;
724
+ }
725
+ var cached = null;
726
+ function resolveDataPath() {
727
+ if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
728
+ try {
729
+ const here = dirname2(fileURLToPath(import.meta.url));
730
+ return join3(here, "..", "data", "deepseek-tokenizer.json.gz");
731
+ } catch {
732
+ const req = createRequire(import.meta.url);
733
+ return join3(
734
+ dirname2(req.resolve("reasonix/package.json")),
735
+ "data",
736
+ "deepseek-tokenizer.json.gz"
737
+ );
738
+ }
739
+ }
740
+ function loadTokenizer() {
741
+ if (cached) return cached;
742
+ const buf = readFileSync3(resolveDataPath());
743
+ const json = gunzipSync(buf).toString("utf8");
744
+ const data = JSON.parse(json);
745
+ const mergeRank = /* @__PURE__ */ new Map();
746
+ for (let i = 0; i < data.model.merges.length; i++) {
747
+ mergeRank.set(data.model.merges[i], i);
748
+ }
749
+ const splitRegexes = [];
750
+ for (const p of data.pre_tokenizer.pretokenizers) {
751
+ if (p.type === "Split") {
752
+ splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
753
+ }
754
+ }
755
+ const addedMap = /* @__PURE__ */ new Map();
756
+ const addedContents = [];
757
+ for (const t of data.added_tokens) {
758
+ if (!t.special) {
759
+ addedMap.set(t.content, t.id);
760
+ addedContents.push(t.content);
761
+ }
762
+ }
763
+ addedContents.sort((a, b) => b.length - a.length);
764
+ const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
765
+ cached = {
766
+ vocab: data.model.vocab,
767
+ mergeRank,
768
+ splitRegexes,
769
+ byteToChar: buildByteToChar(),
770
+ addedPattern,
771
+ addedMap
772
+ };
773
+ return cached;
774
+ }
775
+ function escapeRegex(s) {
776
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
777
+ }
778
+ function applySplit(chunks, re) {
779
+ const out = [];
780
+ for (const chunk of chunks) {
781
+ if (!chunk) continue;
782
+ re.lastIndex = 0;
783
+ let last = 0;
784
+ for (const m of chunk.matchAll(re)) {
785
+ const idx = m.index ?? 0;
786
+ if (idx > last) out.push(chunk.slice(last, idx));
787
+ if (m[0].length > 0) out.push(m[0]);
788
+ last = idx + m[0].length;
789
+ }
790
+ if (last < chunk.length) out.push(chunk.slice(last));
791
+ }
792
+ return out;
793
+ }
794
+ function byteLevelEncode(s, byteToChar) {
795
+ const bytes = new TextEncoder().encode(s);
796
+ let out = "";
797
+ for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
798
+ return out;
799
+ }
800
+ function bpeEncode(piece, mergeRank) {
801
+ if (piece.length <= 1) return piece ? [piece] : [];
802
+ let word = Array.from(piece);
803
+ while (true) {
804
+ let bestIdx = -1;
805
+ let bestRank = Number.POSITIVE_INFINITY;
806
+ for (let i = 0; i < word.length - 1; i++) {
807
+ const pair = `${word[i]} ${word[i + 1]}`;
808
+ const rank = mergeRank.get(pair);
809
+ if (rank !== void 0 && rank < bestRank) {
810
+ bestRank = rank;
811
+ bestIdx = i;
812
+ if (rank === 0) break;
813
+ }
814
+ }
815
+ if (bestIdx < 0) break;
816
+ word = [
817
+ ...word.slice(0, bestIdx),
818
+ word[bestIdx] + word[bestIdx + 1],
819
+ ...word.slice(bestIdx + 2)
820
+ ];
821
+ if (word.length === 1) break;
822
+ }
823
+ return word;
824
+ }
825
+ function encode(text) {
826
+ if (!text) return [];
827
+ const t = loadTokenizer();
828
+ const ids = [];
829
+ const process2 = (segment) => {
830
+ if (!segment) return;
831
+ let chunks = [segment];
832
+ for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
833
+ for (const chunk of chunks) {
834
+ if (!chunk) continue;
835
+ const byteLevel = byteLevelEncode(chunk, t.byteToChar);
836
+ const pieces = bpeEncode(byteLevel, t.mergeRank);
837
+ for (const p of pieces) {
838
+ const id = t.vocab[p];
839
+ if (id !== void 0) ids.push(id);
840
+ }
841
+ }
842
+ };
843
+ if (t.addedPattern) {
844
+ t.addedPattern.lastIndex = 0;
845
+ let last = 0;
846
+ for (const m of text.matchAll(t.addedPattern)) {
847
+ const idx = m.index ?? 0;
848
+ if (idx > last) process2(text.slice(last, idx));
849
+ const id = t.addedMap.get(m[0]);
850
+ if (id !== void 0) ids.push(id);
851
+ last = idx + m[0].length;
852
+ }
853
+ if (last < text.length) process2(text.slice(last));
854
+ } else {
855
+ process2(text);
856
+ }
857
+ return ids;
858
+ }
859
+ function countTokens(text) {
860
+ return encode(text).length;
861
+ }
862
+
699
863
  // src/repair/flatten.ts
700
864
  function analyzeSchema(schema) {
701
865
  if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
@@ -846,7 +1010,14 @@ var ToolRegistry = class {
846
1010
  try {
847
1011
  const result = await tool.fn(args, { signal: opts.signal });
848
1012
  const str = typeof result === "string" ? result : JSON.stringify(result);
849
- return opts.maxResultChars ? truncateForModel(str, opts.maxResultChars) : str;
1013
+ let clipped = str;
1014
+ if (opts.maxResultTokens !== void 0) {
1015
+ clipped = truncateForModelByTokens(clipped, opts.maxResultTokens);
1016
+ }
1017
+ if (opts.maxResultChars !== void 0) {
1018
+ clipped = truncateForModel(clipped, opts.maxResultChars);
1019
+ }
1020
+ return clipped;
850
1021
  } catch (err) {
851
1022
  const e = err;
852
1023
  if (typeof e.toToolResult === "function") {
@@ -880,6 +1051,7 @@ function hasDotKey(obj) {
880
1051
 
881
1052
  // src/mcp/registry.ts
882
1053
  var DEFAULT_MAX_RESULT_CHARS = 32e3;
1054
+ var DEFAULT_MAX_RESULT_TOKENS = 8e3;
883
1055
  async function bridgeMcpTools(client, opts = {}) {
884
1056
  const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
885
1057
  const prefix = opts.namePrefix ?? "";
@@ -936,6 +1108,61 @@ function truncateForModel(s, maxChars) {
936
1108
 
937
1109
  ${tail}`;
938
1110
  }
1111
+ function truncateForModelByTokens(s, maxTokens) {
1112
+ if (maxTokens <= 0) return "";
1113
+ if (s.length <= maxTokens) return s;
1114
+ if (s.length <= maxTokens * 4) {
1115
+ const tokens = countTokens(s);
1116
+ if (tokens <= maxTokens) return s;
1117
+ }
1118
+ const markerOverhead = 48;
1119
+ const contentBudget = Math.max(0, maxTokens - markerOverhead);
1120
+ const tailBudget = Math.min(256, Math.floor(contentBudget * 0.1));
1121
+ const headBudget = Math.max(0, contentBudget - tailBudget);
1122
+ const head = sizePrefixToTokens(s, headBudget);
1123
+ const tail = sizeSuffixToTokens(s, tailBudget);
1124
+ const droppedChars = s.length - head.length - tail.length;
1125
+ const headTokens = head ? countTokens(head) : 0;
1126
+ const tailTokens = tail ? countTokens(tail) : 0;
1127
+ const sampleChars = head.length + tail.length;
1128
+ const sampleTokens = headTokens + tailTokens;
1129
+ const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
1130
+ const estTotalTokens = Math.ceil(s.length * ratio);
1131
+ const droppedTokens = Math.max(0, estTotalTokens - sampleTokens);
1132
+ return `${head}
1133
+
1134
+ [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)\u2026]
1135
+
1136
+ ${tail}`;
1137
+ }
1138
+ function sizePrefixToTokens(s, budget) {
1139
+ if (budget <= 0 || s.length === 0) return "";
1140
+ let size = Math.min(s.length, budget * 4);
1141
+ for (let iter = 0; iter < 6; iter++) {
1142
+ if (size <= 0) return "";
1143
+ const slice = s.slice(0, size);
1144
+ const count = countTokens(slice);
1145
+ if (count <= budget) return slice;
1146
+ const next = Math.floor(size * (budget / count) * 0.95);
1147
+ if (next >= size) return s.slice(0, Math.max(0, size - 1));
1148
+ size = next;
1149
+ }
1150
+ return s.slice(0, Math.max(0, size));
1151
+ }
1152
+ function sizeSuffixToTokens(s, budget) {
1153
+ if (budget <= 0 || s.length === 0) return "";
1154
+ let size = Math.min(s.length, budget * 4);
1155
+ for (let iter = 0; iter < 6; iter++) {
1156
+ if (size <= 0) return "";
1157
+ const slice = s.slice(-size);
1158
+ const count = countTokens(slice);
1159
+ if (count <= budget) return slice;
1160
+ const next = Math.floor(size * (budget / count) * 0.95);
1161
+ if (next >= size) return s.slice(-Math.max(0, size - 1));
1162
+ size = next;
1163
+ }
1164
+ return s.slice(-Math.max(0, size));
1165
+ }
939
1166
  function blockToString(block) {
940
1167
  if (block.type === "text") return block.text;
941
1168
  if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
@@ -1321,19 +1548,19 @@ import {
1321
1548
  chmodSync as chmodSync2,
1322
1549
  existsSync as existsSync2,
1323
1550
  mkdirSync as mkdirSync2,
1324
- readFileSync as readFileSync3,
1551
+ readFileSync as readFileSync4,
1325
1552
  readdirSync,
1326
1553
  statSync,
1327
1554
  unlinkSync,
1328
1555
  writeFileSync as writeFileSync2
1329
1556
  } from "fs";
1330
1557
  import { homedir as homedir3 } from "os";
1331
- import { dirname as dirname2, join as join3 } from "path";
1558
+ import { dirname as dirname3, join as join4 } from "path";
1332
1559
  function sessionsDir() {
1333
- return join3(homedir3(), ".reasonix", "sessions");
1560
+ return join4(homedir3(), ".reasonix", "sessions");
1334
1561
  }
1335
1562
  function sessionPath(name) {
1336
- return join3(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1563
+ return join4(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1337
1564
  }
1338
1565
  function sanitizeName(name) {
1339
1566
  const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
@@ -1343,7 +1570,7 @@ function loadSessionMessages(name) {
1343
1570
  const path = sessionPath(name);
1344
1571
  if (!existsSync2(path)) return [];
1345
1572
  try {
1346
- const raw = readFileSync3(path, "utf8");
1573
+ const raw = readFileSync4(path, "utf8");
1347
1574
  const out = [];
1348
1575
  for (const line of raw.split(/\r?\n/)) {
1349
1576
  const trimmed = line.trim();
@@ -1361,7 +1588,7 @@ function loadSessionMessages(name) {
1361
1588
  }
1362
1589
  function appendSessionMessage(name, message) {
1363
1590
  const path = sessionPath(name);
1364
- mkdirSync2(dirname2(path), { recursive: true });
1591
+ mkdirSync2(dirname3(path), { recursive: true });
1365
1592
  appendFileSync(path, `${JSON.stringify(message)}
1366
1593
  `, "utf8");
1367
1594
  try {
@@ -1375,7 +1602,7 @@ function listSessions() {
1375
1602
  try {
1376
1603
  const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1377
1604
  return files.map((file) => {
1378
- const path = join3(dir, file);
1605
+ const path = join4(dir, file);
1379
1606
  const stat = statSync(path);
1380
1607
  const name = file.replace(/\.jsonl$/, "");
1381
1608
  const messageCount = countLines(path);
@@ -1396,7 +1623,7 @@ function deleteSession(name) {
1396
1623
  }
1397
1624
  function rewriteSession(name, messages) {
1398
1625
  const path = sessionPath(name);
1399
- mkdirSync2(dirname2(path), { recursive: true });
1626
+ mkdirSync2(dirname3(path), { recursive: true });
1400
1627
  const body = messages.map((m) => JSON.stringify(m)).join("\n");
1401
1628
  writeFileSync2(path, body ? `${body}
1402
1629
  ` : "", "utf8");
@@ -1407,7 +1634,7 @@ function rewriteSession(name, messages) {
1407
1634
  }
1408
1635
  function countLines(path) {
1409
1636
  try {
1410
- const raw = readFileSync3(path, "utf8");
1637
+ const raw = readFileSync4(path, "utf8");
1411
1638
  return raw.split(/\r?\n/).filter((l) => l.trim()).length;
1412
1639
  } catch {
1413
1640
  return 0;
@@ -2054,7 +2281,7 @@ ${reason}`;
2054
2281
  } else {
2055
2282
  result = await this.tools.dispatch(name, args, {
2056
2283
  signal,
2057
- maxResultChars: DEFAULT_MAX_RESULT_CHARS
2284
+ maxResultTokens: DEFAULT_MAX_RESULT_TOKENS
2058
2285
  });
2059
2286
  const postReport = await runHooks({
2060
2287
  hooks: this.hooks,
@@ -3162,6 +3389,50 @@ function tokenizeCommand(cmd) {
3162
3389
  if (cur.length > 0) out.push(cur);
3163
3390
  return out;
3164
3391
  }
3392
+ function detectShellOperator(cmd) {
3393
+ const opPrefix = /^(?:2>&1|&>|\|{1,2}|&{1,2}|2>{1,2}|>{1,2}|<{1,2})/;
3394
+ let cur = "";
3395
+ let curQuoted = false;
3396
+ let quote = null;
3397
+ const check = () => {
3398
+ if (cur.length === 0 && !curQuoted) return null;
3399
+ if (!curQuoted) {
3400
+ const m = opPrefix.exec(cur);
3401
+ if (m) return m[0] ?? null;
3402
+ }
3403
+ return null;
3404
+ };
3405
+ for (let i = 0; i < cmd.length; i++) {
3406
+ const ch = cmd[i];
3407
+ if (quote) {
3408
+ if (ch === quote) {
3409
+ quote = null;
3410
+ } else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
3411
+ cur += cmd[++i];
3412
+ curQuoted = true;
3413
+ } else {
3414
+ cur += ch;
3415
+ curQuoted = true;
3416
+ }
3417
+ continue;
3418
+ }
3419
+ if (ch === '"' || ch === "'") {
3420
+ quote = ch;
3421
+ curQuoted = true;
3422
+ continue;
3423
+ }
3424
+ if (ch === " " || ch === " ") {
3425
+ const op = check();
3426
+ if (op) return op;
3427
+ cur = "";
3428
+ curQuoted = false;
3429
+ continue;
3430
+ }
3431
+ cur += ch;
3432
+ }
3433
+ if (quote) return null;
3434
+ return check();
3435
+ }
3165
3436
  function isAllowed(cmd, extra = []) {
3166
3437
  const normalized = cmd.trim().replace(/\s+/g, " ");
3167
3438
  const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
@@ -3174,6 +3445,12 @@ function isAllowed(cmd, extra = []) {
3174
3445
  async function runCommand(cmd, opts) {
3175
3446
  const argv = tokenizeCommand(cmd);
3176
3447
  if (argv.length === 0) throw new Error("run_command: empty command");
3448
+ const operator = detectShellOperator(cmd);
3449
+ if (operator !== null) {
3450
+ throw new Error(
3451
+ `run_command: shell operator "${operator}" is not supported \u2014 this tool spawns one process, no shell expansion. Split into separate run_command calls and combine the output in your reasoning (e.g. instead of \`grep foo *.ts | wc -l\`, call \`grep -c foo *.ts\` or two separate commands). To pass "${operator}" as a literal argument, wrap it in quotes.`
3452
+ );
3453
+ }
3177
3454
  const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
3178
3455
  const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
3179
3456
  const spawnOpts = {
@@ -3351,7 +3628,7 @@ function registerShellTools(registry, opts) {
3351
3628
  properties: {
3352
3629
  command: {
3353
3630
  type: "string",
3354
- description: "Full command line. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
3631
+ description: 'Full command line. Tokenized with POSIX-ish quoting; no shell expansion. Pipes (`|`), redirects (`>`, `<`, `2>`), and `&&`/`||` chaining are rejected with an error \u2014 split into separate calls instead. To pass an operator character as a literal argument (e.g. a regex), wrap it in quotes: `grep "a|b" file.txt`.'
3355
3632
  },
3356
3633
  timeoutSec: {
3357
3634
  type: "integer",
@@ -3570,12 +3847,12 @@ ${i + 1}. ${r.title}`);
3570
3847
  }
3571
3848
 
3572
3849
  // src/env.ts
3573
- import { readFileSync as readFileSync4 } from "fs";
3850
+ import { readFileSync as readFileSync5 } from "fs";
3574
3851
  import { resolve as resolve3 } from "path";
3575
3852
  function loadDotenv(path = ".env") {
3576
3853
  let raw;
3577
3854
  try {
3578
- raw = readFileSync4(resolve3(process.cwd(), path), "utf8");
3855
+ raw = readFileSync5(resolve3(process.cwd(), path), "utf8");
3579
3856
  } catch {
3580
3857
  return;
3581
3858
  }
@@ -3594,7 +3871,7 @@ function loadDotenv(path = ".env") {
3594
3871
  }
3595
3872
 
3596
3873
  // src/transcript.ts
3597
- import { createWriteStream, readFileSync as readFileSync5 } from "fs";
3874
+ import { createWriteStream, readFileSync as readFileSync6 } from "fs";
3598
3875
  function recordFromLoopEvent(ev, extra) {
3599
3876
  const rec = {
3600
3877
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3645,7 +3922,7 @@ function openTranscriptFile(path, meta) {
3645
3922
  return stream;
3646
3923
  }
3647
3924
  function readTranscript(path) {
3648
- const raw = readFileSync5(path, "utf8");
3925
+ const raw = readFileSync6(path, "utf8");
3649
3926
  return parseTranscript(raw);
3650
3927
  }
3651
3928
  function isPlanStateEmptyShape(s) {
@@ -4731,8 +5008,8 @@ async function trySection(load) {
4731
5008
  }
4732
5009
 
4733
5010
  // src/code/edit-blocks.ts
4734
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
4735
- import { dirname as dirname4, resolve as resolve4 } from "path";
5011
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
5012
+ import { dirname as dirname5, resolve as resolve4 } from "path";
4736
5013
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
4737
5014
  function parseEditBlocks(text) {
4738
5015
  const out = [];
@@ -4770,11 +5047,11 @@ function applyEditBlock(block, rootDir) {
4770
5047
  message: "file does not exist; to create it, use an empty SEARCH block"
4771
5048
  };
4772
5049
  }
4773
- mkdirSync3(dirname4(absTarget), { recursive: true });
5050
+ mkdirSync3(dirname5(absTarget), { recursive: true });
4774
5051
  writeFileSync3(absTarget, block.replace, "utf8");
4775
5052
  return { path: block.path, status: "created" };
4776
5053
  }
4777
- const content = readFileSync6(absTarget, "utf8");
5054
+ const content = readFileSync7(absTarget, "utf8");
4778
5055
  if (searchEmpty) {
4779
5056
  return {
4780
5057
  path: block.path,
@@ -4813,7 +5090,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
4813
5090
  continue;
4814
5091
  }
4815
5092
  try {
4816
- snapshots.push({ path: b.path, prevContent: readFileSync6(abs, "utf8") });
5093
+ snapshots.push({ path: b.path, prevContent: readFileSync7(abs, "utf8") });
4817
5094
  } catch {
4818
5095
  snapshots.push({ path: b.path, prevContent: null });
4819
5096
  }
@@ -4856,25 +5133,25 @@ function sep() {
4856
5133
  }
4857
5134
 
4858
5135
  // src/version.ts
4859
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
5136
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
4860
5137
  import { homedir as homedir4 } from "os";
4861
- import { dirname as dirname5, join as join5 } from "path";
4862
- import { fileURLToPath } from "url";
5138
+ import { dirname as dirname6, join as join6 } from "path";
5139
+ import { fileURLToPath as fileURLToPath2 } from "url";
4863
5140
  var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
4864
5141
  var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4865
5142
  var LATEST_FETCH_TIMEOUT_MS = 2e3;
4866
5143
  function readPackageVersion() {
4867
5144
  try {
4868
- let dir = dirname5(fileURLToPath(import.meta.url));
5145
+ let dir = dirname6(fileURLToPath2(import.meta.url));
4869
5146
  for (let i = 0; i < 6; i++) {
4870
- const p = join5(dir, "package.json");
5147
+ const p = join6(dir, "package.json");
4871
5148
  if (existsSync5(p)) {
4872
- const pkg = JSON.parse(readFileSync7(p, "utf8"));
5149
+ const pkg = JSON.parse(readFileSync8(p, "utf8"));
4873
5150
  if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
4874
5151
  return pkg.version;
4875
5152
  }
4876
5153
  }
4877
- const parent = dirname5(dir);
5154
+ const parent = dirname6(dir);
4878
5155
  if (parent === dir) break;
4879
5156
  dir = parent;
4880
5157
  }
@@ -4884,11 +5161,11 @@ function readPackageVersion() {
4884
5161
  }
4885
5162
  var VERSION = readPackageVersion();
4886
5163
  function cachePath(homeDirOverride) {
4887
- return join5(homeDirOverride ?? homedir4(), ".reasonix", "version-cache.json");
5164
+ return join6(homeDirOverride ?? homedir4(), ".reasonix", "version-cache.json");
4888
5165
  }
4889
5166
  function readCache(homeDirOverride) {
4890
5167
  try {
4891
- const raw = readFileSync7(cachePath(homeDirOverride), "utf8");
5168
+ const raw = readFileSync8(cachePath(homeDirOverride), "utf8");
4892
5169
  const parsed = JSON.parse(raw);
4893
5170
  if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
4894
5171
  return parsed;
@@ -4900,7 +5177,7 @@ function readCache(homeDirOverride) {
4900
5177
  function writeCache(entry, homeDirOverride) {
4901
5178
  try {
4902
5179
  const p = cachePath(homeDirOverride);
4903
- mkdirSync4(dirname5(p), { recursive: true });
5180
+ mkdirSync4(dirname6(p), { recursive: true });
4904
5181
  writeFileSync4(p, JSON.stringify(entry), "utf8");
4905
5182
  } catch {
4906
5183
  }
@@ -4957,11 +5234,11 @@ function isNpxInstall() {
4957
5234
  }
4958
5235
 
4959
5236
  // src/usage.ts
4960
- import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
5237
+ import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
4961
5238
  import { homedir as homedir5 } from "os";
4962
- import { dirname as dirname6, join as join6 } from "path";
5239
+ import { dirname as dirname7, join as join7 } from "path";
4963
5240
  function defaultUsageLogPath(homeDirOverride) {
4964
- return join6(homeDirOverride ?? homedir5(), ".reasonix", "usage.jsonl");
5241
+ return join7(homeDirOverride ?? homedir5(), ".reasonix", "usage.jsonl");
4965
5242
  }
4966
5243
  function appendUsage(input) {
4967
5244
  const record = {
@@ -4977,7 +5254,7 @@ function appendUsage(input) {
4977
5254
  };
4978
5255
  const path = input.path ?? defaultUsageLogPath();
4979
5256
  try {
4980
- mkdirSync5(dirname6(path), { recursive: true });
5257
+ mkdirSync5(dirname7(path), { recursive: true });
4981
5258
  appendFileSync2(path, `${JSON.stringify(record)}
4982
5259
  `, "utf8");
4983
5260
  } catch {
@@ -4988,7 +5265,7 @@ function readUsageLog(path = defaultUsageLogPath()) {
4988
5265
  if (!existsSync6(path)) return [];
4989
5266
  let raw;
4990
5267
  try {
4991
- raw = readFileSync8(path, "utf8");
5268
+ raw = readFileSync9(path, "utf8");
4992
5269
  } catch {
4993
5270
  return [];
4994
5271
  }
@@ -5181,8 +5458,8 @@ function PlanStateBlock({ planState }) {
5181
5458
  }
5182
5459
 
5183
5460
  // src/cli/ui/markdown.tsx
5184
- import { readFileSync as readFileSync9, statSync as statSync4 } from "fs";
5185
- import { isAbsolute as isAbsolute3, join as join7 } from "path";
5461
+ import { readFileSync as readFileSync10, statSync as statSync4 } from "fs";
5462
+ import { isAbsolute as isAbsolute3, join as join8 } from "path";
5186
5463
  import { Box as Box2, Text as Text2 } from "ink";
5187
5464
  import React2 from "react";
5188
5465
  var SUPERSCRIPT = {
@@ -5260,7 +5537,7 @@ function parseCitationUrl(url) {
5260
5537
  function validateCitation(url, projectRoot) {
5261
5538
  const parts = parseCitationUrl(url);
5262
5539
  if (!parts || !parts.path) return { ok: false, reason: "empty path" };
5263
- const fullPath = isAbsolute3(parts.path) ? parts.path : join7(projectRoot, parts.path);
5540
+ const fullPath = isAbsolute3(parts.path) ? parts.path : join8(projectRoot, parts.path);
5264
5541
  let stat;
5265
5542
  try {
5266
5543
  stat = statSync4(fullPath);
@@ -5271,7 +5548,7 @@ function validateCitation(url, projectRoot) {
5271
5548
  if (parts.startLine === void 0) return { ok: true };
5272
5549
  let lineCount;
5273
5550
  try {
5274
- lineCount = readFileSync9(fullPath, "utf8").split("\n").length;
5551
+ lineCount = readFileSync10(fullPath, "utf8").split("\n").length;
5275
5552
  } catch {
5276
5553
  return { ok: false, reason: "unreadable" };
5277
5554
  }
@@ -6450,170 +6727,6 @@ function formatTokens(n) {
6450
6727
  // src/cli/ui/slash.ts
6451
6728
  import { spawnSync } from "child_process";
6452
6729
 
6453
- // src/tokenizer.ts
6454
- import { readFileSync as readFileSync10 } from "fs";
6455
- import { createRequire } from "module";
6456
- import { dirname as dirname7, join as join8 } from "path";
6457
- import { fileURLToPath as fileURLToPath2 } from "url";
6458
- import { gunzipSync } from "zlib";
6459
- function buildByteToChar() {
6460
- const result = new Array(256);
6461
- const bs = [];
6462
- for (let b = 33; b <= 126; b++) bs.push(b);
6463
- for (let b = 161; b <= 172; b++) bs.push(b);
6464
- for (let b = 174; b <= 255; b++) bs.push(b);
6465
- const cs = bs.slice();
6466
- let n = 0;
6467
- for (let b = 0; b < 256; b++) {
6468
- if (!bs.includes(b)) {
6469
- bs.push(b);
6470
- cs.push(256 + n);
6471
- n++;
6472
- }
6473
- }
6474
- for (let i = 0; i < bs.length; i++) {
6475
- result[bs[i]] = String.fromCodePoint(cs[i]);
6476
- }
6477
- return result;
6478
- }
6479
- var cached = null;
6480
- function resolveDataPath() {
6481
- if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
6482
- try {
6483
- const here = dirname7(fileURLToPath2(import.meta.url));
6484
- return join8(here, "..", "data", "deepseek-tokenizer.json.gz");
6485
- } catch {
6486
- const req = createRequire(import.meta.url);
6487
- return join8(
6488
- dirname7(req.resolve("reasonix/package.json")),
6489
- "data",
6490
- "deepseek-tokenizer.json.gz"
6491
- );
6492
- }
6493
- }
6494
- function loadTokenizer() {
6495
- if (cached) return cached;
6496
- const buf = readFileSync10(resolveDataPath());
6497
- const json = gunzipSync(buf).toString("utf8");
6498
- const data = JSON.parse(json);
6499
- const mergeRank = /* @__PURE__ */ new Map();
6500
- for (let i = 0; i < data.model.merges.length; i++) {
6501
- mergeRank.set(data.model.merges[i], i);
6502
- }
6503
- const splitRegexes = [];
6504
- for (const p of data.pre_tokenizer.pretokenizers) {
6505
- if (p.type === "Split") {
6506
- splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
6507
- }
6508
- }
6509
- const addedMap = /* @__PURE__ */ new Map();
6510
- const addedContents = [];
6511
- for (const t of data.added_tokens) {
6512
- if (!t.special) {
6513
- addedMap.set(t.content, t.id);
6514
- addedContents.push(t.content);
6515
- }
6516
- }
6517
- addedContents.sort((a, b) => b.length - a.length);
6518
- const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
6519
- cached = {
6520
- vocab: data.model.vocab,
6521
- mergeRank,
6522
- splitRegexes,
6523
- byteToChar: buildByteToChar(),
6524
- addedPattern,
6525
- addedMap
6526
- };
6527
- return cached;
6528
- }
6529
- function escapeRegex(s) {
6530
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6531
- }
6532
- function applySplit(chunks, re) {
6533
- const out = [];
6534
- for (const chunk of chunks) {
6535
- if (!chunk) continue;
6536
- re.lastIndex = 0;
6537
- let last = 0;
6538
- for (const m of chunk.matchAll(re)) {
6539
- const idx = m.index ?? 0;
6540
- if (idx > last) out.push(chunk.slice(last, idx));
6541
- if (m[0].length > 0) out.push(m[0]);
6542
- last = idx + m[0].length;
6543
- }
6544
- if (last < chunk.length) out.push(chunk.slice(last));
6545
- }
6546
- return out;
6547
- }
6548
- function byteLevelEncode(s, byteToChar) {
6549
- const bytes = new TextEncoder().encode(s);
6550
- let out = "";
6551
- for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
6552
- return out;
6553
- }
6554
- function bpeEncode(piece, mergeRank) {
6555
- if (piece.length <= 1) return piece ? [piece] : [];
6556
- let word = Array.from(piece);
6557
- while (true) {
6558
- let bestIdx = -1;
6559
- let bestRank = Number.POSITIVE_INFINITY;
6560
- for (let i = 0; i < word.length - 1; i++) {
6561
- const pair = `${word[i]} ${word[i + 1]}`;
6562
- const rank = mergeRank.get(pair);
6563
- if (rank !== void 0 && rank < bestRank) {
6564
- bestRank = rank;
6565
- bestIdx = i;
6566
- if (rank === 0) break;
6567
- }
6568
- }
6569
- if (bestIdx < 0) break;
6570
- word = [
6571
- ...word.slice(0, bestIdx),
6572
- word[bestIdx] + word[bestIdx + 1],
6573
- ...word.slice(bestIdx + 2)
6574
- ];
6575
- if (word.length === 1) break;
6576
- }
6577
- return word;
6578
- }
6579
- function encode(text) {
6580
- if (!text) return [];
6581
- const t = loadTokenizer();
6582
- const ids = [];
6583
- const process2 = (segment) => {
6584
- if (!segment) return;
6585
- let chunks = [segment];
6586
- for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
6587
- for (const chunk of chunks) {
6588
- if (!chunk) continue;
6589
- const byteLevel = byteLevelEncode(chunk, t.byteToChar);
6590
- const pieces = bpeEncode(byteLevel, t.mergeRank);
6591
- for (const p of pieces) {
6592
- const id = t.vocab[p];
6593
- if (id !== void 0) ids.push(id);
6594
- }
6595
- }
6596
- };
6597
- if (t.addedPattern) {
6598
- t.addedPattern.lastIndex = 0;
6599
- let last = 0;
6600
- for (const m of text.matchAll(t.addedPattern)) {
6601
- const idx = m.index ?? 0;
6602
- if (idx > last) process2(text.slice(last, idx));
6603
- const id = t.addedMap.get(m[0]);
6604
- if (id !== void 0) ids.push(id);
6605
- last = idx + m[0].length;
6606
- }
6607
- if (last < text.length) process2(text.slice(last));
6608
- } else {
6609
- process2(text);
6610
- }
6611
- return ids;
6612
- }
6613
- function countTokens(text) {
6614
- return encode(text).length;
6615
- }
6616
-
6617
6730
  // src/cli/commands/stats.ts
6618
6731
  import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
6619
6732
  function statsCommand(opts) {