@ff-labs/pi-fff 0.10.4-nightly.e2cad2f → 0.10.5-dev.4d662ef
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 +7 -7
- package/package.json +3 -3
- package/src/aux-finders.ts +7 -4
- package/src/index.ts +38 -16
- package/src/sdk.ts +15 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ff-labs/pi-fff
|
|
2
2
|
|
|
3
|
-
A [pi](https://github.com/badlogic/pi-mono) extension that replaces the built-in `find` and `grep` tools with [FFF](https://github.com/dmtrKovalenko/fff
|
|
3
|
+
A [pi](https://github.com/badlogic/pi-mono) extension that replaces the built-in `find` and `grep` tools with [FFF](https://github.com/dmtrKovalenko/fff) — a Rust-native, SIMD-accelerated file finder with built-in memory.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
@@ -44,20 +44,20 @@ pi install -l npm:@ff-labs/pi-fff
|
|
|
44
44
|
**Via git:**
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
pi install git:github.com/dmtrKovalenko/fff
|
|
47
|
+
pi install git:github.com/dmtrKovalenko/fff
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Pin to a release:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
pi install git:github.com/dmtrKovalenko/fff
|
|
53
|
+
pi install git:github.com/dmtrKovalenko/fff@v0.3.0
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
### Local development / manual install
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
git clone https://github.com/dmtrKovalenko/fff.
|
|
60
|
-
cd fff
|
|
59
|
+
git clone https://github.com/dmtrKovalenko/fff.git
|
|
60
|
+
cd fff/packages/pi-fff
|
|
61
61
|
npm install
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -65,14 +65,14 @@ Then add to your pi `settings.json`:
|
|
|
65
65
|
|
|
66
66
|
```json
|
|
67
67
|
{
|
|
68
|
-
"extensions": ["/path/to/fff
|
|
68
|
+
"extensions": ["/path/to/fff/packages/pi-fff/src/index.ts"]
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
Or test directly:
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
pi -e /path/to/fff
|
|
75
|
+
pi -e /path/to/fff/packages/pi-fff/src/index.ts
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
This extension registers FFF-powered tools (`fffind`, `ffgrep`, `fff-multi-grep`) alongside pi's built-in tools.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ff-labs/pi-fff",
|
|
3
3
|
"public": true,
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.5-dev.4d662ef",
|
|
5
5
|
"description": "pi extension: FFF-powered fuzzy file and content search",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"typecheck": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ff-labs/fff-bun": "0.10.
|
|
44
|
-
"@ff-labs/fff-node": "0.10.
|
|
43
|
+
"@ff-labs/fff-bun": "0.10.5-dev.4d662ef",
|
|
44
|
+
"@ff-labs/fff-node": "0.10.5-dev.4d662ef"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/aux-finders.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface AuxOpts {
|
|
|
18
18
|
enableHomeDirScanning?: boolean;
|
|
19
19
|
// Called before a newly spawned aux picker starts a scan that covers $HOME.
|
|
20
20
|
onHomeDirScan?: (root: string) => void;
|
|
21
|
+
frecencyDbPath?: string;
|
|
22
|
+
historyDbPath?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export class AuxFinderPool {
|
|
@@ -100,17 +102,18 @@ export class AuxFinderPool {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
const { FileFinder } = await loadSdk();
|
|
103
|
-
// LMDB env can only be opened once per process; the main finder already
|
|
104
|
-
// owns the frecency/history DBs. Aux finders are transient and run without
|
|
105
|
-
// persistent scoring — see issue #700.
|
|
106
105
|
const result = FileFinder.create({
|
|
107
106
|
basePath: root,
|
|
107
|
+
frecencyDbPath: this.opts.frecencyDbPath,
|
|
108
|
+
historyDbPath: this.opts.historyDbPath,
|
|
108
109
|
aiMode: true,
|
|
109
110
|
enableHomeDirScanning,
|
|
110
111
|
enableFsRootScanning: this.opts.enableFsRootScanning,
|
|
111
112
|
});
|
|
112
|
-
|
|
113
|
+
|
|
114
|
+
if (!result.ok) {
|
|
113
115
|
throw new Error(`Failed to create aux file finder for ${root}: ${result.error}`);
|
|
116
|
+
}
|
|
114
117
|
|
|
115
118
|
await result.value.waitForScan(SCAN_TIMEOUT_MS);
|
|
116
119
|
const entry: AuxPicker = {
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,8 @@ export { SCAN_TIMEOUT_MS } from "./sdk";
|
|
|
34
34
|
|
|
35
35
|
const DEFAULT_GREP_LIMIT = 20;
|
|
36
36
|
const DEFAULT_FIND_LIMIT = 30;
|
|
37
|
+
const GREP_PAGE_SIZE_MAX = 50;
|
|
38
|
+
const GREP_CONTEXT_MAX = 20;
|
|
37
39
|
const GREP_MAX_LINE_LENGTH = 500;
|
|
38
40
|
const MENTION_MAX_RESULTS = 20;
|
|
39
41
|
|
|
@@ -128,6 +130,13 @@ function truncateLine(line: string, max = GREP_MAX_LINE_LENGTH): string {
|
|
|
128
130
|
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}...`;
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
// Clamp caller-supplied context to a non-negative bounded integer so a large
|
|
134
|
+
// value cannot multiply output size past the model window.
|
|
135
|
+
function clampContext(context: number | undefined): number {
|
|
136
|
+
if (!context || context < 0) return 0;
|
|
137
|
+
return Math.min(Math.floor(context), GREP_CONTEXT_MAX);
|
|
138
|
+
}
|
|
139
|
+
|
|
131
140
|
const HOT_FRECENCY = 25;
|
|
132
141
|
const WARM_FRECENCY = 20;
|
|
133
142
|
|
|
@@ -309,7 +318,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
309
318
|
|
|
310
319
|
const toolNames = resolveToolNames(currentMode);
|
|
311
320
|
|
|
312
|
-
// DB path resolution: flag > env > undefined (
|
|
321
|
+
// DB path resolution: flag > env > undefined (no persistent DBs)
|
|
313
322
|
const frecencyDbPath =
|
|
314
323
|
(pi.getFlag("fff-frecency-db") as string | undefined) ??
|
|
315
324
|
process.env.FFF_FRECENCY_DB ??
|
|
@@ -320,11 +329,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
320
329
|
undefined;
|
|
321
330
|
|
|
322
331
|
// flag (boolean) > env ("1"/"true", or "0"/"false") > default.
|
|
323
|
-
function resolveBoolOpt(
|
|
324
|
-
flagName: string,
|
|
325
|
-
envName: string,
|
|
326
|
-
fallback = false,
|
|
327
|
-
): boolean {
|
|
332
|
+
function resolveBoolOpt(flagName: string, envName: string, fallback = false): boolean {
|
|
328
333
|
const flag = pi.getFlag(flagName);
|
|
329
334
|
if (typeof flag === "boolean") return flag;
|
|
330
335
|
if (typeof flag === "string") return flag === "true" || flag === "1";
|
|
@@ -375,10 +380,12 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
375
380
|
);
|
|
376
381
|
}
|
|
377
382
|
|
|
378
|
-
|
|
383
|
+
const auxPool = new AuxFinderPool({
|
|
379
384
|
enableFsRootScanning,
|
|
380
385
|
enableHomeDirScanning,
|
|
381
386
|
onHomeDirScan: warnHomeDirScan,
|
|
387
|
+
frecencyDbPath,
|
|
388
|
+
historyDbPath,
|
|
382
389
|
});
|
|
383
390
|
|
|
384
391
|
// in case cwd changes we need to figure this out
|
|
@@ -691,7 +698,9 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
691
698
|
}),
|
|
692
699
|
),
|
|
693
700
|
context: Type.Optional(
|
|
694
|
-
Type.Number({
|
|
701
|
+
Type.Number({
|
|
702
|
+
description: `Context lines before+after each match (0-${GREP_CONTEXT_MAX})`,
|
|
703
|
+
}),
|
|
695
704
|
),
|
|
696
705
|
limit: Type.Optional(
|
|
697
706
|
Type.Number({
|
|
@@ -724,6 +733,10 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
724
733
|
|
|
725
734
|
const picker = aux ? aux.finder : await ensureFinder(activeCwd);
|
|
726
735
|
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
736
|
+
// pageSize caps TOTAL matches across all files; maxMatchesPerFile alone
|
|
737
|
+
// only caps per-file, so limit=5 could still return a full SDK page.
|
|
738
|
+
const pageSize = Math.min(effectiveLimit, GREP_PAGE_SIZE_MAX);
|
|
739
|
+
const context = clampContext(params.context);
|
|
727
740
|
const query = aux
|
|
728
741
|
? aux.query
|
|
729
742
|
: buildQuery(params.path, pattern, params.exclude, activeCwd);
|
|
@@ -771,10 +784,11 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
771
784
|
const grepResult = picker.grep(query, {
|
|
772
785
|
mode,
|
|
773
786
|
smartCase,
|
|
774
|
-
maxMatchesPerFile:
|
|
787
|
+
maxMatchesPerFile: pageSize,
|
|
788
|
+
pageSize,
|
|
775
789
|
cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
|
|
776
|
-
beforeContext:
|
|
777
|
-
afterContext:
|
|
790
|
+
beforeContext: context,
|
|
791
|
+
afterContext: context,
|
|
778
792
|
classifyDefinitions: true,
|
|
779
793
|
timeBudgetMs: GREP_TIME_BUDGET_MS,
|
|
780
794
|
});
|
|
@@ -803,7 +817,8 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
803
817
|
const fuzzy = picker.grep(fuzzyQuery, {
|
|
804
818
|
mode: "fuzzy",
|
|
805
819
|
smartCase,
|
|
806
|
-
maxMatchesPerFile:
|
|
820
|
+
maxMatchesPerFile: pageSize,
|
|
821
|
+
pageSize,
|
|
807
822
|
cursor: null,
|
|
808
823
|
beforeContext: 0,
|
|
809
824
|
afterContext: 0,
|
|
@@ -1015,7 +1030,11 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1015
1030
|
constraints: Type.Optional(
|
|
1016
1031
|
Type.String({ description: "File filter, e.g. '*.{ts,tsx} !test/'" }),
|
|
1017
1032
|
),
|
|
1018
|
-
context: Type.Optional(
|
|
1033
|
+
context: Type.Optional(
|
|
1034
|
+
Type.Number({
|
|
1035
|
+
description: `Context lines before+after (0-${GREP_CONTEXT_MAX})`,
|
|
1036
|
+
}),
|
|
1037
|
+
),
|
|
1019
1038
|
limit: Type.Optional(
|
|
1020
1039
|
Type.Number({
|
|
1021
1040
|
description: `Max matches (default ${DEFAULT_GREP_LIMIT})`,
|
|
@@ -1044,15 +1063,18 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1044
1063
|
|
|
1045
1064
|
const f = await ensureFinder(activeCwd);
|
|
1046
1065
|
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
1066
|
+
const pageSize = Math.min(effectiveLimit, GREP_PAGE_SIZE_MAX);
|
|
1067
|
+
const context = clampContext(params.context);
|
|
1047
1068
|
|
|
1048
1069
|
const grepResult = f.multiGrep({
|
|
1049
1070
|
patterns: params.patterns,
|
|
1050
1071
|
constraints: params.constraints,
|
|
1051
|
-
maxMatchesPerFile:
|
|
1072
|
+
maxMatchesPerFile: pageSize,
|
|
1073
|
+
pageSize,
|
|
1052
1074
|
smartCase: true,
|
|
1053
1075
|
cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
|
|
1054
|
-
beforeContext:
|
|
1055
|
-
afterContext:
|
|
1076
|
+
beforeContext: context,
|
|
1077
|
+
afterContext: context,
|
|
1056
1078
|
});
|
|
1057
1079
|
|
|
1058
1080
|
if (!grepResult.ok) throw new Error(grepResult.error);
|
package/src/sdk.ts
CHANGED
|
@@ -22,8 +22,21 @@ function detectRuntime(): "bun" | "node" {
|
|
|
22
22
|
export function loadSdk(): Promise<{ FileFinder: FileFinderStatic }> {
|
|
23
23
|
if (sdkPromise) return sdkPromise;
|
|
24
24
|
|
|
25
|
+
// Pi reloads extension modules with jiti moduleCache:false, so this module
|
|
26
|
+
// is re-executed on every /reload. Re-importing the fff-bun module graph
|
|
27
|
+
// (which top-level awaits a `type: "file"` import of the native .so) hangs
|
|
28
|
+
// forever inside the Bun-compiled pi binary. Cache the first import on
|
|
29
|
+
// globalThis so reloads reuse the resolved module instead of re-importing.
|
|
30
|
+
const g = globalThis as Record<string, unknown>;
|
|
31
|
+
if (g.__fffSdkPromiseGlobal) {
|
|
32
|
+
sdkPromise = g.__fffSdkPromiseGlobal as Promise<{ FileFinder: FileFinderStatic }>;
|
|
33
|
+
return sdkPromise;
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
// default to node as it seems like default option
|
|
26
37
|
const pkg = detectRuntime() === "bun" ? "@ff-labs/fff-bun" : "@ff-labs/fff-node";
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
const p = import(pkg) as Promise<{ FileFinder: FileFinderStatic }>;
|
|
39
|
+
sdkPromise = p;
|
|
40
|
+
(globalThis as Record<string, unknown>).__fffSdkPromiseGlobal = p;
|
|
41
|
+
return p;
|
|
29
42
|
}
|