march-cli 0.1.11 → 0.1.12
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/package.json
CHANGED
package/src/agent/tool-names.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MARCH_BASE_TOOL_NAMES = ["grep", "ls"];
|
|
1
|
+
export const MARCH_BASE_TOOL_NAMES = ["grep", "find", "ls"];
|
package/src/agent/tools.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { Type } from "typebox";
|
|
|
3
3
|
import { createCommandExecTool } from "./command-exec-tool.mjs";
|
|
4
4
|
import { createContextStatsTool } from "./context-stats-tool.mjs";
|
|
5
5
|
import { createEditFileTool } from "./file-edit-tool.mjs";
|
|
6
|
-
import { createFindTool } from "./find-tool.mjs";
|
|
7
6
|
import { createReadFileTool } from "./read-file-tool.mjs";
|
|
8
7
|
import { toolText } from "./tool-result.mjs";
|
|
9
8
|
import { createShellTools } from "../shell/tools.mjs";
|
|
@@ -14,7 +13,6 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shel
|
|
|
14
13
|
const commandExecTool = createCommandExecTool({ cwd });
|
|
15
14
|
const contextStatsTool = createContextStatsTool({ engine });
|
|
16
15
|
const editFileTool = createEditFileTool({ engine, ui, lspService });
|
|
17
|
-
const findTool = createFindTool({ cwd });
|
|
18
16
|
const readFileTool = createReadFileTool({ engine });
|
|
19
17
|
|
|
20
18
|
const tools = [
|
|
@@ -22,7 +20,6 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shel
|
|
|
22
20
|
contextStatsTool,
|
|
23
21
|
commandExecTool,
|
|
24
22
|
editFileTool,
|
|
25
|
-
findTool,
|
|
26
23
|
...createShellTools(shellRuntime),
|
|
27
24
|
...memoryTools,
|
|
28
25
|
...mcpTools,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "./languages.mjs";
|
|
20
20
|
|
|
21
21
|
const RESOURCE_DIR = join(dirname(fileURLToPath(import.meta.url)), "tree-sitter");
|
|
22
|
-
const ENCODER = new TextEncoder();
|
|
23
22
|
|
|
24
23
|
let initPromise;
|
|
25
24
|
let initialized = false;
|
|
@@ -87,11 +86,10 @@ function treeSitterRuns(text, lang) {
|
|
|
87
86
|
if (!initialized || !parser || !text) return null;
|
|
88
87
|
try {
|
|
89
88
|
const tree = parser.parse(text);
|
|
90
|
-
const byteToIndex = buildByteToIndex(text);
|
|
91
89
|
const scopes = Array.from({ length: text.length }, () => ({ scope: "default", priority: 0 }));
|
|
92
90
|
const query = queries.get(lang);
|
|
93
|
-
if (query) applyQueryScopes(query, tree.rootNode,
|
|
94
|
-
collectNodeScopes(tree.rootNode,
|
|
91
|
+
if (query) applyQueryScopes(query, tree.rootNode, scopes);
|
|
92
|
+
collectNodeScopes(tree.rootNode, scopes);
|
|
95
93
|
return scopesToRuns(text, scopes);
|
|
96
94
|
} catch {
|
|
97
95
|
return null;
|
|
@@ -109,11 +107,11 @@ function loadHighlightQuery(language, queryFile) {
|
|
|
109
107
|
}
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
function applyQueryScopes(query, rootNode,
|
|
110
|
+
function applyQueryScopes(query, rootNode, scopes) {
|
|
113
111
|
for (const capture of query.captures(rootNode)) {
|
|
114
112
|
const scope = captureScope(capture.name);
|
|
115
113
|
if (!scope) continue;
|
|
116
|
-
applyScope(scopes,
|
|
114
|
+
applyScope(scopes, capture.node.startIndex, capture.node.endIndex, scope);
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
|
|
@@ -135,10 +133,10 @@ function captureScope(name) {
|
|
|
135
133
|
return null;
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
function collectNodeScopes(node,
|
|
136
|
+
function collectNodeScopes(node, scopes) {
|
|
139
137
|
const scope = classifyNode(node);
|
|
140
|
-
if (scope) applyScope(scopes,
|
|
141
|
-
for (const child of node.children ?? []) collectNodeScopes(child,
|
|
138
|
+
if (scope) applyScope(scopes, node.startIndex, node.endIndex, scope);
|
|
139
|
+
for (const child of node.children ?? []) collectNodeScopes(child, scopes);
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
function classifyNode(node) {
|
|
@@ -259,19 +257,4 @@ export function styleSyntax(text, scope = "default", bg = "") {
|
|
|
259
257
|
return `\x1b[${codes.join(";")}m${text}${R}`;
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
function buildByteToIndex(text) {
|
|
263
|
-
const map = [];
|
|
264
|
-
let byte = 0;
|
|
265
|
-
for (let index = 0; index < text.length;) {
|
|
266
|
-
const codePoint = text.codePointAt(index);
|
|
267
|
-
const char = String.fromCodePoint(codePoint);
|
|
268
|
-
const bytes = ENCODER.encode(char).length;
|
|
269
|
-
for (let i = 0; i < bytes; i++) map[byte + i] = index;
|
|
270
|
-
byte += bytes;
|
|
271
|
-
index += char.length;
|
|
272
|
-
}
|
|
273
|
-
map[byte] = text.length;
|
|
274
|
-
return map;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
260
|
void initializeTreeSitterHighlighting();
|
|
@@ -25,7 +25,7 @@ export const LANG_ALIASES = new Map([
|
|
|
25
25
|
["cs", "csharp"], ["css", "css"], ["cts", "typescript"], ["csharp", "csharp"], ["cxx", "cpp"],
|
|
26
26
|
["diff", "diff"], ["go", "go"], ["h", "c"], ["hh", "cpp"], ["htm", "html"], ["html", "html"],
|
|
27
27
|
["hpp", "cpp"], ["hxx", "cpp"], ["java", "java"], ["javascript", "javascript"], ["js", "javascript"],
|
|
28
|
-
["json", "json"], ["jsonc", "json"], ["jsx", "
|
|
28
|
+
["json", "json"], ["jsonc", "json"], ["jsx", "tsx"], ["mjs", "javascript"], ["mts", "typescript"],
|
|
29
29
|
["patch", "diff"], ["php", "php"], ["py", "python"], ["python", "python"], ["rb", "ruby"],
|
|
30
30
|
["rs", "rust"], ["ruby", "ruby"], ["rust", "rust"], ["sh", "bash"], ["toml", "toml"],
|
|
31
31
|
["ts", "typescript"], ["tsx", "tsx"], ["typescript", "typescript"], ["yaml", "yaml"], ["yml", "yaml"],
|
package/src/agent/find-tool.mjs
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import { toolText } from "./tool-result.mjs";
|
|
6
|
-
|
|
7
|
-
const DEFAULT_LIMIT = 1000;
|
|
8
|
-
const DEFAULT_IGNORES = new Set([".git", "node_modules"]);
|
|
9
|
-
|
|
10
|
-
export function createFindTool({ cwd }) {
|
|
11
|
-
return defineTool({
|
|
12
|
-
name: "find",
|
|
13
|
-
label: "Find Files",
|
|
14
|
-
description: "Find files by glob pattern. Pattern is matched relative to the search directory. Basename-only patterns like '*.mjs' search recursively, so find('*.mjs', path:'src') and find('src/**/*.mjs') both work.",
|
|
15
|
-
parameters: Type.Object({
|
|
16
|
-
pattern: Type.String({ description: "Glob pattern to match files, e.g. '*.mjs', '**/*.json', or 'src/**/*.test.mjs'" }),
|
|
17
|
-
path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
|
|
18
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of results (default 1000)" })),
|
|
19
|
-
}),
|
|
20
|
-
execute: async (_toolCallId, params) => executeFind({ cwd, ...params }),
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function executeFind({ cwd, pattern, path = ".", limit = DEFAULT_LIMIT }) {
|
|
25
|
-
const searchRoot = resolveSearchRoot(cwd, path);
|
|
26
|
-
const trimmedPattern = String(pattern ?? "").trim().replaceAll("\\", "/");
|
|
27
|
-
if (!trimmedPattern) return toolText("Error: pattern is required", { error: true });
|
|
28
|
-
const effectivePattern = normalizePattern(trimmedPattern);
|
|
29
|
-
|
|
30
|
-
const max = Math.max(1, Number(limit) || DEFAULT_LIMIT);
|
|
31
|
-
let files;
|
|
32
|
-
try {
|
|
33
|
-
files = listFiles(searchRoot);
|
|
34
|
-
} catch (err) {
|
|
35
|
-
return toolText(`Error: ${err.message}`, { error: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const matches = [];
|
|
39
|
-
for (const file of files) {
|
|
40
|
-
const rel = toPosix(relative(searchRoot, file));
|
|
41
|
-
if (!matchesGlob(effectivePattern, rel)) continue;
|
|
42
|
-
matches.push(rel);
|
|
43
|
-
if (matches.length >= max) break;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (matches.length === 0) return toolText("No files found matching pattern", { pattern: trimmedPattern, effectivePattern, path: searchRoot, count: 0 });
|
|
47
|
-
const limitHint = matches.length >= max ? `\n\n[Results truncated to ${max}. Increase limit or refine pattern.]` : "";
|
|
48
|
-
return toolText(`${matches.join("\n")}${limitHint}`, {
|
|
49
|
-
pattern: trimmedPattern,
|
|
50
|
-
effectivePattern: effectivePattern === trimmedPattern ? undefined : effectivePattern,
|
|
51
|
-
path: searchRoot,
|
|
52
|
-
count: matches.length,
|
|
53
|
-
resultLimitReached: matches.length >= max ? max : undefined,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function normalizePattern(pattern) {
|
|
58
|
-
if (pattern.includes("/") || pattern.includes("**")) return pattern;
|
|
59
|
-
return `**/${pattern}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function resolveSearchRoot(cwd, path) {
|
|
63
|
-
const raw = String(path || ".");
|
|
64
|
-
return isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function listFiles(root) {
|
|
68
|
-
const out = [];
|
|
69
|
-
walk(root, out);
|
|
70
|
-
return out.sort((a, b) => toPosix(a).localeCompare(toPosix(b)));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function walk(dir, out) {
|
|
74
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
75
|
-
if (entry.isDirectory() && DEFAULT_IGNORES.has(entry.name)) continue;
|
|
76
|
-
const path = resolve(dir, entry.name);
|
|
77
|
-
if (entry.isDirectory()) walk(path, out);
|
|
78
|
-
else if (entry.isFile()) out.push(path);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function matchesGlob(pattern, candidate) {
|
|
83
|
-
return matchSegments(splitGlob(pattern), splitGlob(candidate));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function matchSegments(patternSegments, candidateSegments) {
|
|
87
|
-
if (patternSegments.length === 0) return candidateSegments.length === 0;
|
|
88
|
-
const [head, ...tail] = patternSegments;
|
|
89
|
-
if (head === "**") {
|
|
90
|
-
if (matchSegments(tail, candidateSegments)) return true;
|
|
91
|
-
return candidateSegments.length > 0 && matchSegments(patternSegments, candidateSegments.slice(1));
|
|
92
|
-
}
|
|
93
|
-
if (candidateSegments.length === 0) return false;
|
|
94
|
-
return matchSegment(head, candidateSegments[0]) && matchSegments(tail, candidateSegments.slice(1));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function matchSegment(pattern, candidate) {
|
|
98
|
-
const regex = new RegExp(`^${escapeRegex(pattern).replaceAll("\\*", "[^/]*").replaceAll("\\?", "[^/]")}$`);
|
|
99
|
-
return regex.test(candidate);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function splitGlob(value) {
|
|
103
|
-
return String(value).split("/").filter(Boolean);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function toPosix(value) {
|
|
107
|
-
return String(value).replaceAll("\\", "/");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function escapeRegex(value) {
|
|
111
|
-
return String(value).replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
112
|
-
}
|