@aria-cli/tools 1.0.1 → 1.0.3
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/.aria-build-stamp.json +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/definitions/code-intelligence.d.ts +9 -0
- package/dist/definitions/code-intelligence.d.ts.map +1 -0
- package/dist/definitions/code-intelligence.js +471 -0
- package/dist/definitions/code-intelligence.js.map +1 -0
- package/dist/definitions/core.d.ts +3 -0
- package/dist/definitions/core.d.ts.map +1 -1
- package/dist/definitions/core.js +13 -1
- package/dist/definitions/core.js.map +1 -1
- package/dist/definitions/filesystem.d.ts +3 -2
- package/dist/definitions/filesystem.d.ts.map +1 -1
- package/dist/definitions/filesystem.js +4 -38
- package/dist/definitions/filesystem.js.map +1 -1
- package/dist/definitions/frg.d.ts +4 -0
- package/dist/definitions/frg.d.ts.map +1 -0
- package/dist/definitions/frg.js +64 -0
- package/dist/definitions/frg.js.map +1 -0
- package/dist/definitions/index.d.ts +3 -0
- package/dist/definitions/index.d.ts.map +1 -1
- package/dist/definitions/index.js +3 -0
- package/dist/definitions/index.js.map +1 -1
- package/dist/definitions/search.d.ts +10 -0
- package/dist/definitions/search.d.ts.map +1 -0
- package/dist/definitions/search.js +61 -0
- package/dist/definitions/search.js.map +1 -0
- package/dist/executors/apply-patch.d.ts.map +1 -1
- package/dist/executors/apply-patch.js +18 -0
- package/dist/executors/apply-patch.js.map +1 -1
- package/dist/executors/code-intelligence.d.ts +139 -0
- package/dist/executors/code-intelligence.d.ts.map +1 -0
- package/dist/executors/code-intelligence.js +883 -0
- package/dist/executors/code-intelligence.js.map +1 -0
- package/dist/executors/filesystem.d.ts.map +1 -1
- package/dist/executors/filesystem.js +14 -8
- package/dist/executors/filesystem.js.map +1 -1
- package/dist/executors/frg-freshness.d.ts +94 -0
- package/dist/executors/frg-freshness.d.ts.map +1 -0
- package/dist/executors/frg-freshness.js +577 -0
- package/dist/executors/frg-freshness.js.map +1 -0
- package/dist/executors/frg.d.ts +28 -0
- package/dist/executors/frg.d.ts.map +1 -0
- package/dist/executors/frg.js +299 -0
- package/dist/executors/frg.js.map +1 -0
- package/dist/executors/index.d.ts +6 -0
- package/dist/executors/index.d.ts.map +1 -1
- package/dist/executors/index.js +5 -0
- package/dist/executors/index.js.map +1 -1
- package/dist/executors/lsp-client.d.ts +39 -0
- package/dist/executors/lsp-client.d.ts.map +1 -0
- package/dist/executors/lsp-client.js +297 -0
- package/dist/executors/lsp-client.js.map +1 -0
- package/dist/executors/restart.d.ts +4 -9
- package/dist/executors/restart.d.ts.map +1 -1
- package/dist/executors/restart.js +20 -51
- package/dist/executors/restart.js.map +1 -1
- package/dist/executors/search-freshness.d.ts +51 -0
- package/dist/executors/search-freshness.d.ts.map +1 -0
- package/dist/executors/search-freshness.js +196 -0
- package/dist/executors/search-freshness.js.map +1 -0
- package/dist/executors/search.d.ts +12 -0
- package/dist/executors/search.d.ts.map +1 -0
- package/dist/executors/search.js +67 -0
- package/dist/executors/search.js.map +1 -0
- package/dist/headless-control-contract.d.ts +4 -0
- package/dist/headless-control-contract.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/network-runtime/local-control-contract.d.ts +2 -0
- package/dist/network-runtime/local-control-contract.d.ts.map +1 -1
- package/dist/network-runtime/local-control-contract.js +2 -0
- package/dist/network-runtime/local-control-contract.js.map +1 -1
- package/dist-cjs/.tsbuildinfo +1 -1
- package/dist-cjs/definitions/code-intelligence.d.ts +8 -0
- package/dist-cjs/definitions/code-intelligence.js +474 -0
- package/dist-cjs/definitions/code-intelligence.js.map +1 -0
- package/dist-cjs/definitions/core.d.ts +3 -0
- package/dist-cjs/definitions/core.js +17 -2
- package/dist-cjs/definitions/core.js.map +1 -1
- package/dist-cjs/definitions/filesystem.d.ts +3 -2
- package/dist-cjs/definitions/filesystem.js +3 -37
- package/dist-cjs/definitions/filesystem.js.map +1 -1
- package/dist-cjs/definitions/frg.d.ts +3 -0
- package/dist-cjs/definitions/frg.js +67 -0
- package/dist-cjs/definitions/frg.js.map +1 -0
- package/dist-cjs/definitions/index.d.ts +3 -0
- package/dist-cjs/definitions/index.js +7 -1
- package/dist-cjs/definitions/index.js.map +1 -1
- package/dist-cjs/definitions/search.d.ts +9 -0
- package/dist-cjs/definitions/search.js +64 -0
- package/dist-cjs/definitions/search.js.map +1 -0
- package/dist-cjs/executors/apply-patch.js +18 -0
- package/dist-cjs/executors/apply-patch.js.map +1 -1
- package/dist-cjs/executors/code-intelligence.d.ts +138 -0
- package/dist-cjs/executors/code-intelligence.js +926 -0
- package/dist-cjs/executors/code-intelligence.js.map +1 -0
- package/dist-cjs/executors/filesystem.js +17 -8
- package/dist-cjs/executors/filesystem.js.map +1 -1
- package/dist-cjs/executors/frg-freshness.d.ts +93 -0
- package/dist-cjs/executors/frg-freshness.js +628 -0
- package/dist-cjs/executors/frg-freshness.js.map +1 -0
- package/dist-cjs/executors/frg.d.ts +27 -0
- package/dist-cjs/executors/frg.js +335 -0
- package/dist-cjs/executors/frg.js.map +1 -0
- package/dist-cjs/executors/index.d.ts +6 -0
- package/dist-cjs/executors/index.js +34 -2
- package/dist-cjs/executors/index.js.map +1 -1
- package/dist-cjs/executors/lsp-client.d.ts +38 -0
- package/dist-cjs/executors/lsp-client.js +311 -0
- package/dist-cjs/executors/lsp-client.js.map +1 -0
- package/dist-cjs/executors/restart.d.ts +4 -9
- package/dist-cjs/executors/restart.js +19 -50
- package/dist-cjs/executors/restart.js.map +1 -1
- package/dist-cjs/executors/search-freshness.d.ts +50 -0
- package/dist-cjs/executors/search-freshness.js +235 -0
- package/dist-cjs/executors/search-freshness.js.map +1 -0
- package/dist-cjs/executors/search.d.ts +11 -0
- package/dist-cjs/executors/search.js +103 -0
- package/dist-cjs/executors/search.js.map +1 -0
- package/dist-cjs/headless-control-contract.d.ts +15 -11
- package/dist-cjs/index.d.ts +2 -2
- package/dist-cjs/index.js +22 -2
- package/dist-cjs/index.js.map +1 -1
- package/dist-cjs/network-runtime/local-control-contract.d.ts +2 -0
- package/dist-cjs/network-runtime/local-control-contract.js +2 -0
- package/dist-cjs/network-runtime/local-control-contract.js.map +1 -1
- package/package.json +22 -18
- package/src/definitions/code-intelligence.ts +526 -0
- package/src/definitions/core.ts +13 -1
- package/src/definitions/filesystem.ts +3 -39
- package/src/definitions/frg.ts +67 -0
- package/src/definitions/index.ts +3 -0
- package/src/definitions/search.ts +67 -0
- package/src/executors/apply-patch.ts +20 -0
- package/src/executors/code-intelligence.ts +1179 -0
- package/src/executors/filesystem.ts +15 -8
- package/src/executors/frg-freshness.ts +743 -0
- package/src/executors/frg.ts +394 -0
- package/src/executors/index.ts +58 -0
- package/src/executors/lsp-client.ts +355 -0
- package/src/executors/restart.ts +21 -56
- package/src/executors/search-freshness.ts +249 -0
- package/src/executors/search.ts +89 -0
- package/src/index.ts +25 -0
- package/src/network-runtime/local-control-contract.ts +2 -0
- package/tests/definitions/tool-inventory.test.ts +17 -6
- package/tests/executors/frg-freshness.test.ts +136 -0
- package/tests/executors/frg-merge.test.ts +70 -0
- package/tests/executors/frg-session-content.test.ts +40 -0
- package/tests/executors/frg.test.ts +56 -0
- package/tests/integration/headless-control-contract.integration.test.ts +2 -0
- package/tests/loading-tier.test.ts +6 -6
- package/tests/test-lane-manifest.ts +4 -0
- package/tsconfig.cjs.json +9 -1
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import * as fsSync from "node:fs";
|
|
2
|
+
import * as nodePath from "node:path";
|
|
3
|
+
import { frg, type FrgToolInput, type FrgToolOutput } from "@aria-cli/fastripgrep";
|
|
4
|
+
import { getErrorMessage, fail, isPathWithinBase, success } from "./utils.js";
|
|
5
|
+
import type { ToolContext, ToolResult } from "../types.js";
|
|
6
|
+
import { executeSpawn } from "./shell.js";
|
|
7
|
+
import {
|
|
8
|
+
clearPendingFrgMutations,
|
|
9
|
+
ensureFrgRepoStateLoaded,
|
|
10
|
+
flushPendingFrgMutations,
|
|
11
|
+
getFrgFreshnessSnapshot,
|
|
12
|
+
getPendingFrgMutations,
|
|
13
|
+
maybeBuildFrgIndexForRepo,
|
|
14
|
+
reconcileGitDirtyMutations,
|
|
15
|
+
scheduleBackgroundFrgFlush,
|
|
16
|
+
shouldForceSynchronousFrgFlush,
|
|
17
|
+
} from "./frg-freshness.js";
|
|
18
|
+
|
|
19
|
+
function resolveSearchRoot(inputPath: string | undefined, ctx: ToolContext): string {
|
|
20
|
+
const requested = inputPath ?? ".";
|
|
21
|
+
return nodePath.isAbsolute(requested)
|
|
22
|
+
? nodePath.resolve(requested)
|
|
23
|
+
: nodePath.resolve(ctx.workingDir, requested);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function assertAllowedPath(resolvedPath: string, ctx: ToolContext): void {
|
|
27
|
+
let realBase = nodePath.resolve(ctx.workingDir);
|
|
28
|
+
try {
|
|
29
|
+
realBase = fsSync.realpathSync(ctx.workingDir);
|
|
30
|
+
} catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let candidate = resolvedPath;
|
|
35
|
+
try {
|
|
36
|
+
candidate = fsSync.realpathSync(resolvedPath);
|
|
37
|
+
} catch {
|
|
38
|
+
candidate = nodePath.resolve(resolvedPath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isPathWithinBase(candidate, realBase)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const home = process.env.HOME;
|
|
46
|
+
if (home) {
|
|
47
|
+
try {
|
|
48
|
+
const realHome = fsSync.realpathSync(home);
|
|
49
|
+
if (isPathWithinBase(candidate, realHome)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Path traversal not allowed: resolved path is outside working directory and home directory",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface FrgInput {
|
|
63
|
+
command:
|
|
64
|
+
| "index"
|
|
65
|
+
| "search"
|
|
66
|
+
| "update"
|
|
67
|
+
| "status"
|
|
68
|
+
| "replace"
|
|
69
|
+
| "init"
|
|
70
|
+
| "watch"
|
|
71
|
+
| "man"
|
|
72
|
+
| "completions"
|
|
73
|
+
| "upgrade";
|
|
74
|
+
pattern?: string;
|
|
75
|
+
replacement?: string;
|
|
76
|
+
path?: string;
|
|
77
|
+
noIndex?: boolean;
|
|
78
|
+
literal?: boolean;
|
|
79
|
+
caseInsensitive?: boolean;
|
|
80
|
+
smartCase?: boolean;
|
|
81
|
+
filesOnly?: boolean;
|
|
82
|
+
count?: boolean;
|
|
83
|
+
maxCount?: number;
|
|
84
|
+
quiet?: boolean;
|
|
85
|
+
context?: number;
|
|
86
|
+
json?: boolean;
|
|
87
|
+
glob?: string;
|
|
88
|
+
fileType?: string;
|
|
89
|
+
follow?: boolean;
|
|
90
|
+
extraPatterns?: string[];
|
|
91
|
+
maxFilesize?: number;
|
|
92
|
+
force?: boolean;
|
|
93
|
+
write?: boolean;
|
|
94
|
+
hook?: boolean;
|
|
95
|
+
shell?: "bash" | "elvish" | "fish" | "powershell" | "zsh";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toNativeInput(input: FrgInput, root: string): FrgToolInput {
|
|
99
|
+
return {
|
|
100
|
+
command: input.command,
|
|
101
|
+
pattern: input.pattern,
|
|
102
|
+
replacement: input.replacement,
|
|
103
|
+
path: root,
|
|
104
|
+
noIndex: input.noIndex,
|
|
105
|
+
literal: input.literal,
|
|
106
|
+
caseInsensitive: input.caseInsensitive,
|
|
107
|
+
smartCase: input.smartCase,
|
|
108
|
+
filesOnly: input.filesOnly,
|
|
109
|
+
count: input.count,
|
|
110
|
+
maxCount: input.maxCount,
|
|
111
|
+
quiet: input.quiet,
|
|
112
|
+
context: input.context,
|
|
113
|
+
json: input.json,
|
|
114
|
+
glob: input.glob,
|
|
115
|
+
fileType: input.fileType,
|
|
116
|
+
follow: input.follow,
|
|
117
|
+
extraPatterns: input.extraPatterns,
|
|
118
|
+
maxFilesize: input.maxFilesize,
|
|
119
|
+
force: input.force,
|
|
120
|
+
write: input.write,
|
|
121
|
+
hook: input.hook,
|
|
122
|
+
shell: input.shell,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getNativeSessionOverlay(root: string): {
|
|
127
|
+
sessionWrites: Array<{ path: string; content: string }>;
|
|
128
|
+
sessionDeletes: string[];
|
|
129
|
+
} {
|
|
130
|
+
const sessionWrites: Array<{ path: string; content: string }> = [];
|
|
131
|
+
const sessionDeletes: string[] = [];
|
|
132
|
+
for (const mutation of getPendingFrgMutations(root)) {
|
|
133
|
+
if (mutation.operation === "delete") {
|
|
134
|
+
sessionDeletes.push(nodePath.resolve(mutation.path));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Read content from disk if not cached. This closes the gap where
|
|
138
|
+
// reconcileGitDirtyMutations detects git-dirty files but sets
|
|
139
|
+
// content=undefined (no cache). Without this read, the session overlay
|
|
140
|
+
// would skip these mutations entirely — making agent-written files
|
|
141
|
+
// invisible until the 750ms background flush completes.
|
|
142
|
+
let content = mutation.content;
|
|
143
|
+
if (typeof content !== "string") {
|
|
144
|
+
try {
|
|
145
|
+
const resolved = nodePath.resolve(mutation.path);
|
|
146
|
+
const buf = fsSync.readFileSync(resolved);
|
|
147
|
+
// Skip binary files
|
|
148
|
+
if (!buf.subarray(0, Math.min(buf.length, 8192)).includes(0)) {
|
|
149
|
+
content = buf.toString("utf8");
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// File unreadable (deleted, permission, etc.) — skip
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (typeof content === "string") {
|
|
156
|
+
sessionWrites.push({
|
|
157
|
+
path: nodePath.resolve(mutation.path),
|
|
158
|
+
content,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { sessionWrites, sessionDeletes };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function executeFrgWatch(
|
|
166
|
+
_input: FrgInput,
|
|
167
|
+
ctx: ToolContext,
|
|
168
|
+
root: string,
|
|
169
|
+
): Promise<ToolResult> {
|
|
170
|
+
const resolvedWorkingDir = nodePath.resolve(ctx.workingDir);
|
|
171
|
+
const repoRoot = resolvedWorkingDir.includes(`${nodePath.sep}packages${nodePath.sep}`)
|
|
172
|
+
? (resolvedWorkingDir.split(`${nodePath.sep}packages${nodePath.sep}`)[0] ?? process.cwd())
|
|
173
|
+
: process.cwd();
|
|
174
|
+
const binaryPath = nodePath.join(
|
|
175
|
+
repoRoot,
|
|
176
|
+
"vendor",
|
|
177
|
+
"fastripgrep",
|
|
178
|
+
"target",
|
|
179
|
+
"release",
|
|
180
|
+
process.platform === "win32" ? "frg.exe" : "frg",
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (!fsSync.existsSync(binaryPath)) {
|
|
184
|
+
return fail(
|
|
185
|
+
`frg watch failed: binary not found at ${binaryPath}. Build @aria-cli/fastripgrep first.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const spawnResult = await executeSpawn(
|
|
190
|
+
{
|
|
191
|
+
program: binaryPath,
|
|
192
|
+
args: ["watch", root],
|
|
193
|
+
cwd: root,
|
|
194
|
+
},
|
|
195
|
+
ctx,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (!spawnResult.success) {
|
|
199
|
+
return spawnResult;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const data = (spawnResult.data ?? {}) as { pid?: number };
|
|
203
|
+
return success(`Started frg watch process for ${root}`, {
|
|
204
|
+
command: "watch",
|
|
205
|
+
path: root,
|
|
206
|
+
watched: true,
|
|
207
|
+
pid: data.pid,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function maybeReindexOnHeadChange(root: string, currentOutput: FrgToolOutput): FrgToolOutput {
|
|
212
|
+
if (currentOutput.command !== "status") {
|
|
213
|
+
return currentOutput;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const indexedTree = currentOutput.status?.tree_hash ?? null;
|
|
218
|
+
const indexedHead = currentOutput.status?.commit_hash ?? null;
|
|
219
|
+
const freshness = getFrgFreshnessSnapshot(root, currentOutput.status);
|
|
220
|
+
|
|
221
|
+
if (freshness.headTree && indexedTree && freshness.headTree !== indexedTree) {
|
|
222
|
+
if (
|
|
223
|
+
freshness.sessionPendingCount === 0 &&
|
|
224
|
+
freshness.dirtyTrackedCount === 0 &&
|
|
225
|
+
freshness.dirtyUntrackedCount === 0
|
|
226
|
+
) {
|
|
227
|
+
const updated = frg({ command: "update", path: root });
|
|
228
|
+
return {
|
|
229
|
+
...updated,
|
|
230
|
+
content: `HEAD tree changed from indexed ${indexedTree} to ${freshness.headTree}; triggered incremental update`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...currentOutput,
|
|
236
|
+
content: `HEAD tree differs from indexed tree (${indexedTree} -> ${freshness.headTree}); local dirty state deferred update`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (indexedHead && freshness.headCommit && indexedHead !== freshness.headCommit) {
|
|
241
|
+
return {
|
|
242
|
+
...currentOutput,
|
|
243
|
+
content: `HEAD commit differs from indexed commit (${indexedHead} -> ${freshness.headCommit})`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// ignore and keep status output stable
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return currentOutput;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function enrichStatusOutput(root: string, output: FrgToolOutput): FrgToolOutput {
|
|
254
|
+
if (output.command !== "status") {
|
|
255
|
+
return output;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const freshness = getFrgFreshnessSnapshot(root, output.status);
|
|
259
|
+
return {
|
|
260
|
+
...output,
|
|
261
|
+
status: output.status
|
|
262
|
+
? {
|
|
263
|
+
...output.status,
|
|
264
|
+
head_commit: freshness.headCommit,
|
|
265
|
+
head_tree: freshness.headTree,
|
|
266
|
+
tree_match:
|
|
267
|
+
freshness.headTree && output.status.tree_hash
|
|
268
|
+
? freshness.headTree === output.status.tree_hash
|
|
269
|
+
: undefined,
|
|
270
|
+
dirty_tracked_count: freshness.dirtyTrackedCount,
|
|
271
|
+
dirty_untracked_count: freshness.dirtyUntrackedCount,
|
|
272
|
+
session_pending_count: freshness.sessionPendingCount,
|
|
273
|
+
session_pending_bytes: freshness.sessionPendingBytes,
|
|
274
|
+
background_flush_scheduled: freshness.flush.scheduled,
|
|
275
|
+
flush_in_flight: freshness.flush.inFlight,
|
|
276
|
+
last_flush_at: freshness.flush.lastFlushAt,
|
|
277
|
+
last_flush_error: freshness.flush.lastError,
|
|
278
|
+
freshness_state: freshness.freshnessState,
|
|
279
|
+
}
|
|
280
|
+
: output.status,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function messageFor(output: FrgToolOutput, input: FrgInput): string {
|
|
285
|
+
if (output.command === "search") {
|
|
286
|
+
if (output.mode === "quiet") {
|
|
287
|
+
return output.matched
|
|
288
|
+
? `Match found for ${input.pattern}`
|
|
289
|
+
: `No matches found for ${input.pattern}`;
|
|
290
|
+
}
|
|
291
|
+
if (output.mode === "files") {
|
|
292
|
+
return `Found ${(output.files ?? []).length} files for ${input.pattern}`;
|
|
293
|
+
}
|
|
294
|
+
if (output.mode === "counts") {
|
|
295
|
+
return `Counted matches in ${(output.counts ?? []).length} files for ${input.pattern}`;
|
|
296
|
+
}
|
|
297
|
+
return `Found ${(output.matches ?? []).length} matches for ${input.pattern}`;
|
|
298
|
+
}
|
|
299
|
+
if (output.command === "replace") {
|
|
300
|
+
return input.write
|
|
301
|
+
? `Applied ${output.replacements ?? 0} replacements in ${output.files_changed ?? 0} files`
|
|
302
|
+
: `Previewed ${output.replacements ?? 0} replacements in ${output.files_changed ?? 0} files`;
|
|
303
|
+
}
|
|
304
|
+
if (output.command === "init") {
|
|
305
|
+
return output.hook_installed
|
|
306
|
+
? `Initialized frg for ${output.path} and installed git hook`
|
|
307
|
+
: `Initialized frg for ${output.path}`;
|
|
308
|
+
}
|
|
309
|
+
if (output.command === "completions") {
|
|
310
|
+
return `Generated ${output.shell ?? input.shell ?? "shell"} completions for frg`;
|
|
311
|
+
}
|
|
312
|
+
if (output.command === "man") {
|
|
313
|
+
return "Generated frg man page content";
|
|
314
|
+
}
|
|
315
|
+
if (output.command === "upgrade") {
|
|
316
|
+
return output.content ?? "Provided frg upgrade metadata";
|
|
317
|
+
}
|
|
318
|
+
return `frg ${output.command} completed for ${output.path}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function executeFrg(input: unknown, ctx: ToolContext): Promise<ToolResult> {
|
|
322
|
+
try {
|
|
323
|
+
const args = input as FrgInput;
|
|
324
|
+
const root = resolveSearchRoot(args.path, ctx);
|
|
325
|
+
assertAllowedPath(root, ctx);
|
|
326
|
+
|
|
327
|
+
if (args.command === "watch") {
|
|
328
|
+
return executeFrgWatch(args, ctx, root);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
ensureFrgRepoStateLoaded(root);
|
|
332
|
+
reconcileGitDirtyMutations(root);
|
|
333
|
+
|
|
334
|
+
if (args.command === "search") {
|
|
335
|
+
maybeBuildFrgIndexForRepo(root);
|
|
336
|
+
|
|
337
|
+
// Detect HEAD tree change (e.g., after a commit or branch switch).
|
|
338
|
+
// Without this, the index stays on the old tree until `frg status` is
|
|
339
|
+
// explicitly called. This is the same gap that aria-search had —
|
|
340
|
+
// the index was "ready" because .frg/ exists, but stale because
|
|
341
|
+
// the tree changed. Run incremental update to pick up the new tree.
|
|
342
|
+
try {
|
|
343
|
+
const statusOutput = frg({ command: "status", path: root });
|
|
344
|
+
if (statusOutput.status?.tree_hash && statusOutput.status?.commit_hash) {
|
|
345
|
+
const snapshot = getFrgFreshnessSnapshot(root, statusOutput.status);
|
|
346
|
+
if (
|
|
347
|
+
snapshot.headTree &&
|
|
348
|
+
statusOutput.status.tree_hash !== snapshot.headTree &&
|
|
349
|
+
snapshot.sessionPendingCount === 0 &&
|
|
350
|
+
snapshot.dirtyTrackedCount === 0 &&
|
|
351
|
+
snapshot.dirtyUntrackedCount === 0
|
|
352
|
+
) {
|
|
353
|
+
// Clean working tree but HEAD moved — incremental update
|
|
354
|
+
frg({ command: "update", path: root });
|
|
355
|
+
clearPendingFrgMutations(root);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
// Best effort — search will still work with session overlay
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const pending = getPendingFrgMutations(root);
|
|
363
|
+
if (pending.length > 0) {
|
|
364
|
+
if (!args.noIndex) {
|
|
365
|
+
if (shouldForceSynchronousFrgFlush(root)) {
|
|
366
|
+
flushPendingFrgMutations(root);
|
|
367
|
+
} else {
|
|
368
|
+
scheduleBackgroundFrgFlush(root);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const nativeInput = toNativeInput(args, root);
|
|
375
|
+
if (args.command === "search") {
|
|
376
|
+
const sessionOverlay = getNativeSessionOverlay(root);
|
|
377
|
+
nativeInput.sessionWrites = sessionOverlay.sessionWrites;
|
|
378
|
+
nativeInput.sessionDeletes = sessionOverlay.sessionDeletes;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let output = frg(nativeInput);
|
|
382
|
+
if (args.command === "status") {
|
|
383
|
+
output = maybeReindexOnHeadChange(root, output);
|
|
384
|
+
output = enrichStatusOutput(root, output);
|
|
385
|
+
if (output.content?.includes("triggered incremental update")) {
|
|
386
|
+
clearPendingFrgMutations(root);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return success(messageFor(output, args), output);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return fail(`frg failed: ${getErrorMessage(error)}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
package/src/executors/index.ts
CHANGED
|
@@ -29,6 +29,39 @@ export type {
|
|
|
29
29
|
GrepMatch,
|
|
30
30
|
} from "./filesystem.js";
|
|
31
31
|
|
|
32
|
+
// fastripgrep executors
|
|
33
|
+
export { executeFrg } from "./frg.js";
|
|
34
|
+
export {
|
|
35
|
+
recordFrgMutation,
|
|
36
|
+
getPendingFrgMutations,
|
|
37
|
+
getPendingFrgMutationBytes,
|
|
38
|
+
clearPendingFrgMutations,
|
|
39
|
+
flushPendingFrgMutations,
|
|
40
|
+
maybeBuildFrgIndexForRepo,
|
|
41
|
+
ensureFrgRepoStateLoaded,
|
|
42
|
+
reconcileGitDirtyMutations,
|
|
43
|
+
getGitDirtyStatus,
|
|
44
|
+
getFrgFlushStatus,
|
|
45
|
+
getFrgFreshnessSnapshot,
|
|
46
|
+
scheduleBackgroundFrgFlush,
|
|
47
|
+
shouldForceSynchronousFrgFlush,
|
|
48
|
+
DEFAULT_BACKGROUND_FRG_FLUSH_DELAY_MS,
|
|
49
|
+
BACKGROUND_FRG_MAX_FLUSH_DELAY_MS,
|
|
50
|
+
MAX_PENDING_MUTATIONS_BEFORE_SYNC_FLUSH,
|
|
51
|
+
MAX_PENDING_MUTATION_BYTES_BEFORE_SYNC_FLUSH,
|
|
52
|
+
MAX_CACHED_MUTATION_CONTENT_BYTES,
|
|
53
|
+
} from "./frg-freshness.js";
|
|
54
|
+
|
|
55
|
+
// fastripgrep executor types
|
|
56
|
+
export type { FrgInput } from "./frg.js";
|
|
57
|
+
export type {
|
|
58
|
+
FrgMutationOperation,
|
|
59
|
+
FrgMutationRecord,
|
|
60
|
+
FrgGitDirtyStatus,
|
|
61
|
+
FrgFlushStatus,
|
|
62
|
+
FrgFreshnessSnapshot,
|
|
63
|
+
} from "./frg-freshness.js";
|
|
64
|
+
|
|
32
65
|
// Apply Patch executor
|
|
33
66
|
export { executeApplyPatch } from "./apply-patch.js";
|
|
34
67
|
|
|
@@ -218,5 +251,30 @@ export type { RestartInput } from "./restart.js";
|
|
|
218
251
|
export { executeDeploy } from "./deploy.js";
|
|
219
252
|
export type { DeployInput, DeployOutput } from "./deploy.js";
|
|
220
253
|
|
|
254
|
+
// Code Intelligence executors
|
|
255
|
+
export {
|
|
256
|
+
executeRg,
|
|
257
|
+
executeUg,
|
|
258
|
+
executeProbe,
|
|
259
|
+
executeSg,
|
|
260
|
+
executeCbm,
|
|
261
|
+
executeLsp,
|
|
262
|
+
executeSerena,
|
|
263
|
+
executeFff,
|
|
264
|
+
} from "./code-intelligence.js";
|
|
265
|
+
|
|
266
|
+
// Code Intelligence executor types
|
|
267
|
+
export type {
|
|
268
|
+
RgInput,
|
|
269
|
+
UgInput,
|
|
270
|
+
ProbeInput,
|
|
271
|
+
SgInput,
|
|
272
|
+
CbmInput,
|
|
273
|
+
LspInput,
|
|
274
|
+
SerenaInput,
|
|
275
|
+
FffInput,
|
|
276
|
+
RgMatch,
|
|
277
|
+
} from "./code-intelligence.js";
|
|
278
|
+
|
|
221
279
|
// Shared executor utilities
|
|
222
280
|
export { success, fail, getErrorMessage } from "./utils.js";
|