reasonix 0.27.1 → 0.27.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 +59 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +46 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4163,6 +4163,7 @@ function applyMemoryStack(basePrompt, rootDir) {
|
|
|
4163
4163
|
// src/tools/filesystem.ts
|
|
4164
4164
|
import { promises as fs } from "fs";
|
|
4165
4165
|
import * as pathMod from "path";
|
|
4166
|
+
import picomatch2 from "picomatch";
|
|
4166
4167
|
|
|
4167
4168
|
// src/index/config.ts
|
|
4168
4169
|
import picomatch from "picomatch";
|
|
@@ -4257,6 +4258,20 @@ var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
|
4257
4258
|
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
4258
4259
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
4259
4260
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
4261
|
+
function displayRel(rootDir, full) {
|
|
4262
|
+
return pathMod.relative(rootDir, full).replaceAll("\\", "/");
|
|
4263
|
+
}
|
|
4264
|
+
var GLOB_METACHARS = /[*?{[]/;
|
|
4265
|
+
function compileNameFilter(filter) {
|
|
4266
|
+
if (!filter) return null;
|
|
4267
|
+
if (!GLOB_METACHARS.test(filter)) {
|
|
4268
|
+
const needle = filter.toLowerCase();
|
|
4269
|
+
return (name) => name.toLowerCase().includes(needle);
|
|
4270
|
+
}
|
|
4271
|
+
const matchPath = filter.includes("/");
|
|
4272
|
+
const isMatch = picomatch2(filter, { dot: true, nocase: true });
|
|
4273
|
+
return matchPath ? (_n, rel) => isMatch(rel) : (name) => isMatch(name);
|
|
4274
|
+
}
|
|
4260
4275
|
function isLikelyBinaryByName(name) {
|
|
4261
4276
|
const dot = name.lastIndexOf(".");
|
|
4262
4277
|
if (dot < 0) return false;
|
|
@@ -4461,7 +4476,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4461
4476
|
});
|
|
4462
4477
|
registry.register({
|
|
4463
4478
|
name: "search_files",
|
|
4464
|
-
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line.",
|
|
4479
|
+
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line. Skips dependency / VCS / build directories (node_modules, .git, dist, build, .next, target, .venv) by default.",
|
|
4465
4480
|
readOnly: true,
|
|
4466
4481
|
parameters: {
|
|
4467
4482
|
type: "object",
|
|
@@ -4470,6 +4485,10 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4470
4485
|
pattern: {
|
|
4471
4486
|
type: "string",
|
|
4472
4487
|
description: "Substring (or regex) to match against filenames."
|
|
4488
|
+
},
|
|
4489
|
+
include_deps: {
|
|
4490
|
+
type: "boolean",
|
|
4491
|
+
description: "When true, also walk node_modules / .git / dist / build / etc. Off by default \u2014 most filename searches are about the user's own code."
|
|
4473
4492
|
}
|
|
4474
4493
|
},
|
|
4475
4494
|
required: ["pattern"]
|
|
@@ -4477,6 +4496,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4477
4496
|
fn: async (args) => {
|
|
4478
4497
|
const startAbs = safePath(args.path ?? ".");
|
|
4479
4498
|
const needle = args.pattern.toLowerCase();
|
|
4499
|
+
const includeDeps = args.include_deps === true;
|
|
4480
4500
|
let re = null;
|
|
4481
4501
|
try {
|
|
4482
4502
|
re = new RegExp(args.pattern, "i");
|
|
@@ -4497,7 +4517,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4497
4517
|
const lower = e.name.toLowerCase();
|
|
4498
4518
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
4499
4519
|
if (hit) {
|
|
4500
|
-
const rel =
|
|
4520
|
+
const rel = displayRel(rootDir, full);
|
|
4501
4521
|
if (totalBytes + rel.length + 1 > maxListBytes) {
|
|
4502
4522
|
matches.push("[\u2026 search truncated \u2014 refine pattern \u2026]");
|
|
4503
4523
|
return;
|
|
@@ -4505,7 +4525,10 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4505
4525
|
matches.push(rel);
|
|
4506
4526
|
totalBytes += rel.length + 1;
|
|
4507
4527
|
}
|
|
4508
|
-
if (e.isDirectory())
|
|
4528
|
+
if (e.isDirectory()) {
|
|
4529
|
+
if (!includeDeps && SKIP_DIR_NAMES.has(e.name)) continue;
|
|
4530
|
+
await walk2(full);
|
|
4531
|
+
}
|
|
4509
4532
|
}
|
|
4510
4533
|
};
|
|
4511
4534
|
await walk2(startAbs);
|
|
@@ -4529,7 +4552,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4529
4552
|
},
|
|
4530
4553
|
glob: {
|
|
4531
4554
|
type: "string",
|
|
4532
|
-
description: "Optional
|
|
4555
|
+
description: "Optional filename filter. Real glob when the value contains `*`, `?`, `{`, or `[` \u2014 e.g. '*.ts', '**/*.tsx', 'src/**/*.{ts,tsx}'. Plain substring otherwise \u2014 e.g. '.ts' (suffix), 'test' (anywhere in the name). Patterns containing `/` match against the path relative to the search root; otherwise just the basename."
|
|
4533
4556
|
},
|
|
4534
4557
|
case_sensitive: {
|
|
4535
4558
|
type: "boolean",
|
|
@@ -4546,7 +4569,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4546
4569
|
const startAbs = safePath(args.path ?? ".");
|
|
4547
4570
|
const caseSensitive = args.case_sensitive === true;
|
|
4548
4571
|
const includeDeps = args.include_deps === true;
|
|
4549
|
-
const
|
|
4572
|
+
const nameMatch = compileNameFilter(typeof args.glob === "string" ? args.glob : null);
|
|
4550
4573
|
let re = null;
|
|
4551
4574
|
try {
|
|
4552
4575
|
re = new RegExp(args.pattern, caseSensitive ? "" : "i");
|
|
@@ -4574,9 +4597,9 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4574
4597
|
continue;
|
|
4575
4598
|
}
|
|
4576
4599
|
if (!e.isFile()) continue;
|
|
4577
|
-
if (nameFilter && !e.name.toLowerCase().includes(nameFilter)) continue;
|
|
4578
|
-
if (isLikelyBinaryByName(e.name)) continue;
|
|
4579
4600
|
const full = pathMod.join(dir, e.name);
|
|
4601
|
+
if (nameMatch && !nameMatch(e.name, displayRel(rootDir, full))) continue;
|
|
4602
|
+
if (isLikelyBinaryByName(e.name)) continue;
|
|
4580
4603
|
let stat2;
|
|
4581
4604
|
try {
|
|
4582
4605
|
stat2 = await fs.stat(full);
|
|
@@ -4593,7 +4616,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4593
4616
|
const firstNul = raw.indexOf(0);
|
|
4594
4617
|
if (firstNul !== -1 && firstNul < 8 * 1024) continue;
|
|
4595
4618
|
const text = raw.toString("utf8");
|
|
4596
|
-
const rel =
|
|
4619
|
+
const rel = displayRel(rootDir, full);
|
|
4597
4620
|
const lines = text.split(/\r?\n/);
|
|
4598
4621
|
for (let li = 0; li < lines.length; li++) {
|
|
4599
4622
|
const line = lines[li];
|
|
@@ -4658,7 +4681,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4658
4681
|
const abs = safePath(args.path);
|
|
4659
4682
|
await fs.mkdir(pathMod.dirname(abs), { recursive: true });
|
|
4660
4683
|
await fs.writeFile(abs, args.content, "utf8");
|
|
4661
|
-
return `wrote ${args.content.length} chars to ${
|
|
4684
|
+
return `wrote ${args.content.length} chars to ${displayRel(rootDir, abs)}`;
|
|
4662
4685
|
}
|
|
4663
4686
|
});
|
|
4664
4687
|
registry.register({
|
|
@@ -4684,17 +4707,17 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4684
4707
|
const adaptedReplace = args.replace.replace(/\r?\n/g, le);
|
|
4685
4708
|
const firstIdx = before.indexOf(adaptedSearch);
|
|
4686
4709
|
if (firstIdx < 0) {
|
|
4687
|
-
throw new Error(`edit_file: search text not found in ${
|
|
4710
|
+
throw new Error(`edit_file: search text not found in ${displayRel(rootDir, abs)}`);
|
|
4688
4711
|
}
|
|
4689
4712
|
const nextIdx = before.indexOf(adaptedSearch, firstIdx + 1);
|
|
4690
4713
|
if (nextIdx >= 0) {
|
|
4691
4714
|
throw new Error(
|
|
4692
|
-
`edit_file: search text appears multiple times in ${
|
|
4715
|
+
`edit_file: search text appears multiple times in ${displayRel(rootDir, abs)} \u2014 include more context to disambiguate`
|
|
4693
4716
|
);
|
|
4694
4717
|
}
|
|
4695
4718
|
const after = before.slice(0, firstIdx) + adaptedReplace + before.slice(firstIdx + adaptedSearch.length);
|
|
4696
4719
|
await fs.writeFile(abs, after, "utf8");
|
|
4697
|
-
const rel =
|
|
4720
|
+
const rel = displayRel(rootDir, abs);
|
|
4698
4721
|
const header = `edited ${rel} (${adaptedSearch.length}\u2192${adaptedReplace.length} chars)`;
|
|
4699
4722
|
const startLine = before.slice(0, firstIdx).split(/\r?\n/).length;
|
|
4700
4723
|
const diff = renderEditDiff(adaptedSearch, adaptedReplace, startLine);
|
|
@@ -4713,7 +4736,7 @@ ${diff}`;
|
|
|
4713
4736
|
fn: async (args) => {
|
|
4714
4737
|
const abs = safePath(args.path);
|
|
4715
4738
|
await fs.mkdir(abs, { recursive: true });
|
|
4716
|
-
return `created ${
|
|
4739
|
+
return `created ${displayRel(rootDir, abs)}/`;
|
|
4717
4740
|
}
|
|
4718
4741
|
});
|
|
4719
4742
|
registry.register({
|
|
@@ -4732,7 +4755,7 @@ ${diff}`;
|
|
|
4732
4755
|
const dst = safePath(args.destination);
|
|
4733
4756
|
await fs.mkdir(pathMod.dirname(dst), { recursive: true });
|
|
4734
4757
|
await fs.rename(src, dst);
|
|
4735
|
-
return `moved ${
|
|
4758
|
+
return `moved ${displayRel(rootDir, src)} \u2192 ${displayRel(rootDir, dst)}`;
|
|
4736
4759
|
}
|
|
4737
4760
|
});
|
|
4738
4761
|
return registry;
|
|
@@ -6044,6 +6067,14 @@ function parseCommandChain(cmd) {
|
|
|
6044
6067
|
}
|
|
6045
6068
|
segments.push(parseSegment(trimmed));
|
|
6046
6069
|
}
|
|
6070
|
+
for (const seg of segments) {
|
|
6071
|
+
const cmdName = seg.argv[0] ?? "";
|
|
6072
|
+
if (cmdName.toLowerCase() === "cd") {
|
|
6073
|
+
throw new UnsupportedSyntaxError(
|
|
6074
|
+
"cd in parsed command chains does not change cwd for later segments. Use a command-native cwd flag instead, such as `npm --prefix <dir> run <script>`, `git -C <dir> ...`, or `cargo -C <dir> ...`."
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
6077
|
+
}
|
|
6047
6078
|
if (ops.length === 0 && segments[0].redirects.length === 0) return null;
|
|
6048
6079
|
return { segments, ops };
|
|
6049
6080
|
}
|
|
@@ -6711,7 +6742,7 @@ function registerShellTools(registry, opts) {
|
|
|
6711
6742
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
6712
6743
|
registry.register({
|
|
6713
6744
|
name: "run_command",
|
|
6714
|
-
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 Chain operators `|`, `||`, `&&`, `;` ARE supported \u2014 parsed natively, no shell invoked, so semantics are identical on Windows / macOS / Linux. Each chain segment is allowlist-checked individually: `git status | grep main` runs if both halves are allowed.\n\u2022 File redirects ARE supported: `>` truncate, `>>` append, `<` stdin from file, `2>` / `2>>` stderr to file, `2>&1` merge stderr\u2192stdout, `&>` both to file. Targets resolve relative to the project root. At most one redirect per fd per segment.\n\u2022 Background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, and process substitution `<(\u2026)` are NOT supported. Wrap a literal `&` arg in quotes; for input use a `<` file or the binary's own --input flag.\n\u2022 Env-var expansion `$VAR` is NOT performed \u2014 `$VAR` is passed as a literal string. Use the binary's own --env flag or substitute the value yourself.\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project.
|
|
6745
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 Chain operators `|`, `||`, `&&`, `;` ARE supported \u2014 parsed natively, no shell invoked, so semantics are identical on Windows / macOS / Linux. Each chain segment is allowlist-checked individually: `git status | grep main` runs if both halves are allowed.\n\u2022 File redirects ARE supported: `>` truncate, `>>` append, `<` stdin from file, `2>` / `2>>` stderr to file, `2>&1` merge stderr\u2192stdout, `&>` both to file. Targets resolve relative to the project root. At most one redirect per fd per segment.\n\u2022 Background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, and process substitution `<(\u2026)` are NOT supported. Wrap a literal `&` arg in quotes; for input use a `<` file or the binary's own --input flag.\n\u2022 Env-var expansion `$VAR` is NOT performed \u2014 `$VAR` is passed as a literal string. Use the binary's own --env flag or substitute the value yourself.\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project. `cd` also does not persist within parsed chains like `cd dir && command`. Use a command-native cwd flag instead: `npm --prefix <dir> run <script>`, `npm --prefix <dir> exec -- <bin>`, `git -C <dir> ...`, `cargo -C <dir> ...`, `pytest <dir>/tests`.\n\u2022 Glob patterns (`*.ts`) are passed through as literal arguments \u2014 no shell expansion. Use `grep -r`, `rg`, `find -name`, etc.\n\u2022 Avoid commands with unbounded output (`netstat -ano`, `find /`, etc.) \u2014 they waste tokens. Filter at source: `netstat -ano -p TCP`, `find src -name '*.ts'`, `grep -c`, `wc -l`.\n\nCommon read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
6715
6746
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
6716
6747
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
6717
6748
|
// during planning. Anything that would otherwise trigger a
|