pi-fovea 0.4.0 → 0.4.1
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 +26 -20
- package/cli.ts +3 -1
- package/package.json +1 -1
- package/skills/pi-fovea/SKILL.md +8 -8
- package/src/core/basins.ts +8 -3
- package/src/core/config.ts +3 -3
- package/src/core/ops.ts +234 -36
- package/src/core/render.ts +54 -11
- package/src/core/session.ts +21 -7
- package/src/core/sync.ts +92 -41
- package/src/index.ts +193 -61
- package/src/ui/settings.ts +10 -10
package/README.md
CHANGED
|
@@ -19,21 +19,21 @@ _See the whole repo on every prompt, sharp where you work and cheap everywhere e
|
|
|
19
19
|
|
|
20
20
|
pi-fovea hands the model a map of your repo on every prompt. The repo compiles once into a code graph across languages, where symbols, files, and route anchors join into one network. Each question becomes an interest vector that diffuses over the graph as heat. The renderer converts the field into a token-capped view: exact source locations and full signatures near your task, typed one-hop relationships next, and a skeleton of the rest.
|
|
21
21
|
|
|
22
|
-
After each assistant turn
|
|
22
|
+
At agent start Fovea establishes or checks its semantic baseline, so out-of-band edits made while Pi was idle enter context before the first model call. After each assistant turn it re-syncs again. Detection does not trust tool events: edits made by Pi tools, fabric_exec, bash, subagents, or an editor land identically, while comment- and formatting-only drift stays silent. A meaningful post-turn change is delivered as a **steer**, and Fovea triggers the continuation itself if the agent would otherwise wait.
|
|
23
23
|
|
|
24
24
|
## What the model gets
|
|
25
25
|
|
|
26
26
|
| Command | Ask | Answer |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| `fovea_sketch` | where is everything? |
|
|
29
|
-
| `fovea_focus` | what is this? |
|
|
30
|
-
| `fovea_dwell` | what else? |
|
|
28
|
+
| `fovea_sketch` | where is everything? | production-first silhouette; test and fixture architecture stays collapsed |
|
|
29
|
+
| `fovea_focus` | what is this? | exact matches, typed relationships, suggested reads, optional source scopes, and deterministic `fresh` views |
|
|
30
|
+
| `fovea_dwell` | what else? | widens the current focus and returns newly relevant neighbors |
|
|
31
31
|
| `fovea_impact` | what does this touch? | warms everything a file, symbol, or PR base reaches across languages |
|
|
32
|
-
| `grep` *(default
|
|
32
|
+
| `grep` *(default hybrid)* | graph or text? | bare identifiers, qualified symbols, repo paths, and routes use Fovea; search options and obvious regex retain native grep |
|
|
33
33
|
|
|
34
34
|
Focus normalizes camelCase and common inflections, so an approximate name such as `switchServer` can resolve `switchingServers`. If a query is still uncertain, Fovea returns nearby symbols with locations instead of a dead miss. Direct graph edges are labeled (caller, callee, route, shared literal, co-change), while unrelated same-file siblings remain collapsed.
|
|
35
35
|
|
|
36
|
-
The **
|
|
36
|
+
The **Hybrid grep** toggle is on by default. `grep({ pattern: "CreateUser" })`, `grep({ pattern: "Controller.create" })`, and route paths can navigate the graph. Calls with text-search options and obvious regexes delegate to Pi's native grep unchanged; a graph miss also falls back to native text. Disable the toggle for a purely native slot. Changing it reloads extensions so Pi and pi-fabric capture the same behavior.
|
|
37
37
|
|
|
38
38
|
### pi-fabric
|
|
39
39
|
|
|
@@ -54,10 +54,12 @@ return tools.call({ ref: action.ref, args: { query: "CreateUserHandler" } });
|
|
|
54
54
|
|
|
55
55
|
The stable explicit ref is `extensions.fovea_focus`, not bare `fovea_focus` or `fovea.fovea_focus`.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Runtime slash controls:
|
|
58
58
|
|
|
59
|
-
- `/fovea status` for graph
|
|
60
|
-
- `/fovea settings` for
|
|
59
|
+
- `/fovea status` for loaded versions, graph coverage, and active modes
|
|
60
|
+
- `/fovea settings` for a TUI configuration overlay
|
|
61
|
+
- `/fovea reset` for a fresh focus and sync baseline
|
|
62
|
+
- `/fovea reload` to activate updated extension source
|
|
61
63
|
|
|
62
64
|
## Install
|
|
63
65
|
|
|
@@ -99,14 +101,18 @@ fovea status /path/to/repo
|
|
|
99
101
|
|
|
100
102
|
## Turn sync
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
Continuous sync is on by default. Before an agent starts, Fovea establishes its baseline or injects any out-of-band drift before the first model call. After every assistant turn it compares extracted symbols, calls, imports, literals, and anchors again. Content hashes keep the unchanged fast path cheap, while comment- and formatting-only edits do not wake the model.
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
- **red**: a capped custom message naming route anchors that appeared or disappeared, plus files warmed by the edit cascade that the model has not focused on yet.
|
|
106
|
+
A meaningful change found before agent start is injected directly into that run. A post-turn route or dependency change is sent with `deliverAs: "steer"`; if the agent would otherwise settle, `triggerTurn` starts the continuation automatically. The compact update names directly changed files, route deltas, newly relevant files, and causal channels such as calls, imports, shared literals, tests, or co-change history. Clean turns remain silent unless `sync.ackClean` is enabled.
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
Runtime controls:
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
- `/fovea status` — loaded package/ast-grep versions, indexed coverage, anchor scopes, sync and grep modes.
|
|
111
|
+
- `/fovea reset` — clear focus disclosure/depth and establish a fresh sync baseline.
|
|
112
|
+
- `/fovea reload` — hot-reload extensions and activate newly installed source.
|
|
113
|
+
- `/fovea settings` — configure sync, budgets, and hybrid grep.
|
|
114
|
+
|
|
115
|
+
Turn sync off per repo or globally through settings, or with:
|
|
110
116
|
|
|
111
117
|
```sh
|
|
112
118
|
FOVEA_TURN_SYNC=off pi
|
|
@@ -118,12 +124,12 @@ Global settings live in `~/.pi/agent/fovea.json`. A trusted repo-level override
|
|
|
118
124
|
|
|
119
125
|
| Key | Default | Meaning |
|
|
120
126
|
| --- | :-----: | ------- |
|
|
121
|
-
| `sync.enabled` | `true` |
|
|
122
|
-
| `sync.budget` | `1024` | token cap for
|
|
127
|
+
| `sync.enabled` | `true` | pre-agent and post-turn continuous sync |
|
|
128
|
+
| `sync.budget` | `1024` | token cap for proactive steering context |
|
|
123
129
|
| `sync.ackClean` | `false` | toast after clean structural turns |
|
|
124
|
-
| `sync.warmFileThreshold` | `2` |
|
|
130
|
+
| `sync.warmFileThreshold` | `2` | newly relevant files that justify proactive model steering |
|
|
125
131
|
| `tools.defaultBudget` | `2000` | fallback maxTokens for the fovea_* tools |
|
|
126
|
-
| `tools.replaceGrep` | `true` |
|
|
132
|
+
| `tools.replaceGrep` | `true` | install hybrid native-text / bare-query graph grep |
|
|
127
133
|
|
|
128
134
|
## How routes are found
|
|
129
135
|
|
|
@@ -172,7 +178,7 @@ $$
|
|
|
172
178
|
v(t) = e^{-tL} \cdot s \quad \text{with} \quad L = I - D^{-1/2} W D^{-1/2}
|
|
173
179
|
$$
|
|
174
180
|
|
|
175
|
-
The four tools are the same operator at four timescales: sketch at $t=16$ with hub and anchor seeds, focus at $t=
|
|
181
|
+
The four tools are the same operator at four timescales: sketch at $t=16$ with production hub and anchor seeds, focus at $t=2$ with your query as seed, dwell doubling $t$ within that focus, and impact using changed files as seeds. Changing focus resets to the sharp timescale and its own disclosure scope.
|
|
176
182
|
|
|
177
183
|
The kernel is evaluated with a Chebyshev expansion. Rescale $M = L - I$ so the spectrum sits in $[-1,1]$; then with $T_k$ the Chebyshev polynomials and $I_k$ the modified Bessel functions:
|
|
178
184
|
|
|
@@ -190,7 +196,7 @@ $$
|
|
|
190
196
|
|
|
191
197
|
Measured against eight cloned projects, corpus junk sits below $\hat{p} \approx 0.27$ and real route shapes above $\hat{p} \approx 0.75$. The cutoff stays mid-cliff regardless of repo size.
|
|
192
198
|
|
|
193
|
-
Lineage: spectral-graph wavelets evaluated by shared Chebyshev recurrence, progressive image coding where the budget is a bitrate over significance-ordered coefficients, and foveated rendering. Aider's PageRank repo map is the fixed-timescale special case of this field. The full walkthrough of conductance tiers, specificity bridges, hub gravity, and
|
|
199
|
+
Lineage: spectral-graph wavelets evaluated by shared Chebyshev recurrence, progressive image coding where the budget is a bitrate over significance-ordered coefficients, and foveated rendering. Aider's PageRank repo map is the fixed-timescale special case of this field. The full walkthrough of conductance tiers, specificity bridges, hub gravity, and inferred regions lives in [docs/heat-diffusion.md](docs/heat-diffusion.md).
|
|
194
200
|
|
|
195
201
|
## Languages
|
|
196
202
|
|
package/cli.ts
CHANGED
|
@@ -56,7 +56,9 @@ try {
|
|
|
56
56
|
let out = "";
|
|
57
57
|
if (cmd === "status") {
|
|
58
58
|
const s = sketch(rootAt(0), 256);
|
|
59
|
-
|
|
59
|
+
const testAnchors = Number(s.details.testAnchors ?? 0);
|
|
60
|
+
out = `${s.details.files} files, ${s.details.nodes} symbols, ${s.details.productionAnchors ?? s.details.anchors} production anchors` +
|
|
61
|
+
(testAnchors ? `, ${testAnchors} test/fixture anchors collapsed` : "");
|
|
60
62
|
} else if (cmd === "sketch") {
|
|
61
63
|
const root = rootAt(0);
|
|
62
64
|
const B = numAt(pos[0] === root && pos.length > 1 ? 1 : 0) ?? 1400;
|
package/package.json
CHANGED
package/skills/pi-fovea/SKILL.md
CHANGED
|
@@ -9,22 +9,22 @@ pi-fovea maintains a cross-language code graph of the working repository — rou
|
|
|
9
9
|
|
|
10
10
|
## The loop
|
|
11
11
|
|
|
12
|
-
1. **`fovea_sketch`** —
|
|
13
|
-
2. **`fovea_focus` `<query>`** — point at a symbol name (close spellings work), route
|
|
14
|
-
3. **`fovea_dwell`** — optional second look. If
|
|
15
|
-
4. **`fovea_impact`** — blast radius. Seed with
|
|
12
|
+
1. **`fovea_sketch`** — production-first silhouette. Shipped routes and source regions lead; test and fixture architecture is collapsed. Start here in an unfamiliar repo. ~256–1024 tokens.
|
|
13
|
+
2. **`fovea_focus` `<query>`** — point at a symbol name (close spellings work), route, env key, or file. The active seed and direct relationships always remain visible; previously seen periphery is suppressed only within that focus. A different focus resets to sharp context. Use `path`, `language`, or `kind` to scope output and `fresh: true` for a reproducible full view. Structured details include nodes and suggested read windows.
|
|
14
|
+
3. **`fovea_dwell`** — optional second look. If focus says more results remain, dwell widens only the current focus and returns newly relevant neighbors.
|
|
15
|
+
4. **`fovea_impact`** — blast radius. Seed with repo-relative `files`, symbols, uncommitted changes, or a PR `base`. Output is likely review order with causal channels (calls, imports, literals, routes, tests, inheritance, co-change).
|
|
16
16
|
|
|
17
17
|
All four accept `maxTokens` (256–16000). Budget is roughly 4 chars per token.
|
|
18
18
|
|
|
19
19
|
## Working rules
|
|
20
20
|
|
|
21
|
-
- **
|
|
21
|
+
- **Do not bulk-read to discover structure.** Focus first, then read its suggested ranges. Native grep semantics remain available whenever grep receives path/glob/literal/context/limit options or an obvious regex; unresolved graph queries fall back to native text.
|
|
22
22
|
- **Impact before destructive edits.** One `fovea_impact` call is cheaper than rediscovering dependents by breaking them.
|
|
23
23
|
- **Sketch is the safe opening bid.** If unsure, pay for a sketch; it almost never exceeds a few hundred tokens.
|
|
24
24
|
|
|
25
25
|
## Turn sync
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Before an agent starts, pi-fovea establishes its baseline or injects out-of-band semantic drift into that run. After each assistant turn it compares again; meaningful route/dependency drift is delivered as a **steer**, and Fovea triggers a continuation if the agent would otherwise stop. Treat its changed files, route deltas, and causal channels as continuous task context. Comment- and formatting-only edits stay silent.
|
|
28
28
|
|
|
29
29
|
Sync is **mutation-path agnostic**: pi's edit/write tools, a pi-fabric `fabric_exec` program's inner `pi.edit`, a bash heredoc, a subagent, or an editor save outside the session all register identically. Content hashes are the source of truth; tool events are not consulted for detection. In repos with no `.git` directory this is also the only drift signal — do not fall back to `git status` assumptions.
|
|
30
30
|
|
|
@@ -36,7 +36,7 @@ When writing or editing code **inside a `fabric_exec` program**, the fovea tools
|
|
|
36
36
|
- For dynamic discovery, use `const hits = await tools.search({ query: "fovea_focus" })`, then call the returned namespaced ref with `tools.call({ ref: hits[0].ref, args: { query: "CreateUserHandler", maxTokens: 6000 } })`. The stable explicit ref is `extensions.fovea_focus`; bare `fovea_focus` and `fovea.fovea_focus` are invalid.
|
|
37
37
|
- Prefer a single `extensions.fovea_impact(...)` call over hand-rolled grep fan-outs when computing what an edit touches — the graph already resolved imports/calls across Go, TypeScript, Python, and Java.
|
|
38
38
|
- Any file mutation performed by the program (including `pi.edit`/`pi.write` calls inside the sandbox) is picked up by turn sync automatically, so post-edit verification does not need a re-sketch.
|
|
39
|
-
-
|
|
39
|
+
- Sketch `details` carries coverage counts; its compact text names the highest-value entry points. On an unfamiliar repo, fetch it once and reuse it instead of rediscovering entry points per call.
|
|
40
40
|
|
|
41
41
|
## CLI
|
|
42
42
|
|
|
@@ -44,4 +44,4 @@ The same engine runs headlessly as the `fovea` binary (repo root scan, plus JSON
|
|
|
44
44
|
|
|
45
45
|
## Settings
|
|
46
46
|
|
|
47
|
-
`/fovea
|
|
47
|
+
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`. `tools.replaceGrep` installs hybrid grep: native text semantics plus bare-query graph navigation.
|
package/src/core/basins.ts
CHANGED
|
@@ -15,11 +15,14 @@ const MIN_BASIN_SIZE = 4;
|
|
|
15
15
|
|
|
16
16
|
// eligible marks nodes that may seed a basin (symbols, not files/anchors: a
|
|
17
17
|
// file's contains-star has ~zero triangle density and yields useless seeds).
|
|
18
|
+
// include optionally constrains every member, for operation-specific views
|
|
19
|
+
// such as production-first sketching without changing the underlying graph.
|
|
18
20
|
export const detectBasins = (
|
|
19
21
|
adjacency: Map<number, Array<{ to: number; kind: string; w: number }>>,
|
|
20
22
|
conductance: Float64Array,
|
|
21
23
|
n: number,
|
|
22
24
|
eligible?: (i: number) => boolean,
|
|
25
|
+
include?: (i: number) => boolean,
|
|
23
26
|
): Basin[] => {
|
|
24
27
|
// Triangle density: fraction of a node's neighbors that are co-neighbors.
|
|
25
28
|
// Cheap O(deg^2) sampling with degree cap — hubs are star points anyway.
|
|
@@ -60,12 +63,14 @@ export const detectBasins = (
|
|
|
60
63
|
const order: number[] = [seed];
|
|
61
64
|
let internal = 0;
|
|
62
65
|
const boundary = new Map<number, number>();
|
|
63
|
-
for (const e of adjacency.get(seed) ?? [])
|
|
66
|
+
for (const e of adjacency.get(seed) ?? []) {
|
|
67
|
+
if (!include || include(e.to)) boundary.set(e.to, (boundary.get(e.to) ?? 0) + e.w);
|
|
68
|
+
}
|
|
64
69
|
while (order.length < MAX_BASIN_SIZE && boundary.size) {
|
|
65
70
|
let best = -1;
|
|
66
71
|
let bestRatio = -1;
|
|
67
72
|
for (const [j, inW] of boundary) {
|
|
68
|
-
if (claimed.has(j) || members.has(j)) continue;
|
|
73
|
+
if (claimed.has(j) || members.has(j) || (include && !include(j))) continue;
|
|
69
74
|
const total = [...(adjacency.get(j) ?? [])].reduce((s, e) => s + e.w, 0);
|
|
70
75
|
const ratio = total > 0 ? inW / total : 0;
|
|
71
76
|
if (ratio > bestRatio) { bestRatio = ratio; best = j; }
|
|
@@ -79,7 +84,7 @@ export const detectBasins = (
|
|
|
79
84
|
internal += boundary.get(best) ?? 0;
|
|
80
85
|
boundary.delete(best);
|
|
81
86
|
for (const e of adjacency.get(best) ?? []) {
|
|
82
|
-
if (members.has(e.to)) continue;
|
|
87
|
+
if (members.has(e.to) || (include && !include(e.to))) continue;
|
|
83
88
|
boundary.set(e.to, (boundary.get(e.to) ?? 0) + e.w);
|
|
84
89
|
}
|
|
85
90
|
const cut = [...boundary.values()].reduce((a, b2) => a + b2, 0);
|
package/src/core/config.ts
CHANGED
|
@@ -11,18 +11,18 @@ import path from "node:path";
|
|
|
11
11
|
interface FoveaSyncConfig {
|
|
12
12
|
/** turn_end feedback loop on/off (the default-on, opt-out knob). */
|
|
13
13
|
enabled: boolean;
|
|
14
|
-
/** Token budget for
|
|
14
|
+
/** Token budget for proactive model steering context. */
|
|
15
15
|
budget: number;
|
|
16
16
|
/** Also send a tiny model-visible ack on clean turns (default false: silent green). */
|
|
17
17
|
ackClean: boolean;
|
|
18
|
-
/** Number of newly
|
|
18
|
+
/** Number of newly relevant files that justifies proactive steering on its own. */
|
|
19
19
|
warmFileThreshold: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
interface FoveaToolsConfig {
|
|
23
23
|
/** Budget applied when a fovea_* tool call omits maxTokens. */
|
|
24
24
|
defaultBudget: number;
|
|
25
|
-
/**
|
|
25
|
+
/** Install hybrid grep: native text semantics plus bare-query Fovea navigation. */
|
|
26
26
|
replaceGrep: boolean;
|
|
27
27
|
}
|
|
28
28
|
|