@alint-js/tools-fs 0.0.20

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 ADDED
@@ -0,0 +1,34 @@
1
+ # `@alint-js/tools-fs`
2
+
3
+ Shared filesystem `AgentTool`s for alint plugins whose rules let an agent explore a project.
4
+ `createTools(cwd, options?)` returns four tools, all reporting paths relative to `cwd`:
5
+
6
+ - `read_file` — read a UTF-8 file (path relative to `cwd` or absolute).
7
+ - `list_files` — list files, with optional glob `patterns`, `ignore`, and `directory`.
8
+ - `search_files` — match file paths by substring.
9
+ - `search_in_files` — match file contents, returning `path:line: snippet` lines.
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { createTools, DEFAULT_IGNORE_PATTERNS } from '@alint-js/tools-fs'
15
+
16
+ // Built-in ignores:
17
+ const tools = createTools(ctx.cwd)
18
+ // Extend the builtins:
19
+ const py = createTools(ctx.cwd, { ignore: [...DEFAULT_IGNORE_PATTERNS, '**/.venv/**'] })
20
+ // Replace the builtins:
21
+ const bare = createTools(ctx.cwd, { ignore: ['**/generated/**'] })
22
+ ```
23
+
24
+ `options.ignore` **replaces** the builtins (`git/build/dist/node_modules/vendor`); spread
25
+ `DEFAULT_IGNORE_PATTERNS` to extend them instead. Per-call ignores from the agent always append to the ignore list set at tool creation time.
26
+ The underlying `listFiles` / `readFile` / `searchFiles` / `searchInFiles` are exported too.
27
+
28
+ ## When to use
29
+
30
+ - **Use** when a `ctx.agent` rule needs generic read/list/search tools to explore a project.
31
+
32
+ ## When not to use
33
+
34
+ - **Don't** for a single structured model judgement without an agent loop, or when you need real code search (regex/AST/ranking).
@@ -0,0 +1,28 @@
1
+ import { AgentTool } from "@alint-js/core/agent";
2
+
3
+ //#region src/list.d.ts
4
+ declare const DEFAULT_IGNORE_PATTERNS: readonly string[];
5
+ interface ListFilesOptions {
6
+ ignore?: readonly string[] | string;
7
+ patterns?: readonly string[] | string;
8
+ }
9
+ declare function listFiles(root: string, options?: ListFilesOptions): Promise<string[]>;
10
+ declare function toStringArray(value: readonly string[] | string | undefined): string[];
11
+ //#endregion
12
+ //#region src/read.d.ts
13
+ declare function readFile(cwd: string, inputPath: string | undefined): Promise<string>;
14
+ //#endregion
15
+ //#region src/search.d.ts
16
+ interface SearchOptions extends ListFilesOptions {
17
+ directory?: string;
18
+ }
19
+ declare function searchFiles(cwd: string, query: string, options?: SearchOptions): Promise<string>;
20
+ declare function searchInFiles(cwd: string, query: string, options?: SearchOptions): Promise<string>;
21
+ //#endregion
22
+ //#region src/index.d.ts
23
+ interface CreateToolsOptions {
24
+ ignore?: readonly string[] | string;
25
+ }
26
+ declare function createTools(cwd: string, options?: CreateToolsOptions): AgentTool[];
27
+ //#endregion
28
+ export { CreateToolsOptions, DEFAULT_IGNORE_PATTERNS, type ListFilesOptions, type SearchOptions, createTools, listFiles, readFile, searchFiles, searchInFiles, toStringArray };
package/dist/index.mjs ADDED
@@ -0,0 +1,144 @@
1
+ import { relative, resolve } from "node:path";
2
+ import { glob } from "tinyglobby";
3
+ import { readFile as readFile$1 } from "node:fs/promises";
4
+ //#region src/list.ts
5
+ const maxListedFiles = 160;
6
+ const DEFAULT_IGNORE_PATTERNS = [
7
+ "**/.git/**",
8
+ "**/build/**",
9
+ "**/dist/**",
10
+ "**/node_modules/**",
11
+ "**/vendor/**"
12
+ ];
13
+ async function listFiles(root, options = {}) {
14
+ try {
15
+ return (await glob(options.patterns ?? "**/*", {
16
+ absolute: true,
17
+ cwd: root,
18
+ ignore: toStringArray(options.ignore),
19
+ onlyFiles: true
20
+ })).slice(0, maxListedFiles);
21
+ } catch {
22
+ return [];
23
+ }
24
+ }
25
+ function toStringArray(value) {
26
+ if (Array.isArray(value)) return [...value];
27
+ return typeof value === "string" ? [value] : [];
28
+ }
29
+ //#endregion
30
+ //#region src/read.ts
31
+ async function readFile(cwd, inputPath) {
32
+ if (!inputPath) throw new TypeError("Expected file path");
33
+ return readFile$1(resolve(cwd, inputPath), "utf8");
34
+ }
35
+ //#endregion
36
+ //#region src/search.ts
37
+ const maxSearchResults = 24;
38
+ const maxSearchBytesPerFile = 2e5;
39
+ async function searchFiles(cwd, query, options = {}) {
40
+ return (await listFiles(resolve(cwd, options.directory ?? "."), options)).filter((file) => relative(cwd, file).includes(query)).slice(0, maxSearchResults).map((file) => relative(cwd, file)).join("\n");
41
+ }
42
+ async function searchInFiles(cwd, query, options = {}) {
43
+ const files = await listFiles(resolve(cwd, options.directory ?? "."), options);
44
+ const snippets = [];
45
+ for (const file of files) {
46
+ if (snippets.length >= maxSearchResults) break;
47
+ let text;
48
+ try {
49
+ text = await readFile$1(file, "utf8");
50
+ } catch {
51
+ continue;
52
+ }
53
+ if (text.length > maxSearchBytesPerFile || !text.includes(query)) continue;
54
+ for (const [index, line] of text.split("\n").entries()) {
55
+ if (!line.includes(query)) continue;
56
+ snippets.push(`${relative(cwd, file)}:${index + 1}: ${line.trim()}`);
57
+ if (snippets.length >= maxSearchResults) break;
58
+ }
59
+ }
60
+ return snippets.join("\n");
61
+ }
62
+ //#endregion
63
+ //#region src/index.ts
64
+ function createTools(cwd, options = {}) {
65
+ const baseIgnore = options.ignore === void 0 ? DEFAULT_IGNORE_PATTERNS : toStringArray(options.ignore);
66
+ return [
67
+ {
68
+ description: "Read a UTF-8 text file. The path may be relative to the project root or absolute.",
69
+ execute: (input) => readFile(cwd, getStringProperty(input, "path")),
70
+ name: "read_file",
71
+ parameters: {
72
+ additionalProperties: false,
73
+ properties: { path: { type: "string" } },
74
+ required: ["path"],
75
+ type: "object"
76
+ }
77
+ },
78
+ {
79
+ description: "List files under a directory with optional glob patterns and ignore patterns.",
80
+ execute: async (input) => {
81
+ const listOptions = getListOptions(input, baseIgnore);
82
+ return (await listFiles(resolve(cwd, listOptions.directory ?? "."), listOptions)).map((path) => relative(cwd, path)).join("\n");
83
+ },
84
+ name: "list_files",
85
+ parameters: fileSearchParameters(false)
86
+ },
87
+ {
88
+ description: "Search listed file paths by substring. Use search_in_files to search file contents.",
89
+ execute: (input) => searchFiles(cwd, getRequiredStringProperty(input, "query"), getListOptions(input, baseIgnore)),
90
+ name: "search_files",
91
+ parameters: fileSearchParameters(true)
92
+ },
93
+ {
94
+ description: "Search file contents by literal substring and return path:line snippets.",
95
+ execute: (input) => searchInFiles(cwd, getRequiredStringProperty(input, "query"), getListOptions(input, baseIgnore)),
96
+ name: "search_in_files",
97
+ parameters: fileSearchParameters(true)
98
+ }
99
+ ];
100
+ }
101
+ function fileSearchParameters(requiresQuery) {
102
+ return {
103
+ additionalProperties: false,
104
+ properties: {
105
+ directory: { type: "string" },
106
+ ignore: stringOrStringArraySchema(),
107
+ patterns: stringOrStringArraySchema(),
108
+ ...requiresQuery ? { query: { type: "string" } } : {}
109
+ },
110
+ required: requiresQuery ? ["query"] : [],
111
+ type: "object"
112
+ };
113
+ }
114
+ function getListOptions(input, baseIgnore) {
115
+ return {
116
+ directory: getStringProperty(input, "directory"),
117
+ ignore: [...baseIgnore, ...toStringArray(getStringOrStringArrayProperty(input, "ignore"))],
118
+ patterns: getStringOrStringArrayProperty(input, "patterns")
119
+ };
120
+ }
121
+ function getRequiredStringProperty(input, key) {
122
+ const value = getStringProperty(input, key);
123
+ if (value === void 0) throw new TypeError(`Expected tool input property "${key}" to be a string`);
124
+ return value;
125
+ }
126
+ function getStringOrStringArrayProperty(input, key) {
127
+ if (!input || typeof input !== "object" || Array.isArray(input)) return;
128
+ const value = input[key];
129
+ if (typeof value === "string") return value;
130
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
131
+ }
132
+ function getStringProperty(input, key) {
133
+ if (!input || typeof input !== "object" || Array.isArray(input)) return;
134
+ const value = input[key];
135
+ return typeof value === "string" ? value : void 0;
136
+ }
137
+ function stringOrStringArraySchema() {
138
+ return { anyOf: [{ type: "string" }, {
139
+ items: { type: "string" },
140
+ type: "array"
141
+ }] };
142
+ }
143
+ //#endregion
144
+ export { DEFAULT_IGNORE_PATTERNS, createTools, listFiles, readFile, searchFiles, searchInFiles, toStringArray };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@alint-js/tools-fs",
3
+ "type": "module",
4
+ "version": "0.0.20",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.mts",
8
+ "default": "./dist/index.mjs"
9
+ },
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "tinyglobby": "^0.2.17",
17
+ "@alint-js/core": "0.0.20"
18
+ },
19
+ "devDependencies": {
20
+ "tsdown": "^0.22.3",
21
+ "typescript": "^6.0.3"
22
+ },
23
+ "scripts": {
24
+ "build": "tsdown",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit"
26
+ }
27
+ }