@ff-labs/pi-fff 0.10.6-nightly.e6df253 → 0.10.6

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
@@ -143,7 +143,9 @@ For persistent global configuration, create `pi-fff.json` in pi's agent director
143
143
  "frecencyDbPath": "/path/to/frecency",
144
144
  "historyDbPath": "/path/to/history",
145
145
  "enableFsRootScanning": false,
146
- "enableHomeDirScanning": true
146
+ "enableHomeDirScanning": true,
147
+ "warnOnHomeDirScan": true,
148
+ "followSymlinks": true
147
149
  }
148
150
  ```
149
151
 
@@ -157,6 +159,8 @@ All fields are optional:
157
159
  | `historyDbPath` | non-empty string | See [Data](#data) |
158
160
  | `enableFsRootScanning` | boolean | `false` |
159
161
  | `enableHomeDirScanning` | boolean | `true` |
162
+ | `warnOnHomeDirScan` | boolean | `true` |
163
+ | `followSymlinks` | boolean | `true` |
160
164
 
161
165
  CLI flags take precedence over environment variables, which take precedence over this file. A missing file is ignored. Malformed JSON, unknown fields, and invalid values stop the extension from loading and report the file path and error. `/fff-mode` changes the current session; it does not edit this file.
162
166
 
@@ -169,6 +173,8 @@ The file is global only. Project-level config cannot safely control tool names b
169
173
  - `--fff-history-db <path>` — path to query history database (also: `FFF_HISTORY_DB` env). Optional; see [Data](#data) for the default.
170
174
  - `--fff-enable-root-scan` — allow indexing when launched from `/` (also: `FFF_ENABLE_ROOT_SCAN=1` env). FFF refuses to init at the filesystem root by default.
171
175
  - `--fff-enable-home-scan` — index the home directory when launched from `$HOME` (also: `FFF_ENABLE_HOME_SCAN` env). Enabled by default. Disable with `--fff-enable-home-scan=false` or `FFF_ENABLE_HOME_SCAN=0` if your `$HOME` contains huge trees (toolchains, kernel sources, build outputs) that make the background index run for a long time. When launched from `$HOME` with this enabled, pi shows a warning that the whole home tree is being indexed.
176
+ - `--fff-warn-home-scan` — show the warning notification when `$HOME` is indexed (also: `FFF_WARN_HOME_SCAN` env). Enabled by default. Disable with `--fff-warn-home-scan=false`, `FFF_WARN_HOME_SCAN=0`, or `"warnOnHomeDirScan": false` in `pi-fff.json`. Indexing and the footer status are unaffected.
177
+ - `--fff-follow-symlinks` — index through directory symlinks (also: `FFF_FOLLOW_SYMLINKS` env, or `"followSymlinks"` in `pi-fff.json`). Enabled by default: trees that reach their real files through links — a git worktree whose `docs/` links back to the main checkout, or a stowed dotfiles layout — would otherwise be missing from `@`-mentions and from find/grep with no visible sign. Disable with `--fff-follow-symlinks=false` or `FFF_FOLLOW_SYMLINKS=0` to keep the walk inside the real tree, which is worth doing when a linked target pulls in a large tree outside the workspace. Symlink cycles are detected and broken by the walker.
172
178
 
173
179
  ## Data
174
180
 
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.6-nightly.e6df253",
4
+ "version": "0.10.6",
5
5
  "description": "pi extension: FFF-powered fuzzy file and content search",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -41,8 +41,8 @@
41
41
  "typecheck": "tsc --noEmit"
42
42
  },
43
43
  "dependencies": {
44
- "@ff-labs/fff-bun": "0.10.6-nightly.e6df253",
45
- "@ff-labs/fff-node": "0.10.6-nightly.e6df253"
44
+ "@ff-labs/fff-bun": "0.10.6",
45
+ "@ff-labs/fff-node": "0.10.6"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@earendil-works/pi-coding-agent": "*",
@@ -36,6 +36,16 @@
36
36
  "type": "boolean",
37
37
  "default": true,
38
38
  "description": "Allows indexing when pi is launched from the home directory."
39
+ },
40
+ "warnOnHomeDirScan": {
41
+ "type": "boolean",
42
+ "default": true,
43
+ "description": "Shows a warning notification when the home directory is indexed."
44
+ },
45
+ "followSymlinks": {
46
+ "type": "boolean",
47
+ "default": true,
48
+ "description": "Indexes through directory symlinks, e.g. a git worktree or stow layout whose files live behind links. Set to false to keep the walk inside the real tree."
39
49
  }
40
50
  }
41
51
  }
@@ -16,6 +16,7 @@ interface AuxPicker {
16
16
  export interface AuxOpts {
17
17
  enableFsRootScanning: boolean;
18
18
  enableHomeDirScanning?: boolean;
19
+ followSymlinks?: boolean;
19
20
  pickers: FilePickerFactory;
20
21
  // Called before a newly spawned aux picker starts a scan that covers $HOME.
21
22
  onHomeDirScan?: (root: string) => void;
@@ -104,6 +105,7 @@ export class AuxFinderPool {
104
105
  basePath: root,
105
106
  enableHomeDirScanning,
106
107
  enableFsRootScanning: this.opts.enableFsRootScanning,
108
+ followSymlinks: this.opts.followSymlinks,
107
109
  });
108
110
 
109
111
  const entry: AuxPicker = { root, finder, lastUsed: Date.now() };
package/src/config.ts CHANGED
@@ -14,6 +14,8 @@ export interface FffConfig {
14
14
  historyDbPath?: string;
15
15
  enableFsRootScanning?: boolean;
16
16
  enableHomeDirScanning?: boolean;
17
+ warnOnHomeDirScan?: boolean;
18
+ followSymlinks?: boolean;
17
19
  }
18
20
 
19
21
  const CONFIG_KEYS = new Set<keyof FffConfig>([
@@ -23,6 +25,8 @@ const CONFIG_KEYS = new Set<keyof FffConfig>([
23
25
  "historyDbPath",
24
26
  "enableFsRootScanning",
25
27
  "enableHomeDirScanning",
28
+ "warnOnHomeDirScan",
29
+ "followSymlinks",
26
30
  ]);
27
31
 
28
32
  export function loadConfig(agentDir = piDataDir()): FffConfig {
@@ -64,6 +68,8 @@ export function loadConfig(agentDir = piDataDir()): FffConfig {
64
68
  validateString(configPath, parsed, "historyDbPath");
65
69
  validateBoolean(configPath, parsed, "enableFsRootScanning");
66
70
  validateBoolean(configPath, parsed, "enableHomeDirScanning");
71
+ validateBoolean(configPath, parsed, "warnOnHomeDirScan");
72
+ validateBoolean(configPath, parsed, "followSymlinks");
67
73
 
68
74
  return parsed as FffConfig;
69
75
  }
@@ -94,7 +100,11 @@ function validateString(
94
100
  function validateBoolean(
95
101
  configPath: string,
96
102
  config: Record<string, unknown>,
97
- key: "enableFsRootScanning" | "enableHomeDirScanning",
103
+ key:
104
+ | "enableFsRootScanning"
105
+ | "enableHomeDirScanning"
106
+ | "warnOnHomeDirScan"
107
+ | "followSymlinks",
98
108
  ): void {
99
109
  const value = config[key];
100
110
  if (value !== undefined && typeof value !== "boolean") {
@@ -5,6 +5,7 @@ export interface PickerOptions {
5
5
  basePath: string;
6
6
  enableHomeDirScanning?: boolean;
7
7
  enableFsRootScanning?: boolean;
8
+ followSymlinks?: boolean;
8
9
  }
9
10
 
10
11
  /** Opens every picker in this pi process — the cwd picker and the aux pickers —
package/src/index.ts CHANGED
@@ -40,6 +40,10 @@ export { SCAN_TIMEOUT_MS } from "./sdk";
40
40
  const DEFAULT_GREP_LIMIT = 20;
41
41
  const DEFAULT_FIND_LIMIT = 30;
42
42
  const GREP_PAGE_SIZE_MAX = 50;
43
+ // Per-file match cap. Must stay decoupled from pageSize: grep cursors advance by
44
+ // file offset, so clamping this to pageSize makes same-file overflow unreachable
45
+ // on later pages (#825). Matches the engine default.
46
+ const GREP_MAX_MATCHES_PER_FILE = 200;
43
47
  const GREP_CONTEXT_MAX = 20;
44
48
  const GREP_MAX_LINE_LENGTH = 500;
45
49
  const MENTION_MAX_RESULTS = 20;
@@ -50,7 +54,8 @@ const GREP_TIME_BUDGET_MS = 10_000;
50
54
  const HOME_SCAN_STATUS_KEY = "fff";
51
55
  const HOME_SCAN_POLL_MS = 1_000;
52
56
  const HOME_SCAN_DISABLE_HINT =
53
- "You can prevent home dir indexing with --fff-enable-home-scan=false, FFF_ENABLE_HOME_SCAN=0, or enableHomeDirScanning in pi-fff.json.";
57
+ 'You can prevent home dir indexing with --fff-enable-home-scan=false, FFF_ENABLE_HOME_SCAN=0, or "enableHomeDirScanning": false in pi-fff.json. ' +
58
+ 'To keep indexing but silence this warning use --fff-warn-home-scan=false, FFF_WARN_HOME_SCAN=0, or "warnOnHomeDirScan": false in pi-fff.json.';
54
59
 
55
60
  interface ToolNames {
56
61
  grep: string;
@@ -343,6 +348,8 @@ export default function fffExtension(pi: ExtensionAPI) {
343
348
  let resolvedDbPaths: ReturnType<typeof resolveDbPaths>;
344
349
  let enableFsRootScanning = false;
345
350
  let enableHomeDirScanning = true;
351
+ let warnOnHomeDirScan = true;
352
+ let followSymlinks = true;
346
353
 
347
354
  function setMode(mode: FffMode): void {
348
355
  currentMode = mode;
@@ -385,6 +392,22 @@ export default function fffExtension(pi: ExtensionAPI) {
385
392
  true,
386
393
  parseBoolean,
387
394
  );
395
+ warnOnHomeDirScan = getConfigValue(
396
+ "fff-warn-home-scan",
397
+ "FFF_WARN_HOME_SCAN",
398
+ config.warnOnHomeDirScan,
399
+ true,
400
+ parseBoolean,
401
+ );
402
+ // On by default: worktree and stow layouts reach their files through links,
403
+ // and an agent silently missing them is worse than the extra walk.
404
+ followSymlinks = getConfigValue(
405
+ "fff-follow-symlinks",
406
+ "FFF_FOLLOW_SYMLINKS",
407
+ config.followSymlinks,
408
+ true,
409
+ parseBoolean,
410
+ );
388
411
  }
389
412
 
390
413
  function getMode(): FffMode {
@@ -406,6 +429,7 @@ export default function fffExtension(pi: ExtensionAPI) {
406
429
  let homeScanTimer: ReturnType<typeof setInterval> | null = null;
407
430
 
408
431
  function warnHomeDirScan(root: string): void {
432
+ if (!warnOnHomeDirScan) return;
409
433
  uiCtx?.ui.notify(
410
434
  `(fff): Your cwd (${root}) is too large. Indexing will take additional time and resources.\n${HOME_SCAN_DISABLE_HINT}`,
411
435
  "warning",
@@ -430,6 +454,7 @@ export default function fffExtension(pi: ExtensionAPI) {
430
454
  auxPool = new AuxFinderPool({
431
455
  enableFsRootScanning,
432
456
  enableHomeDirScanning,
457
+ followSymlinks,
433
458
  onHomeDirScan: warnHomeDirScan,
434
459
  pickers,
435
460
  });
@@ -456,6 +481,7 @@ export default function fffExtension(pi: ExtensionAPI) {
456
481
  basePath: cwd,
457
482
  enableHomeDirScanning,
458
483
  enableFsRootScanning,
484
+ followSymlinks,
459
485
  });
460
486
  finderCwd = cwd;
461
487
  return mainFinder;
@@ -667,6 +693,18 @@ export default function fffExtension(pi: ExtensionAPI) {
667
693
  type: "boolean",
668
694
  });
669
695
 
696
+ pi.registerFlag("fff-follow-symlinks", {
697
+ description:
698
+ "Index through directory symlinks, e.g. a git worktree or stow layout (default true; disable with --fff-follow-symlinks=false or FFF_FOLLOW_SYMLINKS=0)",
699
+ type: "boolean",
700
+ });
701
+
702
+ pi.registerFlag("fff-warn-home-scan", {
703
+ description:
704
+ "Warn when indexing $HOME (default true; silence with --fff-warn-home-scan=false or FFF_WARN_HOME_SCAN=0)",
705
+ type: "boolean",
706
+ });
707
+
670
708
  function reportInitFailure(ctx: ExtensionContext, error: unknown): void {
671
709
  ctx.ui.notify(
672
710
  `FFF init failed: ${error instanceof Error ? error.message : String(error)}`,
@@ -833,8 +871,9 @@ export default function fffExtension(pi: ExtensionAPI) {
833
871
 
834
872
  const picker = aux ? aux.finder : await ensureFinder(activeCwd);
835
873
  const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
836
- // pageSize caps TOTAL matches across all files; maxMatchesPerFile alone
837
- // only caps per-file, so limit=5 could still return a full SDK page.
874
+ // pageSize caps TOTAL matches across all files (soft cap: the current file
875
+ // is always finished first). maxMatchesPerFile stays decoupled at the engine
876
+ // default so same-file overflow remains reachable via cursor (#825).
838
877
  const pageSize = Math.min(effectiveLimit, GREP_PAGE_SIZE_MAX);
839
878
  const context = clampContext(params.context);
840
879
  const query = aux
@@ -884,7 +923,7 @@ export default function fffExtension(pi: ExtensionAPI) {
884
923
  const grepResult = picker.grep(query, {
885
924
  mode,
886
925
  smartCase,
887
- maxMatchesPerFile: pageSize,
926
+ maxMatchesPerFile: GREP_MAX_MATCHES_PER_FILE,
888
927
  pageSize,
889
928
  cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,
890
929
  beforeContext: context,
@@ -917,7 +956,7 @@ export default function fffExtension(pi: ExtensionAPI) {
917
956
  const fuzzy = picker.grep(fuzzyQuery, {
918
957
  mode: "fuzzy",
919
958
  smartCase,
920
- maxMatchesPerFile: pageSize,
959
+ maxMatchesPerFile: GREP_MAX_MATCHES_PER_FILE,
921
960
  pageSize,
922
961
  cursor: null,
923
962
  beforeContext: 0,
@@ -1167,7 +1206,7 @@ export default function fffExtension(pi: ExtensionAPI) {
1167
1206
  const grepResult = f.multiGrep({
1168
1207
  patterns: params.patterns,
1169
1208
  constraints: params.constraints,
1170
- maxMatchesPerFile: pageSize,
1209
+ maxMatchesPerFile: GREP_MAX_MATCHES_PER_FILE,
1171
1210
  pageSize,
1172
1211
  smartCase: true,
1173
1212
  cursor: (params.cursor ? getCursor(params.cursor) : null) ?? null,