pi-fovea 0.13.2 → 0.13.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/README.md +4 -1
- package/package.json +1 -1
- package/skills/pi-fovea/SKILL.md +1 -1
- package/src/core/config.ts +18 -2
- package/src/ui/settings.ts +54 -32
package/README.md
CHANGED
|
@@ -208,7 +208,10 @@ FOVEA_TURN_SYNC=off pi
|
|
|
208
208
|
|
|
209
209
|
Global settings live in `~/.pi/agent/fovea.json`. A trusted repo-level override
|
|
210
210
|
sits in `<repo>/.pi/fovea.json`. These are the same two scopes pi-fabric uses
|
|
211
|
-
with `fabric.json`.
|
|
211
|
+
with `fabric.json`. In `/fovea settings`, the configured external-editor key
|
|
212
|
+
(`Ctrl+G` by default) switches both the displayed values and save destination
|
|
213
|
+
between project overrides and global defaults. A project override can remain
|
|
214
|
+
effective while its global default is being edited.
|
|
212
215
|
|
|
213
216
|
| Key | Default | Meaning |
|
|
214
217
|
| --- | :-----: | ------- |
|
package/package.json
CHANGED
package/skills/pi-fovea/SKILL.md
CHANGED
|
@@ -45,6 +45,6 @@ The same engine runs headlessly as the `fovea` binary (repo root scan, plus JSON
|
|
|
45
45
|
|
|
46
46
|
## Settings
|
|
47
47
|
|
|
48
|
-
Use `/fovea status` for loaded version and index coverage, `/fovea reset` for fresh state, `/fovea reload` after updates, and `/fovea settings` for configuration. Files live under `~/.pi/agent/fovea.json` or trusted `.pi/fovea.json
|
|
48
|
+
Use `/fovea status` for loaded version and index coverage, `/fovea reset` for fresh state, `/fovea reload` after updates, and `/fovea settings` for configuration. Files live under `~/.pi/agent/fovea.json` or trusted `.pi/fovea.json`; the external-editor key (`Ctrl+G` by default) switches the displayed and saved layer between global defaults and project overrides. `tools.grepMode` controls the grep integration: `"augment"` (default) keeps native grep semantics and appends a Fovea graph section to symbol-query results — including `pi.grep` calls inside fabric_exec — `"replace"` keeps the legacy bare-query takeover, `"off"` is native only.
|
|
49
49
|
|
|
50
50
|
Budget overflow is not a dead end: any `… more results collapsed or outside budget` footer names a tmp artifact (`/tmp/pi-fovea-<op>-<hash>.txt`) holding the FULL list — read or grep that file for the remainder. Reach for `fovea_dwell` when you want a wider neighborhood, not just more of the same list.
|
package/src/core/config.ts
CHANGED
|
@@ -155,9 +155,12 @@ const readConfigFile = (file: string): Record<string, unknown> => {
|
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
const resolveFoveaConfig = (
|
|
159
|
+
scopes: FoveaConfigScopes,
|
|
160
|
+
includeProject: boolean,
|
|
161
|
+
): FoveaConfig => {
|
|
159
162
|
let config = applyPartial(DEFAULT_FOVEA_CONFIG, readConfigFile(globalFoveaConfigPath(scopes.agentDir)));
|
|
160
|
-
if (
|
|
163
|
+
if (includeProject) {
|
|
161
164
|
config = applyPartial(config, readConfigFile(projectFoveaConfigPath(scopes.cwd)));
|
|
162
165
|
}
|
|
163
166
|
// Environment override mirrors pi-fabric's PI_* precedence over stored values.
|
|
@@ -166,6 +169,19 @@ export const loadFoveaConfig = (scopes: FoveaConfigScopes): FoveaConfig => {
|
|
|
166
169
|
return config;
|
|
167
170
|
};
|
|
168
171
|
|
|
172
|
+
export const loadFoveaConfigForScope = (
|
|
173
|
+
scopes: FoveaConfigScopes,
|
|
174
|
+
scope: FoveaConfigScope,
|
|
175
|
+
): FoveaConfig => {
|
|
176
|
+
if (scope === "project" && !scopes.projectTrusted) {
|
|
177
|
+
throw new Error("Cannot load project Fovea configuration for an untrusted project");
|
|
178
|
+
}
|
|
179
|
+
return resolveFoveaConfig(scopes, scope === "project");
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const loadFoveaConfig = (scopes: FoveaConfigScopes): FoveaConfig =>
|
|
183
|
+
resolveFoveaConfig(scopes, scopes.projectTrusted);
|
|
184
|
+
|
|
169
185
|
const mergeDeep = (existing: Record<string, unknown>, partial: Record<string, unknown>): Record<string, unknown> => {
|
|
170
186
|
const out = { ...existing };
|
|
171
187
|
for (const [key, value] of Object.entries(partial)) {
|
package/src/ui/settings.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SelectList submenus, dotted-id coercion) so both extensions feel like one
|
|
4
4
|
// settings surface. Persistence: fovea.json under the pi agent dir, with a
|
|
5
5
|
// project override beneath pi's configured project resource directory; the
|
|
6
|
-
// external-editor keybinding
|
|
6
|
+
// external-editor keybinding switches the configuration layer being viewed and saved.
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
CONFIG_DIR_NAME,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
buildPartialFromId,
|
|
31
31
|
loadFoveaConfig,
|
|
32
|
+
loadFoveaConfigForScope,
|
|
32
33
|
SYNC_MODES,
|
|
33
34
|
saveFoveaConfig,
|
|
34
35
|
type FoveaConfig,
|
|
@@ -133,15 +134,20 @@ export interface FoveaSettingsComponentOptions {
|
|
|
133
134
|
initialSaveScope?: FoveaConfigScope;
|
|
134
135
|
projectScopeAvailable?: boolean;
|
|
135
136
|
onSaveScopeChange?: (scope: FoveaConfigScope) => void;
|
|
137
|
+
itemsForSaveScope?: (scope: FoveaConfigScope) => SettingItem[];
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
export class FoveaSettingsComponent extends Container {
|
|
139
|
-
|
|
141
|
+
settingsList: SettingsList;
|
|
140
142
|
private readonly theme: Theme;
|
|
141
143
|
private readonly keybindings: Pick<KeybindingsManager, "matches" | "getKeys"> | undefined;
|
|
142
144
|
private readonly saveScopeText: Text;
|
|
145
|
+
private readonly settingsListContainer: Container;
|
|
143
146
|
private readonly projectScopeAvailable: boolean;
|
|
147
|
+
private readonly onChange: (id: string, newValue: string) => void;
|
|
148
|
+
private readonly onCancel: () => void;
|
|
144
149
|
private readonly onSaveScopeChange: (scope: FoveaConfigScope) => void;
|
|
150
|
+
private readonly itemsForSaveScope: ((scope: FoveaConfigScope) => SettingItem[]) | undefined;
|
|
145
151
|
private saveScope: FoveaConfigScope;
|
|
146
152
|
|
|
147
153
|
constructor(
|
|
@@ -158,16 +164,19 @@ export class FoveaSettingsComponent extends Container {
|
|
|
158
164
|
this.saveScope = options.initialSaveScope === "global" || !this.projectScopeAvailable
|
|
159
165
|
? "global"
|
|
160
166
|
: "project";
|
|
167
|
+
this.onChange = onChange;
|
|
168
|
+
this.onCancel = onCancel;
|
|
161
169
|
this.onSaveScopeChange = options.onSaveScopeChange ?? (() => {});
|
|
170
|
+
this.itemsForSaveScope = options.itemsForSaveScope;
|
|
162
171
|
this.addChild(new DynamicBorder((text) => theme.fg("border", text)));
|
|
163
172
|
this.saveScopeText = new Text("", 1, 0);
|
|
164
173
|
this.updateSaveScopeText();
|
|
165
174
|
this.addChild(this.saveScopeText);
|
|
166
175
|
this.addChild(new Spacer(1));
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.addChild(this.
|
|
176
|
+
this.settingsListContainer = new Container();
|
|
177
|
+
this.settingsList = this.createSettingsList(items);
|
|
178
|
+
this.settingsListContainer.addChild(this.settingsList);
|
|
179
|
+
this.addChild(this.settingsListContainer);
|
|
171
180
|
this.addChild(new DynamicBorder((text) => theme.fg("border", text)));
|
|
172
181
|
}
|
|
173
182
|
|
|
@@ -177,6 +186,12 @@ export class FoveaSettingsComponent extends Container {
|
|
|
177
186
|
this.saveScope = this.saveScope === "project" ? "global" : "project";
|
|
178
187
|
this.updateSaveScopeText();
|
|
179
188
|
this.onSaveScopeChange(this.saveScope);
|
|
189
|
+
const nextItems = this.itemsForSaveScope?.(this.saveScope);
|
|
190
|
+
if (nextItems) {
|
|
191
|
+
this.settingsListContainer.clear();
|
|
192
|
+
this.settingsList = this.createSettingsList(nextItems);
|
|
193
|
+
this.settingsListContainer.addChild(this.settingsList);
|
|
194
|
+
}
|
|
180
195
|
return;
|
|
181
196
|
}
|
|
182
197
|
this.settingsList.handleInput(data);
|
|
@@ -187,17 +202,32 @@ export class FoveaSettingsComponent extends Container {
|
|
|
187
202
|
this.updateSaveScopeText();
|
|
188
203
|
}
|
|
189
204
|
|
|
205
|
+
private createSettingsList(items: SettingItem[]): SettingsList {
|
|
206
|
+
return new SettingsList(
|
|
207
|
+
items,
|
|
208
|
+
10,
|
|
209
|
+
settingsListTheme(this.theme),
|
|
210
|
+
this.onChange,
|
|
211
|
+
this.onCancel,
|
|
212
|
+
{ enableSearch: true },
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
190
216
|
private updateSaveScopeText(): void {
|
|
191
217
|
const destination = this.saveScope === "project"
|
|
192
|
-
? `Project (${CONFIG_DIR_NAME}/fovea.json)`
|
|
193
|
-
: `Global (~/${CONFIG_DIR_NAME}/agent/fovea.json)`;
|
|
218
|
+
? `Project overrides (${CONFIG_DIR_NAME}/fovea.json)`
|
|
219
|
+
: `Global defaults (~/${CONFIG_DIR_NAME}/agent/fovea.json)`;
|
|
194
220
|
const keys = this.keybindings?.getKeys("app.editor.external") ?? [];
|
|
195
221
|
const shortcut = keys.map(formatKey).join("/");
|
|
196
|
-
const hint = this.projectScopeAvailable
|
|
197
|
-
?
|
|
198
|
-
:
|
|
222
|
+
const hint = !this.projectScopeAvailable
|
|
223
|
+
? " · project scope unavailable for untrusted projects"
|
|
224
|
+
: !shortcut
|
|
225
|
+
? " · scope switch key unavailable"
|
|
226
|
+
: this.saveScope === "global"
|
|
227
|
+
? ` · ${shortcut} switches scope · project overrides may remain active here`
|
|
228
|
+
: ` · ${shortcut} switches scope`;
|
|
199
229
|
this.saveScopeText.setText(
|
|
200
|
-
this.theme.fg("muted", "
|
|
230
|
+
this.theme.fg("muted", "Editing: ") +
|
|
201
231
|
this.theme.fg("accent", destination) +
|
|
202
232
|
this.theme.fg("dim", hint),
|
|
203
233
|
);
|
|
@@ -299,7 +329,7 @@ export const openFoveaSettings = async (
|
|
|
299
329
|
projectTrusted: context.isProjectTrusted(),
|
|
300
330
|
};
|
|
301
331
|
let saveScope: FoveaConfigScope = scopes.projectTrusted ? "project" : "global";
|
|
302
|
-
let config =
|
|
332
|
+
let config = loadFoveaConfigForScope(scopes, saveScope);
|
|
303
333
|
const initialGrepRegistration = config.tools.grepMode === "replace";
|
|
304
334
|
let dirty = false;
|
|
305
335
|
|
|
@@ -313,16 +343,19 @@ export const openFoveaSettings = async (
|
|
|
313
343
|
);
|
|
314
344
|
return;
|
|
315
345
|
}
|
|
316
|
-
config =
|
|
346
|
+
config = loadFoveaConfigForScope(scopes, saveScope);
|
|
317
347
|
dirty = true;
|
|
318
348
|
deps.onConfigApplied?.();
|
|
319
349
|
};
|
|
320
350
|
|
|
321
351
|
await context.ui.custom<void>((tui, theme, keybindings, done) => {
|
|
322
|
-
const
|
|
352
|
+
const itemsForScope = (scope: FoveaConfigScope): SettingItem[] => {
|
|
353
|
+
config = loadFoveaConfigForScope(scopes, scope);
|
|
354
|
+
return buildItems(theme, config);
|
|
355
|
+
};
|
|
323
356
|
return new FoveaSettingsComponent(
|
|
324
357
|
theme,
|
|
325
|
-
|
|
358
|
+
itemsForScope(saveScope),
|
|
326
359
|
(id, newValue) => apply(id, coerceValue(id, newValue, config)),
|
|
327
360
|
() => done(),
|
|
328
361
|
{
|
|
@@ -333,27 +366,16 @@ export const openFoveaSettings = async (
|
|
|
333
366
|
saveScope = scope;
|
|
334
367
|
tui.requestRender();
|
|
335
368
|
},
|
|
369
|
+
itemsForSaveScope: itemsForScope,
|
|
336
370
|
},
|
|
337
371
|
);
|
|
338
372
|
});
|
|
339
373
|
|
|
340
374
|
if (dirty) context.ui.notify("Fovea settings saved.", "info");
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// already-saved value (config is reloaded from disk on open).
|
|
346
|
-
const applyPartialLocal = (config: FoveaConfig, id: string, value: unknown): FoveaConfig => {
|
|
347
|
-
const next: FoveaConfig = {
|
|
348
|
-
sync: { ...config.sync },
|
|
349
|
-
tools: { ...config.tools },
|
|
375
|
+
const effectiveConfig = loadFoveaConfig(scopes);
|
|
376
|
+
return {
|
|
377
|
+
grepRegistrationChanged:
|
|
378
|
+
(effectiveConfig.tools.grepMode === "replace") !== initialGrepRegistration,
|
|
350
379
|
};
|
|
351
|
-
const segments = id.split(".");
|
|
352
|
-
let target: Record<string, unknown> = next as unknown as Record<string, unknown>;
|
|
353
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
354
|
-
target = target[segments[i]!] as Record<string, unknown>;
|
|
355
|
-
}
|
|
356
|
-
target[segments[segments.length - 1]!] = value;
|
|
357
|
-
return next;
|
|
358
380
|
};
|
|
359
381
|
|