reasonix 0.4.15 → 0.4.17
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/README.md +22 -0
- package/dist/cli/{chunk-2P2MZLCE.js → chunk-3YQRWFES.js} +56 -6
- package/dist/cli/chunk-3YQRWFES.js.map +1 -0
- package/dist/cli/index.js +793 -258
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-MMANQ36Z.js → prompt-HK5XLH55.js} +2 -2
- package/dist/index.d.ts +155 -3
- package/dist/index.js +317 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-2P2MZLCE.js.map +0 -1
- /package/dist/cli/{prompt-MMANQ36Z.js.map → prompt-HK5XLH55.js.map} +0 -0
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((
|
|
51
|
-
const timer = setTimeout(
|
|
50
|
+
return new Promise((resolve5, reject) => {
|
|
51
|
+
const timer = setTimeout(resolve5, ms);
|
|
52
52
|
if (signal) {
|
|
53
53
|
const onAbort = () => {
|
|
54
54
|
clearTimeout(timer);
|
|
@@ -1488,8 +1488,8 @@ var CacheFirstLoop = class {
|
|
|
1488
1488
|
}
|
|
1489
1489
|
);
|
|
1490
1490
|
for (let k = 0; k < budget; k++) {
|
|
1491
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1492
|
-
waiter =
|
|
1491
|
+
const sample = queue.shift() ?? await new Promise((resolve5) => {
|
|
1492
|
+
waiter = resolve5;
|
|
1493
1493
|
});
|
|
1494
1494
|
yield {
|
|
1495
1495
|
turn: this._turn,
|
|
@@ -1848,6 +1848,49 @@ function formatLoopError(err) {
|
|
|
1848
1848
|
return msg;
|
|
1849
1849
|
}
|
|
1850
1850
|
|
|
1851
|
+
// src/project-memory.ts
|
|
1852
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1853
|
+
import { join as join2 } from "path";
|
|
1854
|
+
var PROJECT_MEMORY_FILE = "REASONIX.md";
|
|
1855
|
+
var PROJECT_MEMORY_MAX_CHARS = 8e3;
|
|
1856
|
+
function readProjectMemory(rootDir) {
|
|
1857
|
+
const path = join2(rootDir, PROJECT_MEMORY_FILE);
|
|
1858
|
+
if (!existsSync2(path)) return null;
|
|
1859
|
+
let raw;
|
|
1860
|
+
try {
|
|
1861
|
+
raw = readFileSync2(path, "utf8");
|
|
1862
|
+
} catch {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
const trimmed = raw.trim();
|
|
1866
|
+
if (!trimmed) return null;
|
|
1867
|
+
const originalChars = trimmed.length;
|
|
1868
|
+
const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
|
|
1869
|
+
const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
|
|
1870
|
+
\u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
|
|
1871
|
+
return { path, content, originalChars, truncated };
|
|
1872
|
+
}
|
|
1873
|
+
function memoryEnabled() {
|
|
1874
|
+
const env = process.env.REASONIX_MEMORY;
|
|
1875
|
+
if (env === "off" || env === "false" || env === "0") return false;
|
|
1876
|
+
return true;
|
|
1877
|
+
}
|
|
1878
|
+
function applyProjectMemory(basePrompt, rootDir) {
|
|
1879
|
+
if (!memoryEnabled()) return basePrompt;
|
|
1880
|
+
const mem = readProjectMemory(rootDir);
|
|
1881
|
+
if (!mem) return basePrompt;
|
|
1882
|
+
return `${basePrompt}
|
|
1883
|
+
|
|
1884
|
+
# Project memory (REASONIX.md)
|
|
1885
|
+
|
|
1886
|
+
The user pinned these notes about this project \u2014 treat them as authoritative context for every turn:
|
|
1887
|
+
|
|
1888
|
+
\`\`\`
|
|
1889
|
+
${mem.content}
|
|
1890
|
+
\`\`\`
|
|
1891
|
+
`;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1851
1894
|
// src/tools/filesystem.ts
|
|
1852
1895
|
import { promises as fs } from "fs";
|
|
1853
1896
|
import * as pathMod from "path";
|
|
@@ -2183,6 +2226,221 @@ function lineDiff(a, b) {
|
|
|
2183
2226
|
return out;
|
|
2184
2227
|
}
|
|
2185
2228
|
|
|
2229
|
+
// src/tools/shell.ts
|
|
2230
|
+
import { spawn } from "child_process";
|
|
2231
|
+
import * as pathMod2 from "path";
|
|
2232
|
+
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2233
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
2234
|
+
var BUILTIN_ALLOWLIST = [
|
|
2235
|
+
// Repo inspection
|
|
2236
|
+
"git status",
|
|
2237
|
+
"git diff",
|
|
2238
|
+
"git log",
|
|
2239
|
+
"git show",
|
|
2240
|
+
"git blame",
|
|
2241
|
+
"git branch",
|
|
2242
|
+
"git remote",
|
|
2243
|
+
"git rev-parse",
|
|
2244
|
+
"git config --get",
|
|
2245
|
+
// Filesystem inspection
|
|
2246
|
+
"ls",
|
|
2247
|
+
"pwd",
|
|
2248
|
+
"cat",
|
|
2249
|
+
"head",
|
|
2250
|
+
"tail",
|
|
2251
|
+
"wc",
|
|
2252
|
+
"file",
|
|
2253
|
+
"tree",
|
|
2254
|
+
"find",
|
|
2255
|
+
"grep",
|
|
2256
|
+
"rg",
|
|
2257
|
+
// Language version probes
|
|
2258
|
+
"node --version",
|
|
2259
|
+
"node -v",
|
|
2260
|
+
"npm --version",
|
|
2261
|
+
"npx --version",
|
|
2262
|
+
"python --version",
|
|
2263
|
+
"python3 --version",
|
|
2264
|
+
"cargo --version",
|
|
2265
|
+
"go version",
|
|
2266
|
+
"rustc --version",
|
|
2267
|
+
"deno --version",
|
|
2268
|
+
"bun --version",
|
|
2269
|
+
// Test runners (non-destructive by convention)
|
|
2270
|
+
"npm test",
|
|
2271
|
+
"npm run test",
|
|
2272
|
+
"npx vitest run",
|
|
2273
|
+
"npx vitest",
|
|
2274
|
+
"npx jest",
|
|
2275
|
+
"pytest",
|
|
2276
|
+
"python -m pytest",
|
|
2277
|
+
"cargo test",
|
|
2278
|
+
"cargo check",
|
|
2279
|
+
"cargo clippy",
|
|
2280
|
+
"go test",
|
|
2281
|
+
"go vet",
|
|
2282
|
+
"deno test",
|
|
2283
|
+
"bun test",
|
|
2284
|
+
// Linters / typecheckers (read-only by convention)
|
|
2285
|
+
"npm run lint",
|
|
2286
|
+
"npm run typecheck",
|
|
2287
|
+
"npx tsc --noEmit",
|
|
2288
|
+
"npx biome check",
|
|
2289
|
+
"npx eslint",
|
|
2290
|
+
"npx prettier --check",
|
|
2291
|
+
"ruff",
|
|
2292
|
+
"mypy"
|
|
2293
|
+
];
|
|
2294
|
+
function tokenizeCommand(cmd) {
|
|
2295
|
+
const out = [];
|
|
2296
|
+
let cur = "";
|
|
2297
|
+
let quote = null;
|
|
2298
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
2299
|
+
const ch = cmd[i];
|
|
2300
|
+
if (quote) {
|
|
2301
|
+
if (ch === quote) {
|
|
2302
|
+
quote = null;
|
|
2303
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
2304
|
+
cur += cmd[++i];
|
|
2305
|
+
} else {
|
|
2306
|
+
cur += ch;
|
|
2307
|
+
}
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
if (ch === '"' || ch === "'") {
|
|
2311
|
+
quote = ch;
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
if (ch === " " || ch === " ") {
|
|
2315
|
+
if (cur.length > 0) {
|
|
2316
|
+
out.push(cur);
|
|
2317
|
+
cur = "";
|
|
2318
|
+
}
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
cur += ch;
|
|
2322
|
+
}
|
|
2323
|
+
if (quote) throw new Error(`unclosed ${quote} in command`);
|
|
2324
|
+
if (cur.length > 0) out.push(cur);
|
|
2325
|
+
return out;
|
|
2326
|
+
}
|
|
2327
|
+
function isAllowed(cmd, extra = []) {
|
|
2328
|
+
const normalized = cmd.trim().replace(/\s+/g, " ");
|
|
2329
|
+
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
2330
|
+
for (const prefix of allowlist) {
|
|
2331
|
+
if (normalized === prefix) return true;
|
|
2332
|
+
if (normalized.startsWith(`${prefix} `)) return true;
|
|
2333
|
+
}
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
async function runCommand(cmd, opts) {
|
|
2337
|
+
const argv = tokenizeCommand(cmd);
|
|
2338
|
+
if (argv.length === 0) throw new Error("run_command: empty command");
|
|
2339
|
+
const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
|
|
2340
|
+
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2341
|
+
const spawnOpts = {
|
|
2342
|
+
cwd: opts.cwd,
|
|
2343
|
+
shell: false,
|
|
2344
|
+
// no shell-expansion — see header comment
|
|
2345
|
+
windowsHide: true,
|
|
2346
|
+
env: process.env
|
|
2347
|
+
};
|
|
2348
|
+
return await new Promise((resolve5, reject) => {
|
|
2349
|
+
let child;
|
|
2350
|
+
try {
|
|
2351
|
+
child = spawn(argv[0], argv.slice(1), spawnOpts);
|
|
2352
|
+
} catch (err) {
|
|
2353
|
+
reject(err);
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
let buf = "";
|
|
2357
|
+
let timedOut = false;
|
|
2358
|
+
const killTimer = setTimeout(() => {
|
|
2359
|
+
timedOut = true;
|
|
2360
|
+
child.kill("SIGKILL");
|
|
2361
|
+
}, timeoutMs);
|
|
2362
|
+
const onAbort = () => child.kill("SIGKILL");
|
|
2363
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2364
|
+
const onData = (chunk) => {
|
|
2365
|
+
buf += chunk.toString();
|
|
2366
|
+
if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
|
|
2367
|
+
};
|
|
2368
|
+
child.stdout?.on("data", onData);
|
|
2369
|
+
child.stderr?.on("data", onData);
|
|
2370
|
+
child.on("error", (err) => {
|
|
2371
|
+
clearTimeout(killTimer);
|
|
2372
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
2373
|
+
reject(err);
|
|
2374
|
+
});
|
|
2375
|
+
child.on("close", (code) => {
|
|
2376
|
+
clearTimeout(killTimer);
|
|
2377
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
2378
|
+
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
2379
|
+
|
|
2380
|
+
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
2381
|
+
resolve5({ exitCode: code, output, timedOut });
|
|
2382
|
+
});
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
var NeedsConfirmationError = class extends Error {
|
|
2386
|
+
command;
|
|
2387
|
+
constructor(command) {
|
|
2388
|
+
super(
|
|
2389
|
+
`run_command: "${command}" needs the user's approval before it runs. STOP calling tools now \u2014 the TUI has already prompted the user to press y (run) or n (deny). Wait for their next message; it will either be the command's output (if they approved) or an instruction to continue without it (if they denied). Don't retry the command or call other shell commands in the meantime.`
|
|
2390
|
+
);
|
|
2391
|
+
this.name = "NeedsConfirmationError";
|
|
2392
|
+
this.command = command;
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
function registerShellTools(registry, opts) {
|
|
2396
|
+
const rootDir = pathMod2.resolve(opts.rootDir);
|
|
2397
|
+
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
2398
|
+
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2399
|
+
const extraAllowed = opts.extraAllowed ?? [];
|
|
2400
|
+
const allowAll = opts.allowAll ?? false;
|
|
2401
|
+
registry.register({
|
|
2402
|
+
name: "run_command",
|
|
2403
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr. Read-only and test commands (git status, ls, npm test, pytest, cargo test, grep, etc.) run immediately. Anything that could mutate state (npm install, git commit, rm, chmod) is refused and the user has to confirm in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
2404
|
+
parameters: {
|
|
2405
|
+
type: "object",
|
|
2406
|
+
properties: {
|
|
2407
|
+
command: {
|
|
2408
|
+
type: "string",
|
|
2409
|
+
description: "Full command line, e.g. 'npm test' or 'git diff src/foo.ts'. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
|
|
2410
|
+
},
|
|
2411
|
+
timeoutSec: {
|
|
2412
|
+
type: "integer",
|
|
2413
|
+
description: `Override the default ${timeoutSec}s timeout for a single command.`
|
|
2414
|
+
}
|
|
2415
|
+
},
|
|
2416
|
+
required: ["command"]
|
|
2417
|
+
},
|
|
2418
|
+
fn: async (args, ctx) => {
|
|
2419
|
+
const cmd = args.command.trim();
|
|
2420
|
+
if (!cmd) throw new Error("run_command: empty command");
|
|
2421
|
+
if (!allowAll && !isAllowed(cmd, extraAllowed)) {
|
|
2422
|
+
throw new NeedsConfirmationError(cmd);
|
|
2423
|
+
}
|
|
2424
|
+
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
2425
|
+
const result = await runCommand(cmd, {
|
|
2426
|
+
cwd: rootDir,
|
|
2427
|
+
timeoutSec: effectiveTimeout,
|
|
2428
|
+
maxOutputChars,
|
|
2429
|
+
signal: ctx?.signal
|
|
2430
|
+
});
|
|
2431
|
+
return formatCommandResult(cmd, result);
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
return registry;
|
|
2435
|
+
}
|
|
2436
|
+
function formatCommandResult(cmd, r) {
|
|
2437
|
+
const header = r.timedOut ? `$ ${cmd}
|
|
2438
|
+
[killed after timeout]` : `$ ${cmd}
|
|
2439
|
+
[exit ${r.exitCode ?? "?"}]`;
|
|
2440
|
+
return r.output ? `${header}
|
|
2441
|
+
${r.output}` : header;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2186
2444
|
// src/tools/web.ts
|
|
2187
2445
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
2188
2446
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
@@ -2365,12 +2623,12 @@ ${i + 1}. ${r.title}`);
|
|
|
2365
2623
|
}
|
|
2366
2624
|
|
|
2367
2625
|
// src/env.ts
|
|
2368
|
-
import { readFileSync as
|
|
2369
|
-
import { resolve as
|
|
2626
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2627
|
+
import { resolve as resolve3 } from "path";
|
|
2370
2628
|
function loadDotenv(path = ".env") {
|
|
2371
2629
|
let raw;
|
|
2372
2630
|
try {
|
|
2373
|
-
raw =
|
|
2631
|
+
raw = readFileSync3(resolve3(process.cwd(), path), "utf8");
|
|
2374
2632
|
} catch {
|
|
2375
2633
|
return;
|
|
2376
2634
|
}
|
|
@@ -2389,7 +2647,7 @@ function loadDotenv(path = ".env") {
|
|
|
2389
2647
|
}
|
|
2390
2648
|
|
|
2391
2649
|
// src/transcript.ts
|
|
2392
|
-
import { createWriteStream, readFileSync as
|
|
2650
|
+
import { createWriteStream, readFileSync as readFileSync4 } from "fs";
|
|
2393
2651
|
function recordFromLoopEvent(ev, extra) {
|
|
2394
2652
|
const rec = {
|
|
2395
2653
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2440,7 +2698,7 @@ function openTranscriptFile(path, meta) {
|
|
|
2440
2698
|
return stream;
|
|
2441
2699
|
}
|
|
2442
2700
|
function readTranscript(path) {
|
|
2443
|
-
const raw =
|
|
2701
|
+
const raw = readFileSync4(path, "utf8");
|
|
2444
2702
|
return parseTranscript(raw);
|
|
2445
2703
|
}
|
|
2446
2704
|
function isPlanStateEmptyShape(s) {
|
|
@@ -3052,7 +3310,7 @@ var McpClient = class {
|
|
|
3052
3310
|
const id = this.nextId++;
|
|
3053
3311
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
3054
3312
|
let abortHandler = null;
|
|
3055
|
-
const promise = new Promise((
|
|
3313
|
+
const promise = new Promise((resolve5, reject) => {
|
|
3056
3314
|
const timeout = setTimeout(() => {
|
|
3057
3315
|
this.pending.delete(id);
|
|
3058
3316
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -3061,7 +3319,7 @@ var McpClient = class {
|
|
|
3061
3319
|
);
|
|
3062
3320
|
}, this.requestTimeoutMs);
|
|
3063
3321
|
this.pending.set(id, {
|
|
3064
|
-
resolve:
|
|
3322
|
+
resolve: resolve5,
|
|
3065
3323
|
reject,
|
|
3066
3324
|
timeout
|
|
3067
3325
|
});
|
|
@@ -3143,7 +3401,7 @@ var McpClient = class {
|
|
|
3143
3401
|
};
|
|
3144
3402
|
|
|
3145
3403
|
// src/mcp/stdio.ts
|
|
3146
|
-
import { spawn } from "child_process";
|
|
3404
|
+
import { spawn as spawn2 } from "child_process";
|
|
3147
3405
|
var StdioTransport = class {
|
|
3148
3406
|
child;
|
|
3149
3407
|
queue = [];
|
|
@@ -3158,14 +3416,14 @@ var StdioTransport = class {
|
|
|
3158
3416
|
opts.command,
|
|
3159
3417
|
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
3160
3418
|
].join(" ");
|
|
3161
|
-
this.child =
|
|
3419
|
+
this.child = spawn2(line, [], {
|
|
3162
3420
|
env,
|
|
3163
3421
|
cwd: opts.cwd,
|
|
3164
3422
|
stdio: ["pipe", "pipe", "inherit"],
|
|
3165
3423
|
shell: true
|
|
3166
3424
|
});
|
|
3167
3425
|
} else {
|
|
3168
|
-
this.child =
|
|
3426
|
+
this.child = spawn2(opts.command, opts.args ?? [], {
|
|
3169
3427
|
env,
|
|
3170
3428
|
cwd: opts.cwd,
|
|
3171
3429
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -3184,12 +3442,12 @@ var StdioTransport = class {
|
|
|
3184
3442
|
}
|
|
3185
3443
|
async send(message) {
|
|
3186
3444
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
3187
|
-
return new Promise((
|
|
3445
|
+
return new Promise((resolve5, reject) => {
|
|
3188
3446
|
const line = `${JSON.stringify(message)}
|
|
3189
3447
|
`;
|
|
3190
3448
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
3191
3449
|
if (err) reject(err);
|
|
3192
|
-
else
|
|
3450
|
+
else resolve5();
|
|
3193
3451
|
});
|
|
3194
3452
|
});
|
|
3195
3453
|
}
|
|
@@ -3200,8 +3458,8 @@ var StdioTransport = class {
|
|
|
3200
3458
|
continue;
|
|
3201
3459
|
}
|
|
3202
3460
|
if (this.closed) return;
|
|
3203
|
-
const next = await new Promise((
|
|
3204
|
-
this.waiters.push(
|
|
3461
|
+
const next = await new Promise((resolve5) => {
|
|
3462
|
+
this.waiters.push(resolve5);
|
|
3205
3463
|
});
|
|
3206
3464
|
if (next === null) return;
|
|
3207
3465
|
yield next;
|
|
@@ -3267,8 +3525,8 @@ var SseTransport = class {
|
|
|
3267
3525
|
constructor(opts) {
|
|
3268
3526
|
this.url = opts.url;
|
|
3269
3527
|
this.headers = opts.headers ?? {};
|
|
3270
|
-
this.endpointReady = new Promise((
|
|
3271
|
-
this.resolveEndpoint =
|
|
3528
|
+
this.endpointReady = new Promise((resolve5, reject) => {
|
|
3529
|
+
this.resolveEndpoint = resolve5;
|
|
3272
3530
|
this.rejectEndpoint = reject;
|
|
3273
3531
|
});
|
|
3274
3532
|
this.endpointReady.catch(() => void 0);
|
|
@@ -3295,8 +3553,8 @@ var SseTransport = class {
|
|
|
3295
3553
|
continue;
|
|
3296
3554
|
}
|
|
3297
3555
|
if (this.closed) return;
|
|
3298
|
-
const next = await new Promise((
|
|
3299
|
-
this.waiters.push(
|
|
3556
|
+
const next = await new Promise((resolve5) => {
|
|
3557
|
+
this.waiters.push(resolve5);
|
|
3300
3558
|
});
|
|
3301
3559
|
if (next === null) return;
|
|
3302
3560
|
yield next;
|
|
@@ -3495,8 +3753,8 @@ async function trySection(load) {
|
|
|
3495
3753
|
}
|
|
3496
3754
|
|
|
3497
3755
|
// src/code/edit-blocks.ts
|
|
3498
|
-
import { existsSync as
|
|
3499
|
-
import { dirname as dirname3, resolve as
|
|
3756
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3757
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
3500
3758
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3501
3759
|
function parseEditBlocks(text) {
|
|
3502
3760
|
const out = [];
|
|
@@ -3514,8 +3772,8 @@ function parseEditBlocks(text) {
|
|
|
3514
3772
|
return out;
|
|
3515
3773
|
}
|
|
3516
3774
|
function applyEditBlock(block, rootDir) {
|
|
3517
|
-
const absRoot =
|
|
3518
|
-
const absTarget =
|
|
3775
|
+
const absRoot = resolve4(rootDir);
|
|
3776
|
+
const absTarget = resolve4(absRoot, block.path);
|
|
3519
3777
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
3520
3778
|
return {
|
|
3521
3779
|
path: block.path,
|
|
@@ -3524,7 +3782,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3524
3782
|
};
|
|
3525
3783
|
}
|
|
3526
3784
|
const searchEmpty = block.search.length === 0;
|
|
3527
|
-
const exists =
|
|
3785
|
+
const exists = existsSync3(absTarget);
|
|
3528
3786
|
try {
|
|
3529
3787
|
if (!exists) {
|
|
3530
3788
|
if (!searchEmpty) {
|
|
@@ -3538,7 +3796,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3538
3796
|
writeFileSync2(absTarget, block.replace, "utf8");
|
|
3539
3797
|
return { path: block.path, status: "created" };
|
|
3540
3798
|
}
|
|
3541
|
-
const content =
|
|
3799
|
+
const content = readFileSync5(absTarget, "utf8");
|
|
3542
3800
|
if (searchEmpty) {
|
|
3543
3801
|
return {
|
|
3544
3802
|
path: block.path,
|
|
@@ -3565,19 +3823,19 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
3565
3823
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
3566
3824
|
}
|
|
3567
3825
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
3568
|
-
const absRoot =
|
|
3826
|
+
const absRoot = resolve4(rootDir);
|
|
3569
3827
|
const seen = /* @__PURE__ */ new Set();
|
|
3570
3828
|
const snapshots = [];
|
|
3571
3829
|
for (const b of blocks) {
|
|
3572
3830
|
if (seen.has(b.path)) continue;
|
|
3573
3831
|
seen.add(b.path);
|
|
3574
|
-
const abs =
|
|
3575
|
-
if (!
|
|
3832
|
+
const abs = resolve4(absRoot, b.path);
|
|
3833
|
+
if (!existsSync3(abs)) {
|
|
3576
3834
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3577
3835
|
continue;
|
|
3578
3836
|
}
|
|
3579
3837
|
try {
|
|
3580
|
-
snapshots.push({ path: b.path, prevContent:
|
|
3838
|
+
snapshots.push({ path: b.path, prevContent: readFileSync5(abs, "utf8") });
|
|
3581
3839
|
} catch {
|
|
3582
3840
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3583
3841
|
}
|
|
@@ -3585,9 +3843,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3585
3843
|
return snapshots;
|
|
3586
3844
|
}
|
|
3587
3845
|
function restoreSnapshots(snapshots, rootDir) {
|
|
3588
|
-
const absRoot =
|
|
3846
|
+
const absRoot = resolve4(rootDir);
|
|
3589
3847
|
return snapshots.map((snap) => {
|
|
3590
|
-
const abs =
|
|
3848
|
+
const abs = resolve4(absRoot, snap.path);
|
|
3591
3849
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
3592
3850
|
return {
|
|
3593
3851
|
path: snap.path,
|
|
@@ -3597,7 +3855,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
3597
3855
|
}
|
|
3598
3856
|
try {
|
|
3599
3857
|
if (snap.prevContent === null) {
|
|
3600
|
-
if (
|
|
3858
|
+
if (existsSync3(abs)) unlinkSync2(abs);
|
|
3601
3859
|
return {
|
|
3602
3860
|
path: snap.path,
|
|
3603
3861
|
status: "applied",
|
|
@@ -3620,8 +3878,8 @@ function sep() {
|
|
|
3620
3878
|
}
|
|
3621
3879
|
|
|
3622
3880
|
// src/code/prompt.ts
|
|
3623
|
-
import { existsSync as
|
|
3624
|
-
import { join as
|
|
3881
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
3882
|
+
import { join as join4 } from "path";
|
|
3625
3883
|
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.
|
|
3626
3884
|
|
|
3627
3885
|
# When to edit vs. when to explore
|
|
@@ -3670,18 +3928,19 @@ Rules:
|
|
|
3670
3928
|
- If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
|
|
3671
3929
|
`;
|
|
3672
3930
|
function codeSystemPrompt(rootDir) {
|
|
3673
|
-
const
|
|
3674
|
-
|
|
3931
|
+
const withMemory = applyProjectMemory(CODE_SYSTEM_PROMPT, rootDir);
|
|
3932
|
+
const gitignorePath = join4(rootDir, ".gitignore");
|
|
3933
|
+
if (!existsSync4(gitignorePath)) return withMemory;
|
|
3675
3934
|
let content;
|
|
3676
3935
|
try {
|
|
3677
|
-
content =
|
|
3936
|
+
content = readFileSync6(gitignorePath, "utf8");
|
|
3678
3937
|
} catch {
|
|
3679
|
-
return
|
|
3938
|
+
return withMemory;
|
|
3680
3939
|
}
|
|
3681
3940
|
const MAX = 2e3;
|
|
3682
3941
|
const truncated = content.length > MAX ? `${content.slice(0, MAX)}
|
|
3683
3942
|
\u2026 (truncated ${content.length - MAX} chars)` : content;
|
|
3684
|
-
return `${
|
|
3943
|
+
return `${withMemory}
|
|
3685
3944
|
|
|
3686
3945
|
# Project .gitignore
|
|
3687
3946
|
|
|
@@ -3694,15 +3953,15 @@ ${truncated}
|
|
|
3694
3953
|
}
|
|
3695
3954
|
|
|
3696
3955
|
// src/config.ts
|
|
3697
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
3956
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
3698
3957
|
import { homedir as homedir2 } from "os";
|
|
3699
|
-
import { dirname as dirname4, join as
|
|
3958
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
3700
3959
|
function defaultConfigPath() {
|
|
3701
|
-
return
|
|
3960
|
+
return join5(homedir2(), ".reasonix", "config.json");
|
|
3702
3961
|
}
|
|
3703
3962
|
function readConfig(path = defaultConfigPath()) {
|
|
3704
3963
|
try {
|
|
3705
|
-
const raw =
|
|
3964
|
+
const raw = readFileSync7(path, "utf8");
|
|
3706
3965
|
const parsed = JSON.parse(raw);
|
|
3707
3966
|
if (parsed && typeof parsed === "object") return parsed;
|
|
3708
3967
|
} catch {
|
|
@@ -3737,7 +3996,7 @@ function redactKey(key) {
|
|
|
3737
3996
|
}
|
|
3738
3997
|
|
|
3739
3998
|
// src/index.ts
|
|
3740
|
-
var VERSION = "0.4.
|
|
3999
|
+
var VERSION = "0.4.17";
|
|
3741
4000
|
export {
|
|
3742
4001
|
AppendOnlyLog,
|
|
3743
4002
|
CODE_SYSTEM_PROMPT,
|
|
@@ -3747,6 +4006,9 @@ export {
|
|
|
3747
4006
|
ImmutablePrefix,
|
|
3748
4007
|
MCP_PROTOCOL_VERSION,
|
|
3749
4008
|
McpClient,
|
|
4009
|
+
NeedsConfirmationError,
|
|
4010
|
+
PROJECT_MEMORY_FILE,
|
|
4011
|
+
PROJECT_MEMORY_MAX_CHARS,
|
|
3750
4012
|
SessionStats,
|
|
3751
4013
|
SseTransport,
|
|
3752
4014
|
StdioTransport,
|
|
@@ -3761,6 +4023,7 @@ export {
|
|
|
3761
4023
|
appendSessionMessage,
|
|
3762
4024
|
applyEditBlock,
|
|
3763
4025
|
applyEditBlocks,
|
|
4026
|
+
applyProjectMemory,
|
|
3764
4027
|
bridgeMcpTools,
|
|
3765
4028
|
claudeEquivalentCost,
|
|
3766
4029
|
codeSystemPrompt,
|
|
@@ -3774,6 +4037,7 @@ export {
|
|
|
3774
4037
|
fetchWithRetry,
|
|
3775
4038
|
flattenMcpResult,
|
|
3776
4039
|
flattenSchema,
|
|
4040
|
+
formatCommandResult,
|
|
3777
4041
|
formatLoopError,
|
|
3778
4042
|
formatSearchResults,
|
|
3779
4043
|
harvest,
|
|
@@ -3781,6 +4045,7 @@ export {
|
|
|
3781
4045
|
htmlToText,
|
|
3782
4046
|
inputCostUsd,
|
|
3783
4047
|
inspectMcpServer,
|
|
4048
|
+
isAllowed,
|
|
3784
4049
|
isJsonRpcError,
|
|
3785
4050
|
isPlanStateEmpty,
|
|
3786
4051
|
isPlausibleKey,
|
|
@@ -3788,6 +4053,7 @@ export {
|
|
|
3788
4053
|
loadApiKey,
|
|
3789
4054
|
loadDotenv,
|
|
3790
4055
|
loadSessionMessages,
|
|
4056
|
+
memoryEnabled,
|
|
3791
4057
|
nestArguments,
|
|
3792
4058
|
openTranscriptFile,
|
|
3793
4059
|
outputCostUsd,
|
|
@@ -3796,10 +4062,12 @@ export {
|
|
|
3796
4062
|
parseMojeekResults,
|
|
3797
4063
|
parseTranscript,
|
|
3798
4064
|
readConfig,
|
|
4065
|
+
readProjectMemory,
|
|
3799
4066
|
readTranscript,
|
|
3800
4067
|
recordFromLoopEvent,
|
|
3801
4068
|
redactKey,
|
|
3802
4069
|
registerFilesystemTools,
|
|
4070
|
+
registerShellTools,
|
|
3803
4071
|
registerWebTools,
|
|
3804
4072
|
renderMarkdown as renderDiffMarkdown,
|
|
3805
4073
|
renderSummaryTable as renderDiffSummary,
|
|
@@ -3807,6 +4075,7 @@ export {
|
|
|
3807
4075
|
replayFromFile,
|
|
3808
4076
|
restoreSnapshots,
|
|
3809
4077
|
runBranches,
|
|
4078
|
+
runCommand,
|
|
3810
4079
|
sanitizeName as sanitizeSessionName,
|
|
3811
4080
|
saveApiKey,
|
|
3812
4081
|
scavengeToolCalls,
|
|
@@ -3815,6 +4084,7 @@ export {
|
|
|
3815
4084
|
similarity,
|
|
3816
4085
|
snapshotBeforeEdits,
|
|
3817
4086
|
stripHallucinatedToolMarkup,
|
|
4087
|
+
tokenizeCommand,
|
|
3818
4088
|
truncateForModel,
|
|
3819
4089
|
webFetch,
|
|
3820
4090
|
webSearch,
|