@arnilo/prism-coding-agent 0.0.17 → 0.0.19
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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/atomic-write.d.ts +3 -0
- package/dist/atomic-write.js +24 -0
- package/dist/edit.js +3 -2
- package/dist/repository.d.ts +2 -2
- package/dist/repository.js +9 -27
- package/dist/search.d.ts +1 -1
- package/dist/search.js +11 -5
- package/dist/write.js +3 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.19] - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Released with exact 0.0.19 graph.
|
|
7
|
+
|
|
8
|
+
## [0.0.18] - 2026-07-30
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `repo_search` is literal-only: `mode: "regex"` removed from the tool schema; `compileSearchPattern` no longer compiles `RegExp` (ReDoS mitigation).
|
|
12
|
+
- Default `write`/`edit` local `writeFile` uses same-directory temp + `rename` for crash-safe replacement.
|
|
13
|
+
|
|
3
14
|
## [0.0.17] - 2026-07-29
|
|
4
15
|
|
|
5
16
|
### Added
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ const askUser = createAskUserDecisionTool({
|
|
|
73
73
|
| `write` | `{ path, content }` | Bounded UTF-8 input; `Successfully wrote N bytes (M lines) to <abs>`. |
|
|
74
74
|
| `edit` | `{ path, edits: [{oldText,newText}] }` | Bounded target/input/count; `Successfully replaced N block(s)` + diff metadata. |
|
|
75
75
|
| `repo_list` | `{ path?, includeHidden?, maxDepth?, maxResults?, offset? }` | Deterministic relative entries; skips hidden/excluded basenames; does not follow symlinks; paginates with `nextOffset`. |
|
|
76
|
-
| `repo_search` | `{ query, path?, mode?, caseSensitive?, includeHidden?, context?, maxMatches? }` | Literal
|
|
76
|
+
| `repo_search` | `{ query, path?, mode?, caseSensitive?, includeHidden?, context?, maxMatches? }` | Literal substring matches with context; skips binary/excluded paths; finite scan/match/time caps. |
|
|
77
77
|
| `git_*` / `coding_check` | via `createGitTools(cwd, { commitIdentity, checks? })` | Opt-in structured Git status/diff/branch/worktree/apply/commit/PR-handoff and named checks. Not in `createCodingTools()`. |
|
|
78
78
|
| `ask_user_decision` | via `createAskUserDecisionTool({ ask })` | Opt-in user choice: question + options (3 pros/3 cons); `selectionMode` single\|multiple; `allowCustom` for XOR free-text; host `ask` returns `selectedId` / `selectedIds` / `customText`. Durable: `suspendAskUserDecision` + resume validators. Not in default aggregators. |
|
|
79
79
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-directory temp + rename for crash-safe UTF-8 file replacement.
|
|
3
|
+
* Custom WriteOperations/EditOperations should provide equivalent durability.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { rename as fsRename, unlink as fsUnlink, writeFile as fsWriteFile } from "node:fs/promises";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
export async function atomicWriteUtf8File(targetPath, content, options) {
|
|
9
|
+
const dir = dirname(targetPath);
|
|
10
|
+
const tempPath = join(dir, `.prism-write-${randomBytes(8).toString("hex")}`);
|
|
11
|
+
try {
|
|
12
|
+
await fsWriteFile(tempPath, content, { encoding: "utf-8", signal: options?.signal });
|
|
13
|
+
if (options?.signal?.aborted) {
|
|
14
|
+
await fsUnlink(tempPath).catch(() => { });
|
|
15
|
+
throw new Error("Operation aborted");
|
|
16
|
+
}
|
|
17
|
+
await fsRename(tempPath, targetPath);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
await fsUnlink(tempPath).catch(() => { });
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=atomic-write.js.map
|
package/dist/edit.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { Buffer } from "node:buffer";
|
|
23
23
|
import { constants } from "node:fs";
|
|
24
|
-
import { access as fsAccess, stat as fsStat
|
|
24
|
+
import { access as fsAccess, stat as fsStat } from "node:fs/promises";
|
|
25
|
+
import { atomicWriteUtf8File } from "./atomic-write.js";
|
|
25
26
|
import { readFileBounded } from "./bounded-file.js";
|
|
26
27
|
import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
|
|
27
28
|
import { enforceExecutionPolicy } from "./execution-policy.js";
|
|
@@ -30,7 +31,7 @@ import { DEFAULT_MAX_EDIT_FILE_BYTES, DEFAULT_MAX_EDIT_INPUT_BYTES, DEFAULT_MAX_
|
|
|
30
31
|
import { resolveToCwd } from "./path-utils.js";
|
|
31
32
|
const defaultEditOperations = {
|
|
32
33
|
readFile: (path, options) => readFileBounded(path, options.maxBytes, options.signal),
|
|
33
|
-
writeFile: (path, content, options) =>
|
|
34
|
+
writeFile: (path, content, options) => atomicWriteUtf8File(path, content, { signal: options?.signal }),
|
|
34
35
|
access: (path) => fsAccess(path, constants.R_OK | constants.W_OK),
|
|
35
36
|
statFile: async (path) => ({ size: (await fsStat(path)).size }),
|
|
36
37
|
};
|
package/dist/repository.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ export interface RepositorySearchRequest {
|
|
|
85
85
|
readonly root: string;
|
|
86
86
|
readonly query: string;
|
|
87
87
|
readonly path?: string;
|
|
88
|
-
readonly mode?: "literal"
|
|
88
|
+
readonly mode?: "literal";
|
|
89
89
|
readonly caseSensitive?: boolean;
|
|
90
90
|
readonly includeHidden?: boolean;
|
|
91
91
|
readonly exclude?: readonly string[];
|
|
@@ -116,7 +116,7 @@ export declare function resolveRepoPath(root: string, inputPath: string | undefi
|
|
|
116
116
|
rootReal: string;
|
|
117
117
|
}>;
|
|
118
118
|
export declare function isBinaryBuffer(buffer: Buffer): boolean;
|
|
119
|
-
export declare function compileSearchPattern(query: string,
|
|
119
|
+
export declare function compileSearchPattern(query: string, caseSensitive: boolean, maxPatternBytes: number): {
|
|
120
120
|
testLine: (line: string) => {
|
|
121
121
|
column: number;
|
|
122
122
|
} | null;
|
package/dist/repository.js
CHANGED
|
@@ -119,46 +119,28 @@ export function isBinaryBuffer(buffer) {
|
|
|
119
119
|
}
|
|
120
120
|
return false;
|
|
121
121
|
}
|
|
122
|
-
export function compileSearchPattern(query,
|
|
122
|
+
export function compileSearchPattern(query, caseSensitive, maxPatternBytes) {
|
|
123
123
|
const patternBytes = Buffer.byteLength(query, "utf8");
|
|
124
124
|
if (patternBytes < 1)
|
|
125
125
|
throw new RepositoryError("query must be non-empty");
|
|
126
126
|
if (patternBytes > maxPatternBytes) {
|
|
127
127
|
throw new RepositoryError(`query exceeds ${maxPatternBytes} byte pattern limit`);
|
|
128
128
|
}
|
|
129
|
-
if (
|
|
130
|
-
if (caseSensitive) {
|
|
131
|
-
return {
|
|
132
|
-
patternBytes,
|
|
133
|
-
testLine: (line) => {
|
|
134
|
-
const column = line.indexOf(query);
|
|
135
|
-
return column >= 0 ? { column: column + 1 } : null;
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
const needle = query.toLowerCase();
|
|
129
|
+
if (caseSensitive) {
|
|
140
130
|
return {
|
|
141
131
|
patternBytes,
|
|
142
132
|
testLine: (line) => {
|
|
143
|
-
const column = line.
|
|
133
|
+
const column = line.indexOf(query);
|
|
144
134
|
return column >= 0 ? { column: column + 1 } : null;
|
|
145
135
|
},
|
|
146
136
|
};
|
|
147
137
|
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
regex = new RegExp(query, caseSensitive ? "u" : "iu");
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
-
throw new RepositoryError(`invalid regular expression: ${message}`);
|
|
155
|
-
}
|
|
138
|
+
const needle = query.toLowerCase();
|
|
156
139
|
return {
|
|
157
140
|
patternBytes,
|
|
158
141
|
testLine: (line) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return match && match.index !== undefined ? { column: match.index + 1 } : null;
|
|
142
|
+
const column = line.toLowerCase().indexOf(needle);
|
|
143
|
+
return column >= 0 ? { column: column + 1 } : null;
|
|
162
144
|
},
|
|
163
145
|
};
|
|
164
146
|
}
|
|
@@ -477,11 +459,11 @@ async function searchFileLines(absolutePath, relativePath, testLine, options) {
|
|
|
477
459
|
}
|
|
478
460
|
async function searchLocal(request, defaults) {
|
|
479
461
|
const mode = request.mode ?? "literal";
|
|
480
|
-
if (mode !== "literal"
|
|
481
|
-
throw new RepositoryError(`unsupported search mode: ${String(mode)}`);
|
|
462
|
+
if (mode !== "literal") {
|
|
463
|
+
throw new RepositoryError(`unsupported search mode: ${String(mode)} (literal only)`);
|
|
482
464
|
}
|
|
483
465
|
const caseSensitive = request.caseSensitive === true;
|
|
484
|
-
const { testLine } = compileSearchPattern(request.query,
|
|
466
|
+
const { testLine } = compileSearchPattern(request.query, caseSensitive, defaults.maxPatternBytes);
|
|
485
467
|
const resolved = await resolveRepoPath(request.root, request.path);
|
|
486
468
|
const maxMatches = validateCodingLimit("maxMatches", request.maxMatches ?? defaults.maxMatches, HARD_MAX_SEARCH_MATCHES);
|
|
487
469
|
const context = validateCodingLimitAllowZero("context", request.context ?? defaults.maxContextLines, HARD_MAX_SEARCH_CONTEXT_LINES);
|
package/dist/search.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `repo_search` tool: bounded native literal
|
|
2
|
+
* `repo_search` tool: bounded native literal repository text search.
|
|
3
3
|
*/
|
|
4
4
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
5
5
|
import { type RepositoryLimitOptions, type RepositoryOperations } from "./repository.js";
|
package/dist/search.js
CHANGED
|
@@ -43,19 +43,19 @@ export function createRepoSearchTool(cwd, options) {
|
|
|
43
43
|
const ops = options?.operations ?? createLocalRepositoryOperations(limits);
|
|
44
44
|
return {
|
|
45
45
|
name: "repo_search",
|
|
46
|
-
description: `Search text files under the workspace
|
|
46
|
+
description: `Search text files under the workspace using literal substring match. Skips binary files, excluded basenames (default: ${limits.exclude.join(", ")}), and hidden names unless includeHidden is true. Does not follow symlinks. Caps matches/scanned bytes/time.`,
|
|
47
47
|
parameters: {
|
|
48
48
|
type: "object",
|
|
49
49
|
properties: {
|
|
50
|
-
query: { type: "string", description: "Literal text
|
|
50
|
+
query: { type: "string", description: "Literal text to search for (required)" },
|
|
51
51
|
path: {
|
|
52
52
|
type: "string",
|
|
53
53
|
description: "Workspace-relative directory or file to search (default: workspace root)",
|
|
54
54
|
},
|
|
55
55
|
mode: {
|
|
56
56
|
type: "string",
|
|
57
|
-
description:
|
|
58
|
-
enum: ["literal"
|
|
57
|
+
description: "Search mode: literal substring match only",
|
|
58
|
+
enum: ["literal"],
|
|
59
59
|
},
|
|
60
60
|
caseSensitive: {
|
|
61
61
|
type: "boolean",
|
|
@@ -85,7 +85,13 @@ export function createRepoSearchTool(cwd, options) {
|
|
|
85
85
|
if (query.length === 0)
|
|
86
86
|
return errorResult(toolCallId, "query is required and must be a non-empty string.");
|
|
87
87
|
const path = typeof args.path === "string" ? args.path : undefined;
|
|
88
|
-
|
|
88
|
+
if (args.mode === "regex") {
|
|
89
|
+
return errorResult(toolCallId, 'repo_search no longer supports mode "regex"; use literal substring search.');
|
|
90
|
+
}
|
|
91
|
+
if (args.mode !== undefined && args.mode !== "literal") {
|
|
92
|
+
return errorResult(toolCallId, `unsupported search mode: ${String(args.mode)}`);
|
|
93
|
+
}
|
|
94
|
+
const mode = "literal";
|
|
89
95
|
const caseSensitive = args.caseSensitive === true;
|
|
90
96
|
const includeHidden = args.includeHidden === true;
|
|
91
97
|
let contextLines;
|
package/dist/write.js
CHANGED
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
* (pi would throw "Operation aborted" even after a successful write — misleading, so dropped).
|
|
16
16
|
*/
|
|
17
17
|
import { Buffer } from "node:buffer";
|
|
18
|
-
import { mkdir as fsMkdir
|
|
18
|
+
import { mkdir as fsMkdir } from "node:fs/promises";
|
|
19
19
|
import { dirname } from "node:path";
|
|
20
|
+
import { atomicWriteUtf8File } from "./atomic-write.js";
|
|
20
21
|
import { enforceExecutionPolicy } from "./execution-policy.js";
|
|
21
22
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
22
23
|
import { DEFAULT_MAX_WRITE_BYTES, HARD_MAX_WRITE_BYTES, validateCodingLimit } from "./limits.js";
|
|
23
24
|
import { resolveToCwd } from "./path-utils.js";
|
|
24
25
|
const defaultWriteOperations = {
|
|
25
|
-
writeFile: (path, content, options) =>
|
|
26
|
+
writeFile: (path, content, options) => atomicWriteUtf8File(path, content, { signal: options?.signal }),
|
|
26
27
|
mkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => { }),
|
|
27
28
|
};
|
|
28
29
|
function errorResult(toolCallId, message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism-coding-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Optional coding-agent tools (shell, read, write, edit, repo_list, repo_search, opt-in Git/check/ask-user-decision, and durable plan/checkpoint helpers) package for Prism.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"diff": "^9.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@arnilo/prism": "0.0.
|
|
32
|
-
"@arnilo/prism-workflows": "0.0.
|
|
31
|
+
"@arnilo/prism": "0.0.19",
|
|
32
|
+
"@arnilo/prism-workflows": "0.0.19"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@arnilo/prism": "file:../..",
|