@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 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.nvim) — a Rust-native, SIMD-accelerated file finder with built-in memory.
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.nvim
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.nvim@v0.3.0
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.nvim.git
60
- cd fff.nvim/packages/pi-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.nvim/packages/pi-fff/src/index.ts"]
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.nvim/packages/pi-fff/src/index.ts
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-nightly.e2cad2f",
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.4-nightly.e2cad2f",
44
- "@ff-labs/fff-node": "0.10.4-nightly.e2cad2f"
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": "*",
@@ -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
- if (!result.ok)
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 (use fff-node defaults)
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
- let auxPool = new AuxFinderPool({
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({ description: "Context lines before+after each match" }),
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: Math.min(effectiveLimit, 50),
787
+ maxMatchesPerFile: pageSize,
788
+ pageSize,
775
789
  cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
776
- beforeContext: params.context ?? 0,
777
- afterContext: params.context ?? 0,
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: Math.min(effectiveLimit, 50),
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(Type.Number({ description: "Context lines before+after" })),
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: Math.min(effectiveLimit, 50),
1072
+ maxMatchesPerFile: pageSize,
1073
+ pageSize,
1052
1074
  smartCase: true,
1053
1075
  cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
1054
- beforeContext: params.context ?? 0,
1055
- afterContext: params.context ?? 0,
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
- sdkPromise = import(pkg) as Promise<{ FileFinder: FileFinderStatic }>;
28
- return sdkPromise;
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
  }