reasonix 0.4.20 → 0.4.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/index.js CHANGED
@@ -47,8 +47,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
47
47
  }
48
48
  function sleep(ms, signal) {
49
49
  if (ms <= 0) return Promise.resolve();
50
- return new Promise((resolve6, reject) => {
51
- const timer = setTimeout(resolve6, ms);
50
+ return new Promise((resolve7, reject) => {
51
+ const timer = setTimeout(resolve7, ms);
52
52
  if (signal) {
53
53
  const onAbort = () => {
54
54
  clearTimeout(timer);
@@ -1537,8 +1537,8 @@ var CacheFirstLoop = class {
1537
1537
  }
1538
1538
  );
1539
1539
  for (let k = 0; k < budget; k++) {
1540
- const sample = queue.shift() ?? await new Promise((resolve6) => {
1541
- waiter = resolve6;
1540
+ const sample = queue.shift() ?? await new Promise((resolve7) => {
1541
+ waiter = resolve7;
1542
1542
  });
1543
1543
  yield {
1544
1544
  turn: this._turn,
@@ -1953,15 +1953,171 @@ ${mem.content}
1953
1953
  // src/user-memory.ts
1954
1954
  import { createHash as createHash2 } from "crypto";
1955
1955
  import {
1956
- existsSync as existsSync3,
1956
+ existsSync as existsSync4,
1957
1957
  mkdirSync as mkdirSync2,
1958
- readFileSync as readFileSync3,
1959
- readdirSync as readdirSync2,
1958
+ readFileSync as readFileSync4,
1959
+ readdirSync as readdirSync3,
1960
1960
  unlinkSync as unlinkSync2,
1961
1961
  writeFileSync as writeFileSync2
1962
1962
  } from "fs";
1963
+ import { homedir as homedir3 } from "os";
1964
+ import { join as join4, resolve as resolve2 } from "path";
1965
+
1966
+ // src/skills.ts
1967
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1963
1968
  import { homedir as homedir2 } from "os";
1964
1969
  import { join as join3, resolve } from "path";
1970
+ var SKILLS_DIRNAME = "skills";
1971
+ var SKILL_FILE = "SKILL.md";
1972
+ var SKILLS_INDEX_MAX_CHARS = 4e3;
1973
+ var VALID_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
1974
+ function parseFrontmatter(raw) {
1975
+ const lines = raw.split(/\r?\n/);
1976
+ if (lines[0] !== "---") return { data: {}, body: raw };
1977
+ const end = lines.indexOf("---", 1);
1978
+ if (end < 0) return { data: {}, body: raw };
1979
+ const data = {};
1980
+ for (let i = 1; i < end; i++) {
1981
+ const line = lines[i];
1982
+ if (!line) continue;
1983
+ const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
1984
+ if (m?.[1]) data[m[1]] = (m[2] ?? "").trim();
1985
+ }
1986
+ return {
1987
+ data,
1988
+ body: lines.slice(end + 1).join("\n").replace(/^\n+/, "")
1989
+ };
1990
+ }
1991
+ function isValidSkillName(name) {
1992
+ return VALID_SKILL_NAME.test(name);
1993
+ }
1994
+ var SkillStore = class {
1995
+ homeDir;
1996
+ projectRoot;
1997
+ constructor(opts = {}) {
1998
+ this.homeDir = opts.homeDir ?? homedir2();
1999
+ this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : void 0;
2000
+ }
2001
+ /** True iff this store was configured with a project root. */
2002
+ hasProjectScope() {
2003
+ return this.projectRoot !== void 0;
2004
+ }
2005
+ /**
2006
+ * Root directories scanned, in priority order. Project scope first
2007
+ * so a per-repo skill overrides a global one with the same name —
2008
+ * users expect the local copy to win when both exist.
2009
+ */
2010
+ roots() {
2011
+ const out = [];
2012
+ if (this.projectRoot) {
2013
+ out.push({
2014
+ dir: join3(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
2015
+ scope: "project"
2016
+ });
2017
+ }
2018
+ out.push({ dir: join3(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
2019
+ return out;
2020
+ }
2021
+ /**
2022
+ * List every skill visible to this store. On name collisions the
2023
+ * higher-priority root (project over global) wins. Sorted by name
2024
+ * for stable prefix hashing.
2025
+ */
2026
+ list() {
2027
+ const byName = /* @__PURE__ */ new Map();
2028
+ for (const { dir, scope } of this.roots()) {
2029
+ if (!existsSync3(dir)) continue;
2030
+ let entries;
2031
+ try {
2032
+ entries = readdirSync2(dir, { withFileTypes: true });
2033
+ } catch {
2034
+ continue;
2035
+ }
2036
+ for (const entry of entries) {
2037
+ const skill = this.readEntry(dir, scope, entry);
2038
+ if (!skill) continue;
2039
+ if (!byName.has(skill.name)) byName.set(skill.name, skill);
2040
+ }
2041
+ }
2042
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
2043
+ }
2044
+ /** Resolve one skill by name. Returns `null` if not found or malformed. */
2045
+ read(name) {
2046
+ if (!isValidSkillName(name)) return null;
2047
+ for (const { dir, scope } of this.roots()) {
2048
+ if (!existsSync3(dir)) continue;
2049
+ const dirCandidate = join3(dir, name, SKILL_FILE);
2050
+ if (existsSync3(dirCandidate) && statSync2(dirCandidate).isFile()) {
2051
+ return this.parse(dirCandidate, name, scope);
2052
+ }
2053
+ const flatCandidate = join3(dir, `${name}.md`);
2054
+ if (existsSync3(flatCandidate) && statSync2(flatCandidate).isFile()) {
2055
+ return this.parse(flatCandidate, name, scope);
2056
+ }
2057
+ }
2058
+ return null;
2059
+ }
2060
+ readEntry(dir, scope, entry) {
2061
+ if (entry.isDirectory()) {
2062
+ if (!isValidSkillName(entry.name)) return null;
2063
+ const file = join3(dir, entry.name, SKILL_FILE);
2064
+ if (!existsSync3(file)) return null;
2065
+ return this.parse(file, entry.name, scope);
2066
+ }
2067
+ if (entry.isFile() && entry.name.endsWith(".md")) {
2068
+ const stem = entry.name.slice(0, -3);
2069
+ if (!isValidSkillName(stem)) return null;
2070
+ return this.parse(join3(dir, entry.name), stem, scope);
2071
+ }
2072
+ return null;
2073
+ }
2074
+ parse(path, stem, scope) {
2075
+ let raw;
2076
+ try {
2077
+ raw = readFileSync3(path, "utf8");
2078
+ } catch {
2079
+ return null;
2080
+ }
2081
+ const { data, body } = parseFrontmatter(raw);
2082
+ const name = data.name && isValidSkillName(data.name) ? data.name : stem;
2083
+ return {
2084
+ name,
2085
+ description: (data.description ?? "").trim(),
2086
+ body: body.trim(),
2087
+ scope,
2088
+ path,
2089
+ allowedTools: data["allowed-tools"]
2090
+ };
2091
+ }
2092
+ };
2093
+ function skillIndexLine(s) {
2094
+ const safeDesc = s.description.replace(/\n/g, " ").trim();
2095
+ const max = 130 - s.name.length;
2096
+ const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}\u2026` : safeDesc;
2097
+ return clipped ? `- ${s.name} \u2014 ${clipped}` : `- ${s.name}`;
2098
+ }
2099
+ function applySkillsIndex(basePrompt, opts = {}) {
2100
+ const store = new SkillStore(opts);
2101
+ const skills = store.list().filter((s) => s.description);
2102
+ if (skills.length === 0) return basePrompt;
2103
+ const lines = skills.map(skillIndexLine);
2104
+ const joined = lines.join("\n");
2105
+ const truncated = joined.length > SKILLS_INDEX_MAX_CHARS ? `${joined.slice(0, SKILLS_INDEX_MAX_CHARS)}
2106
+ \u2026 (truncated ${joined.length - SKILLS_INDEX_MAX_CHARS} chars)` : joined;
2107
+ return [
2108
+ basePrompt,
2109
+ "",
2110
+ "# Skills \u2014 user-defined prompt packs",
2111
+ "",
2112
+ 'One-liner index. Each skill is a self-contained instruction block (plus optional tool hints) the user or an earlier session saved. To load the full body, call `run_skill({ name: "<skill-name>" })` \u2014 the body is NOT in this prompt, only the name and description are. The user can also invoke a skill directly as `/skill <name>`.',
2113
+ "",
2114
+ "```",
2115
+ truncated,
2116
+ "```"
2117
+ ].join("\n");
2118
+ }
2119
+
2120
+ // src/user-memory.ts
1965
2121
  var USER_MEMORY_DIR = "memory";
1966
2122
  var MEMORY_INDEX_FILE = "MEMORY.md";
1967
2123
  var MEMORY_INDEX_MAX_CHARS = 4e3;
@@ -1976,22 +2132,22 @@ function sanitizeMemoryName(raw) {
1976
2132
  return trimmed;
1977
2133
  }
1978
2134
  function projectHash(rootDir) {
1979
- const abs = resolve(rootDir);
2135
+ const abs = resolve2(rootDir);
1980
2136
  return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
1981
2137
  }
1982
2138
  function scopeDir(opts) {
1983
2139
  if (opts.scope === "global") {
1984
- return join3(opts.homeDir, USER_MEMORY_DIR, "global");
2140
+ return join4(opts.homeDir, USER_MEMORY_DIR, "global");
1985
2141
  }
1986
2142
  if (!opts.projectRoot) {
1987
2143
  throw new Error("scope=project requires a projectRoot on MemoryStore");
1988
2144
  }
1989
- return join3(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
2145
+ return join4(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
1990
2146
  }
1991
2147
  function ensureDir(p) {
1992
- if (!existsSync3(p)) mkdirSync2(p, { recursive: true });
2148
+ if (!existsSync4(p)) mkdirSync2(p, { recursive: true });
1993
2149
  }
1994
- function parseFrontmatter(raw) {
2150
+ function parseFrontmatter2(raw) {
1995
2151
  const lines = raw.split(/\r?\n/);
1996
2152
  if (lines[0] !== "---") return { data: {}, body: raw };
1997
2153
  const end = lines.indexOf("---", 1);
@@ -2034,8 +2190,8 @@ var MemoryStore = class {
2034
2190
  homeDir;
2035
2191
  projectRoot;
2036
2192
  constructor(opts = {}) {
2037
- this.homeDir = opts.homeDir ?? join3(homedir2(), ".reasonix");
2038
- this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : void 0;
2193
+ this.homeDir = opts.homeDir ?? join4(homedir3(), ".reasonix");
2194
+ this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
2039
2195
  }
2040
2196
  /** Directory this store writes `scope` files into, creating it if needed. */
2041
2197
  dir(scope) {
@@ -2045,7 +2201,7 @@ var MemoryStore = class {
2045
2201
  }
2046
2202
  /** Absolute path to a memory file (no existence check). */
2047
2203
  pathFor(scope, name) {
2048
- return join3(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
2204
+ return join4(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
2049
2205
  }
2050
2206
  /** True iff this store is configured with a project scope available. */
2051
2207
  hasProjectScope() {
@@ -2057,14 +2213,14 @@ var MemoryStore = class {
2057
2213
  */
2058
2214
  loadIndex(scope) {
2059
2215
  if (scope === "project" && !this.projectRoot) return null;
2060
- const file = join3(
2216
+ const file = join4(
2061
2217
  scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
2062
2218
  MEMORY_INDEX_FILE
2063
2219
  );
2064
- if (!existsSync3(file)) return null;
2220
+ if (!existsSync4(file)) return null;
2065
2221
  let raw;
2066
2222
  try {
2067
- raw = readFileSync3(file, "utf8");
2223
+ raw = readFileSync4(file, "utf8");
2068
2224
  } catch {
2069
2225
  return null;
2070
2226
  }
@@ -2079,11 +2235,11 @@ var MemoryStore = class {
2079
2235
  /** Read one memory file's body (frontmatter stripped). Throws if missing. */
2080
2236
  read(scope, name) {
2081
2237
  const file = this.pathFor(scope, name);
2082
- if (!existsSync3(file)) {
2238
+ if (!existsSync4(file)) {
2083
2239
  throw new Error(`memory not found: scope=${scope} name=${name}`);
2084
2240
  }
2085
- const raw = readFileSync3(file, "utf8");
2086
- const { data, body } = parseFrontmatter(raw);
2241
+ const raw = readFileSync4(file, "utf8");
2242
+ const { data, body } = parseFrontmatter2(raw);
2087
2243
  return {
2088
2244
  name: data.name ?? name,
2089
2245
  type: data.type ?? "project",
@@ -2103,10 +2259,10 @@ var MemoryStore = class {
2103
2259
  const scopes = this.projectRoot ? ["global", "project"] : ["global"];
2104
2260
  for (const scope of scopes) {
2105
2261
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
2106
- if (!existsSync3(dir)) continue;
2262
+ if (!existsSync4(dir)) continue;
2107
2263
  let entries;
2108
2264
  try {
2109
- entries = readdirSync2(dir);
2265
+ entries = readdirSync3(dir);
2110
2266
  } catch {
2111
2267
  continue;
2112
2268
  }
@@ -2144,7 +2300,7 @@ var MemoryStore = class {
2144
2300
  createdAt: todayIso()
2145
2301
  };
2146
2302
  const dir = this.dir(input.scope);
2147
- const file = join3(dir, `${name}.md`);
2303
+ const file = join4(dir, `${name}.md`);
2148
2304
  const content = `${formatFrontmatter(entry)}${body}
2149
2305
  `;
2150
2306
  writeFileSync2(file, content, "utf8");
@@ -2157,7 +2313,7 @@ var MemoryStore = class {
2157
2313
  throw new Error("cannot delete project-scoped memory: no projectRoot configured");
2158
2314
  }
2159
2315
  const file = this.pathFor(scope, rawName);
2160
- if (!existsSync3(file)) return false;
2316
+ if (!existsSync4(file)) return false;
2161
2317
  unlinkSync2(file);
2162
2318
  this.regenerateIndex(scope);
2163
2319
  return true;
@@ -2170,17 +2326,17 @@ var MemoryStore = class {
2170
2326
  */
2171
2327
  regenerateIndex(scope) {
2172
2328
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
2173
- if (!existsSync3(dir)) return;
2329
+ if (!existsSync4(dir)) return;
2174
2330
  let files;
2175
2331
  try {
2176
- files = readdirSync2(dir);
2332
+ files = readdirSync3(dir);
2177
2333
  } catch {
2178
2334
  return;
2179
2335
  }
2180
2336
  const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
2181
- const indexPath = join3(dir, MEMORY_INDEX_FILE);
2337
+ const indexPath = join4(dir, MEMORY_INDEX_FILE);
2182
2338
  if (mdFiles.length === 0) {
2183
- if (existsSync3(indexPath)) unlinkSync2(indexPath);
2339
+ if (existsSync4(indexPath)) unlinkSync2(indexPath);
2184
2340
  return;
2185
2341
  }
2186
2342
  const lines = [];
@@ -2232,7 +2388,8 @@ function applyUserMemory(basePrompt, opts = {}) {
2232
2388
  }
2233
2389
  function applyMemoryStack(basePrompt, rootDir) {
2234
2390
  const withProject = applyProjectMemory(basePrompt, rootDir);
2235
- return applyUserMemory(withProject, { projectRoot: rootDir });
2391
+ const withMemory = applyUserMemory(withProject, { projectRoot: rootDir });
2392
+ return applySkillsIndex(withMemory, { projectRoot: rootDir });
2236
2393
  }
2237
2394
 
2238
2395
  // src/tools/filesystem.ts
@@ -2743,7 +2900,7 @@ function registerPlanTool(registry, opts = {}) {
2743
2900
 
2744
2901
  // src/tools/shell.ts
2745
2902
  import { spawn } from "child_process";
2746
- import { existsSync as existsSync4, statSync as statSync2 } from "fs";
2903
+ import { existsSync as existsSync5, statSync as statSync3 } from "fs";
2747
2904
  import * as pathMod2 from "path";
2748
2905
  var DEFAULT_TIMEOUT_SEC = 60;
2749
2906
  var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
@@ -2863,7 +3020,7 @@ async function runCommand(cmd, opts) {
2863
3020
  };
2864
3021
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
2865
3022
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
2866
- return await new Promise((resolve6, reject) => {
3023
+ return await new Promise((resolve7, reject) => {
2867
3024
  let child;
2868
3025
  try {
2869
3026
  child = spawn(bin, args, effectiveSpawnOpts);
@@ -2896,7 +3053,7 @@ async function runCommand(cmd, opts) {
2896
3053
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
2897
3054
 
2898
3055
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
2899
- resolve6({ exitCode: code, output, timedOut });
3056
+ resolve7({ exitCode: code, output, timedOut });
2900
3057
  });
2901
3058
  });
2902
3059
  }
@@ -2921,7 +3078,7 @@ function resolveExecutable(cmd, opts = {}) {
2921
3078
  }
2922
3079
  function defaultIsFile(full) {
2923
3080
  try {
2924
- return existsSync4(full) && statSync2(full).isFile();
3081
+ return existsSync5(full) && statSync3(full).isFile();
2925
3082
  } catch {
2926
3083
  return false;
2927
3084
  }
@@ -2946,8 +3103,23 @@ function prepareSpawn(argv, opts = {}) {
2946
3103
  spawnOverrides: { windowsVerbatimArguments: true }
2947
3104
  };
2948
3105
  }
3106
+ if (isBareWindowsName(resolved) && resolved === head) {
3107
+ const cmdline = [head, ...tail].map(quoteForCmdExe).join(" ");
3108
+ return {
3109
+ bin: "cmd.exe",
3110
+ args: ["/d", "/s", "/c", cmdline],
3111
+ spawnOverrides: { windowsVerbatimArguments: true }
3112
+ };
3113
+ }
2949
3114
  return { bin: resolved, args: [...tail], spawnOverrides: {} };
2950
3115
  }
3116
+ function isBareWindowsName(s) {
3117
+ if (!s) return false;
3118
+ if (s.includes("/") || s.includes("\\")) return false;
3119
+ if (pathMod2.isAbsolute(s)) return false;
3120
+ if (pathMod2.extname(s)) return false;
3121
+ return true;
3122
+ }
2951
3123
  function quoteForCmdExe(arg) {
2952
3124
  if (arg === "") return '""';
2953
3125
  if (!/[\s"&|<>^%(),;!]/.test(arg)) return arg;
@@ -2967,7 +3139,10 @@ function registerShellTools(registry, opts) {
2967
3139
  const rootDir = pathMod2.resolve(opts.rootDir);
2968
3140
  const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
2969
3141
  const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2970
- const extraAllowed = opts.extraAllowed ?? [];
3142
+ const getExtraAllowed = typeof opts.extraAllowed === "function" ? opts.extraAllowed : (() => {
3143
+ const snapshot = opts.extraAllowed ?? [];
3144
+ return () => snapshot;
3145
+ })();
2971
3146
  const allowAll = opts.allowAll ?? false;
2972
3147
  registry.register({
2973
3148
  name: "run_command",
@@ -2980,7 +3155,7 @@ function registerShellTools(registry, opts) {
2980
3155
  if (allowAll) return true;
2981
3156
  const cmd = typeof args?.command === "string" ? args.command.trim() : "";
2982
3157
  if (!cmd) return false;
2983
- return isAllowed(cmd, extraAllowed);
3158
+ return isAllowed(cmd, getExtraAllowed());
2984
3159
  },
2985
3160
  parameters: {
2986
3161
  type: "object",
@@ -2999,7 +3174,7 @@ function registerShellTools(registry, opts) {
2999
3174
  fn: async (args, ctx) => {
3000
3175
  const cmd = args.command.trim();
3001
3176
  if (!cmd) throw new Error("run_command: empty command");
3002
- if (!allowAll && !isAllowed(cmd, extraAllowed)) {
3177
+ if (!allowAll && !isAllowed(cmd, getExtraAllowed())) {
3003
3178
  throw new NeedsConfirmationError(cmd);
3004
3179
  }
3005
3180
  const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
@@ -3206,12 +3381,12 @@ ${i + 1}. ${r.title}`);
3206
3381
  }
3207
3382
 
3208
3383
  // src/env.ts
3209
- import { readFileSync as readFileSync4 } from "fs";
3210
- import { resolve as resolve4 } from "path";
3384
+ import { readFileSync as readFileSync5 } from "fs";
3385
+ import { resolve as resolve5 } from "path";
3211
3386
  function loadDotenv(path = ".env") {
3212
3387
  let raw;
3213
3388
  try {
3214
- raw = readFileSync4(resolve4(process.cwd(), path), "utf8");
3389
+ raw = readFileSync5(resolve5(process.cwd(), path), "utf8");
3215
3390
  } catch {
3216
3391
  return;
3217
3392
  }
@@ -3230,7 +3405,7 @@ function loadDotenv(path = ".env") {
3230
3405
  }
3231
3406
 
3232
3407
  // src/transcript.ts
3233
- import { createWriteStream, readFileSync as readFileSync5 } from "fs";
3408
+ import { createWriteStream, readFileSync as readFileSync6 } from "fs";
3234
3409
  function recordFromLoopEvent(ev, extra) {
3235
3410
  const rec = {
3236
3411
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3281,7 +3456,7 @@ function openTranscriptFile(path, meta) {
3281
3456
  return stream;
3282
3457
  }
3283
3458
  function readTranscript(path) {
3284
- const raw = readFileSync5(path, "utf8");
3459
+ const raw = readFileSync6(path, "utf8");
3285
3460
  return parseTranscript(raw);
3286
3461
  }
3287
3462
  function isPlanStateEmptyShape(s) {
@@ -3893,7 +4068,7 @@ var McpClient = class {
3893
4068
  const id = this.nextId++;
3894
4069
  const frame = { jsonrpc: "2.0", id, method, params };
3895
4070
  let abortHandler = null;
3896
- const promise = new Promise((resolve6, reject) => {
4071
+ const promise = new Promise((resolve7, reject) => {
3897
4072
  const timeout = setTimeout(() => {
3898
4073
  this.pending.delete(id);
3899
4074
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -3902,7 +4077,7 @@ var McpClient = class {
3902
4077
  );
3903
4078
  }, this.requestTimeoutMs);
3904
4079
  this.pending.set(id, {
3905
- resolve: resolve6,
4080
+ resolve: resolve7,
3906
4081
  reject,
3907
4082
  timeout
3908
4083
  });
@@ -4025,12 +4200,12 @@ var StdioTransport = class {
4025
4200
  }
4026
4201
  async send(message) {
4027
4202
  if (this.closed) throw new Error("MCP transport is closed");
4028
- return new Promise((resolve6, reject) => {
4203
+ return new Promise((resolve7, reject) => {
4029
4204
  const line = `${JSON.stringify(message)}
4030
4205
  `;
4031
4206
  this.child.stdin.write(line, "utf8", (err) => {
4032
4207
  if (err) reject(err);
4033
- else resolve6();
4208
+ else resolve7();
4034
4209
  });
4035
4210
  });
4036
4211
  }
@@ -4041,8 +4216,8 @@ var StdioTransport = class {
4041
4216
  continue;
4042
4217
  }
4043
4218
  if (this.closed) return;
4044
- const next = await new Promise((resolve6) => {
4045
- this.waiters.push(resolve6);
4219
+ const next = await new Promise((resolve7) => {
4220
+ this.waiters.push(resolve7);
4046
4221
  });
4047
4222
  if (next === null) return;
4048
4223
  yield next;
@@ -4108,8 +4283,8 @@ var SseTransport = class {
4108
4283
  constructor(opts) {
4109
4284
  this.url = opts.url;
4110
4285
  this.headers = opts.headers ?? {};
4111
- this.endpointReady = new Promise((resolve6, reject) => {
4112
- this.resolveEndpoint = resolve6;
4286
+ this.endpointReady = new Promise((resolve7, reject) => {
4287
+ this.resolveEndpoint = resolve7;
4113
4288
  this.rejectEndpoint = reject;
4114
4289
  });
4115
4290
  this.endpointReady.catch(() => void 0);
@@ -4136,8 +4311,8 @@ var SseTransport = class {
4136
4311
  continue;
4137
4312
  }
4138
4313
  if (this.closed) return;
4139
- const next = await new Promise((resolve6) => {
4140
- this.waiters.push(resolve6);
4314
+ const next = await new Promise((resolve7) => {
4315
+ this.waiters.push(resolve7);
4141
4316
  });
4142
4317
  if (next === null) return;
4143
4318
  yield next;
@@ -4336,8 +4511,8 @@ async function trySection(load) {
4336
4511
  }
4337
4512
 
4338
4513
  // src/code/edit-blocks.ts
4339
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
4340
- import { dirname as dirname3, resolve as resolve5 } from "path";
4514
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
4515
+ import { dirname as dirname3, resolve as resolve6 } from "path";
4341
4516
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
4342
4517
  function parseEditBlocks(text) {
4343
4518
  const out = [];
@@ -4355,8 +4530,8 @@ function parseEditBlocks(text) {
4355
4530
  return out;
4356
4531
  }
4357
4532
  function applyEditBlock(block, rootDir) {
4358
- const absRoot = resolve5(rootDir);
4359
- const absTarget = resolve5(absRoot, block.path);
4533
+ const absRoot = resolve6(rootDir);
4534
+ const absTarget = resolve6(absRoot, block.path);
4360
4535
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
4361
4536
  return {
4362
4537
  path: block.path,
@@ -4365,7 +4540,7 @@ function applyEditBlock(block, rootDir) {
4365
4540
  };
4366
4541
  }
4367
4542
  const searchEmpty = block.search.length === 0;
4368
- const exists = existsSync5(absTarget);
4543
+ const exists = existsSync6(absTarget);
4369
4544
  try {
4370
4545
  if (!exists) {
4371
4546
  if (!searchEmpty) {
@@ -4379,7 +4554,7 @@ function applyEditBlock(block, rootDir) {
4379
4554
  writeFileSync3(absTarget, block.replace, "utf8");
4380
4555
  return { path: block.path, status: "created" };
4381
4556
  }
4382
- const content = readFileSync6(absTarget, "utf8");
4557
+ const content = readFileSync7(absTarget, "utf8");
4383
4558
  if (searchEmpty) {
4384
4559
  return {
4385
4560
  path: block.path,
@@ -4406,19 +4581,19 @@ function applyEditBlocks(blocks, rootDir) {
4406
4581
  return blocks.map((b) => applyEditBlock(b, rootDir));
4407
4582
  }
4408
4583
  function snapshotBeforeEdits(blocks, rootDir) {
4409
- const absRoot = resolve5(rootDir);
4584
+ const absRoot = resolve6(rootDir);
4410
4585
  const seen = /* @__PURE__ */ new Set();
4411
4586
  const snapshots = [];
4412
4587
  for (const b of blocks) {
4413
4588
  if (seen.has(b.path)) continue;
4414
4589
  seen.add(b.path);
4415
- const abs = resolve5(absRoot, b.path);
4416
- if (!existsSync5(abs)) {
4590
+ const abs = resolve6(absRoot, b.path);
4591
+ if (!existsSync6(abs)) {
4417
4592
  snapshots.push({ path: b.path, prevContent: null });
4418
4593
  continue;
4419
4594
  }
4420
4595
  try {
4421
- snapshots.push({ path: b.path, prevContent: readFileSync6(abs, "utf8") });
4596
+ snapshots.push({ path: b.path, prevContent: readFileSync7(abs, "utf8") });
4422
4597
  } catch {
4423
4598
  snapshots.push({ path: b.path, prevContent: null });
4424
4599
  }
@@ -4426,9 +4601,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
4426
4601
  return snapshots;
4427
4602
  }
4428
4603
  function restoreSnapshots(snapshots, rootDir) {
4429
- const absRoot = resolve5(rootDir);
4604
+ const absRoot = resolve6(rootDir);
4430
4605
  return snapshots.map((snap) => {
4431
- const abs = resolve5(absRoot, snap.path);
4606
+ const abs = resolve6(absRoot, snap.path);
4432
4607
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
4433
4608
  return {
4434
4609
  path: snap.path,
@@ -4438,7 +4613,7 @@ function restoreSnapshots(snapshots, rootDir) {
4438
4613
  }
4439
4614
  try {
4440
4615
  if (snap.prevContent === null) {
4441
- if (existsSync5(abs)) unlinkSync3(abs);
4616
+ if (existsSync6(abs)) unlinkSync3(abs);
4442
4617
  return {
4443
4618
  path: snap.path,
4444
4619
  status: "applied",
@@ -4461,8 +4636,8 @@ function sep() {
4461
4636
  }
4462
4637
 
4463
4638
  // src/code/prompt.ts
4464
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
4465
- import { join as join5 } from "path";
4639
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
4640
+ import { join as join6 } from "path";
4466
4641
  var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.
4467
4642
 
4468
4643
  # When to propose a plan (submit_plan)
@@ -4537,11 +4712,11 @@ Before exploring the filesystem to answer a factual question, check whether the
4537
4712
  `;
4538
4713
  function codeSystemPrompt(rootDir) {
4539
4714
  const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
4540
- const gitignorePath = join5(rootDir, ".gitignore");
4541
- if (!existsSync6(gitignorePath)) return withMemory;
4715
+ const gitignorePath = join6(rootDir, ".gitignore");
4716
+ if (!existsSync7(gitignorePath)) return withMemory;
4542
4717
  let content;
4543
4718
  try {
4544
- content = readFileSync7(gitignorePath, "utf8");
4719
+ content = readFileSync8(gitignorePath, "utf8");
4545
4720
  } catch {
4546
4721
  return withMemory;
4547
4722
  }
@@ -4561,15 +4736,15 @@ ${truncated}
4561
4736
  }
4562
4737
 
4563
4738
  // src/config.ts
4564
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
4565
- import { homedir as homedir3 } from "os";
4566
- import { dirname as dirname4, join as join6 } from "path";
4739
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
4740
+ import { homedir as homedir4 } from "os";
4741
+ import { dirname as dirname4, join as join7 } from "path";
4567
4742
  function defaultConfigPath() {
4568
- return join6(homedir3(), ".reasonix", "config.json");
4743
+ return join7(homedir4(), ".reasonix", "config.json");
4569
4744
  }
4570
4745
  function readConfig(path = defaultConfigPath()) {
4571
4746
  try {
4572
- const raw = readFileSync8(path, "utf8");
4747
+ const raw = readFileSync9(path, "utf8");
4573
4748
  const parsed = JSON.parse(raw);
4574
4749
  if (parsed && typeof parsed === "object") return parsed;
4575
4750
  } catch {
@@ -4603,8 +4778,106 @@ function redactKey(key) {
4603
4778
  return `${key.slice(0, 6)}\u2026${key.slice(-4)}`;
4604
4779
  }
4605
4780
 
4606
- // src/index.ts
4607
- var VERSION = "0.4.20";
4781
+ // src/version.ts
4782
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
4783
+ import { homedir as homedir5 } from "os";
4784
+ import { dirname as dirname5, join as join8 } from "path";
4785
+ import { fileURLToPath } from "url";
4786
+ var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
4787
+ var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4788
+ var LATEST_FETCH_TIMEOUT_MS = 2e3;
4789
+ function readPackageVersion() {
4790
+ try {
4791
+ let dir = dirname5(fileURLToPath(import.meta.url));
4792
+ for (let i = 0; i < 6; i++) {
4793
+ const p = join8(dir, "package.json");
4794
+ if (existsSync8(p)) {
4795
+ const pkg = JSON.parse(readFileSync10(p, "utf8"));
4796
+ if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
4797
+ return pkg.version;
4798
+ }
4799
+ }
4800
+ const parent = dirname5(dir);
4801
+ if (parent === dir) break;
4802
+ dir = parent;
4803
+ }
4804
+ } catch {
4805
+ }
4806
+ return "0.0.0-dev";
4807
+ }
4808
+ var VERSION = readPackageVersion();
4809
+ function cachePath(homeDirOverride) {
4810
+ return join8(homeDirOverride ?? homedir5(), ".reasonix", "version-cache.json");
4811
+ }
4812
+ function readCache(homeDirOverride) {
4813
+ try {
4814
+ const raw = readFileSync10(cachePath(homeDirOverride), "utf8");
4815
+ const parsed = JSON.parse(raw);
4816
+ if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
4817
+ return parsed;
4818
+ }
4819
+ } catch {
4820
+ }
4821
+ return null;
4822
+ }
4823
+ function writeCache(entry, homeDirOverride) {
4824
+ try {
4825
+ const p = cachePath(homeDirOverride);
4826
+ mkdirSync5(dirname5(p), { recursive: true });
4827
+ writeFileSync5(p, JSON.stringify(entry), "utf8");
4828
+ } catch {
4829
+ }
4830
+ }
4831
+ async function getLatestVersion(opts = {}) {
4832
+ const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
4833
+ if (!opts.force) {
4834
+ const cached = readCache(opts.homeDir);
4835
+ if (cached && Date.now() - cached.checkedAt < ttl) return cached.version;
4836
+ }
4837
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
4838
+ if (!fetchImpl) return null;
4839
+ const url = opts.registryUrl ?? REGISTRY_URL;
4840
+ const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
4841
+ const controller = new AbortController();
4842
+ const timer = setTimeout(() => controller.abort(), timeout);
4843
+ try {
4844
+ const res = await fetchImpl(url, {
4845
+ signal: controller.signal,
4846
+ headers: { accept: "application/json" }
4847
+ });
4848
+ if (!res.ok) return null;
4849
+ const body = await res.json();
4850
+ if (typeof body.version !== "string") return null;
4851
+ writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
4852
+ return body.version;
4853
+ } catch {
4854
+ return null;
4855
+ } finally {
4856
+ clearTimeout(timer);
4857
+ }
4858
+ }
4859
+ function compareVersions(a, b) {
4860
+ const [aCore = "0", aPre = ""] = a.split("-", 2);
4861
+ const [bCore = "0", bPre = ""] = b.split("-", 2);
4862
+ const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
4863
+ const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
4864
+ for (let i = 0; i < 3; i++) {
4865
+ const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
4866
+ if (diff !== 0) return diff;
4867
+ }
4868
+ if (!aPre && !bPre) return 0;
4869
+ if (!aPre) return 1;
4870
+ if (!bPre) return -1;
4871
+ return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
4872
+ }
4873
+ function isNpxInstall() {
4874
+ const bin = process.argv[1] ?? "";
4875
+ if (/[/\\]_npx[/\\]/.test(bin)) return true;
4876
+ if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
4877
+ const ua = process.env.npm_config_user_agent ?? "";
4878
+ if (ua.includes("npx/")) return true;
4879
+ return false;
4880
+ }
4608
4881
  export {
4609
4882
  AppendOnlyLog,
4610
4883
  CODE_SYSTEM_PROMPT,
@@ -4612,6 +4885,8 @@ export {
4612
4885
  DEFAULT_MAX_RESULT_CHARS,
4613
4886
  DeepSeekClient,
4614
4887
  ImmutablePrefix,
4888
+ LATEST_CACHE_TTL_MS,
4889
+ LATEST_FETCH_TIMEOUT_MS,
4615
4890
  MCP_PROTOCOL_VERSION,
4616
4891
  MEMORY_INDEX_FILE,
4617
4892
  MEMORY_INDEX_MAX_CHARS,
@@ -4642,6 +4917,7 @@ export {
4642
4917
  bridgeMcpTools,
4643
4918
  claudeEquivalentCost,
4644
4919
  codeSystemPrompt,
4920
+ compareVersions,
4645
4921
  computeReplayStats,
4646
4922
  costUsd,
4647
4923
  defaultConfigPath,
@@ -4655,6 +4931,7 @@ export {
4655
4931
  formatCommandResult,
4656
4932
  formatLoopError,
4657
4933
  formatSearchResults,
4934
+ getLatestVersion,
4658
4935
  harvest,
4659
4936
  healLoadedMessages,
4660
4937
  htmlToText,
@@ -4662,6 +4939,7 @@ export {
4662
4939
  inspectMcpServer,
4663
4940
  isAllowed,
4664
4941
  isJsonRpcError,
4942
+ isNpxInstall,
4665
4943
  isPlanStateEmpty,
4666
4944
  isPlausibleKey,
4667
4945
  listSessions,