@alucard_24/pi-codeindex 0.0.0-stage → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alucard24
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,346 @@
1
- # Temporary Holding Version
1
+ # pi-codeindex
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Indice semantico locale del codice per l'agente pi: embedding con
4
+ **EmbeddingGemma 2** (ONNX, in-process) e ricerca ibrida (coseno + lessico)
5
+ che restituisce `path:righe` + simbolo + anteprima. Nessun server, nessun
6
+ database: due file in `~/.cache/pi-codeindex/<sha1(repo)>/`.
7
+
8
+ ## Requisiti
9
+
10
+ - Node 22+ (testato su 26.5.1).
11
+ - CPU sempre disponibile come profilo base nelle build native supportate.
12
+ - GPU opzionale: CUDA su Linux x64, WebGPU dove incluso nel binding,
13
+ DirectML su Windows, CoreML su macOS. Non tutte le combinazioni modello,
14
+ driver e hardware sono verificate: vedi la matrice sotto.
15
+
16
+ ## Installazione
17
+
18
+ ### Da npm, solo su richiesta
19
+
20
+ ```bash
21
+ pi -e npm:@alucard_24/pi-codeindex@1.0.0
22
+ ```
23
+
24
+ Carica il pacchetto per questa invocazione, senza aggiungerlo alle impostazioni
25
+ globali di pi. Dalla sessione puoi usare `/codeindex configure` e
26
+ `/codeindex doctor`. I pesi vengono scaricati solo al primo utilizzo del modello;
27
+ CUDA e Vulkan richiedono i setup opzionali descritti sotto.
28
+
29
+ Il pacchetto npm include codice, setup, schema, README, licenza e query di
30
+ esempio. Il repository GitHub resta privato; piano di lavoro, report completi,
31
+ harness sperimentale e test non sono inclusi nel tarball npm.
32
+
33
+ ### Dal sorgente (sviluppo)
34
+
35
+ ```bash
36
+ npm install
37
+ npm run setup-gpu # solo GPU NVIDIA: scarica il provider CUDA e lo configura
38
+ ```
39
+
40
+ `setup-gpu.sh` è **solo Linux x64** e richiede CUDA 13 + `patchelf`.
41
+ Va rieseguito se un aggiornamento/reinstallazione sostituisce il provider.
42
+ DirectML, CoreML e WebGPU usano i provider inclusi nel binding nativo;
43
+ non richiedono questo script CUDA.
44
+
45
+ Il pacchetto espone anche il binario `pi-codeindex`: gli esempi
46
+ `node src/cli.js ...` possono essere eseguiti come `pi-codeindex ...`
47
+ quando il binario è sul PATH. `pi-codeindex setup-gpu` avvia il setup CUDA.
48
+ Nessun postinstall scarica i pesi e nessuna configurazione globale di pi viene
49
+ modificata dal pacchetto.
50
+
51
+ ## Embedding e backend
52
+
53
+ Il modello **EmbeddingGemma 2** gira localmente, in-process. I soli pesi testo
54
+ vengono scaricati al primo utilizzo (anche dall'indicizzazione automatica) e
55
+ riutilizzati fra repository. Nessun server o API di embedding; dopo il primo
56
+ download è possibile lavorare offline. Il codice da embeddare non viene
57
+ inviato al server dei pesi. Un processo pi mantiene una propria pipeline.
58
+
59
+ | `device` | Default `dtype: auto` | Stato |
60
+ |---|---|---|
61
+ | `cpu` | `q8` | inferenza reale verificata su Linux x64 |
62
+ | `cuda` | `fp16` | inferenza reale verificata su NVIDIA/Linux x64; setup opzionale |
63
+ | `webgpu` | `fp16` | inferenza reale verificata su questa macchina Linux; supporto hardware/driver variabile |
64
+ | `dml` | `fp16` | DirectML/Windows: integrazione e policy testate, hardware non verificato |
65
+ | `coreml` | `fp16` | CoreML/macOS: integrazione e policy testate, hardware non verificato |
66
+
67
+ **Vulkan è selezionabile come backend nativo di WebGPU**, non come provider
68
+ ONNX autonomo: usa `device: "webgpu"` + `webgpuBackend: "vulkan"` dopo il setup
69
+ opzionale sotto. `device: "vulkan"` resta un errore esplicativo. Con
70
+ `webgpuBackend: "auto"` decide Dawn: non si garantisce Vulkan e non lo si
71
+ presenta come forzato. Stato e metadati distinguono `webgpu→vulkan/fp16` da
72
+ `webgpu→auto/fp16`.
73
+ Un provider presente nel binding non garantisce compatibilità, velocità,
74
+ utilizzo della GPU dedicata o copertura totale del grafo: ORT può eseguire
75
+ alcuni operatori su CPU. Gli smoke test non sostituiscono un benchmark di
76
+ retrieval/performance per piattaforma. `q4` è disponibile ma il drift non è
77
+ validato sull'eval.
78
+
79
+ **Auto:** Linux prova CUDA → WebGPU → CPU; Windows DirectML → WebGPU → CPU;
80
+ macOS CoreML → WebGPU → CPU. I provider assenti vengono saltati; una prova del
81
+ modello reale deve riuscire prima che il profilo venga fissato. Tentativi e
82
+ fallback sono visibili nei log e nello stato/avvisi dell'estensione.
83
+ Un device forzato fallisce per default; `fallback: "cpu"` autorizza il ripiego.
84
+ Il fallback avviene solo all'inizializzazione, mai durante l'embedding di un
85
+ indice già iniziato. La prima riconciliazione di un processo inizializza il
86
+ modello anche se l'indice era caldo; i vecchi tempi di re-index sotto indicati
87
+ precedono questo cambiamento.
88
+
89
+ ### WebGPU con Vulkan forzato (opzionale, Linux x64)
90
+
91
+ ```bash
92
+ npm run setup-vulkan
93
+ CODEINDEX_OFFLINE=true node src/cli.js doctor --probe --device webgpu --webgpu-backend vulkan
94
+ CODEINDEX_DEVICE=webgpu CODEINDEX_WEBGPU_BACKEND=vulkan pi -e ./src/extension.js
95
+ ```
96
+
97
+ Il setup richiede **compilatore C++17, header Node/N-API, `tar`, `patchelf` e
98
+ rete** per scaricare i piccoli sorgenti del binding. Non compila ORT/Dawn e
99
+ non richiede CUDA o il Vulkan SDK: riusa il runtime precompilato con WebGPU;
100
+ per eseguire serve il driver Vulkan. Header fuori dai percorsi standard:
101
+ `npm run setup-vulkan -- --node-headers /percorso/include/node`.
102
+
103
+ Il binding Node standard 1.30.0 non espone il selettore Dawn. Il setup
104
+ compila un binding opzionale da un commit ORT fissato e con hash dei sorgenti
105
+ verificati. Binding e copia del runtime (con SONAME distinto) restano in
106
+ `~/.cache/pi-codeindex/native/`: **nessuna modifica a `node_modules`**, nessun
107
+ provider standard sostituito. Il backend aggiunto ha nome unico e priorità
108
+ negativa: viene usato solo da una sessione che lo richiede esplicitamente.
109
+ Prima del caricamento si verificano gli hash; un controllo negativo obbligatorio
110
+ rifiuta i binding che ignorano il selettore. Manca il setup, il runtime è
111
+ cambiato o non esiste un adapter Vulkan? Errore, non WebGPU automatico nascosto.
112
+ Il fallback CPU resta solo quello già autorizzato da `fallback: "cpu"`.
113
+
114
+ Il setup è idempotente; dopo un cambio di runtime può essere necessario
115
+ rieseguirlo. `pi-codeindex setup-vulkan` è equivalente. Riavvia pi dopo setup
116
+ o cambi di profilo. Il backend WebGPU entra nell'identità dell'indice: cambiare
117
+ `auto` ↔ `vulkan` richiede re-index. Con `device: "auto"` il selettore riguarda
118
+ solo l'eventuale tentativo WebGPU: CUDA può ancora essere scelto prima.
119
+
120
+ Windows richiederebbe anche una build ORT/Dawn con Vulkan abilitato: **non
121
+ implementata/verificata** da questo setup. AMD e Intel richiedono prove sulle
122
+ rispettive GPU. Nessuna promessa di vantaggio prestazionale rispetto a CUDA.
123
+ Le prove con il modello completo richiedono VRAM libera; il processo `doctor`
124
+ riporta l'errore e non termina applicazioni GPU altrui per liberarla.
125
+
126
+ Test opzionali reali, dopo setup e download dei pesi fp16:
127
+ `CODEINDEX_TEST_VULKAN=1 node --test test/vulkan-integration.test.js`.
128
+ La suite normale non richiede GPU né compila addon. Verificati su questa
129
+ macchina Linux x64 / RTX 5070 Ti: EmbeddingGemma fp16, indice incrementale,
130
+ ricerca su fixture e coesistenza con i provider standard. Queste prove non
131
+ sostituiscono un benchmark di qualità o prestazioni su altri hardware.
132
+
133
+ ## Configurazione
134
+
135
+ Dentro pi:
136
+
137
+ ```text
138
+ /codeindex configure menu interattivo + conferma salvataggio
139
+ /codeindex config valori effettivi, provenienza e percorso
140
+ /codeindex config set device webgpu
141
+ /codeindex config set dtype fp16
142
+ /codeindex config set webgpuBackend vulkan solo WebGPU, setup-vulkan richiesto
143
+ /codeindex config set fallback cpu
144
+ /codeindex config unset device ripristina la scelta automatica
145
+ /codeindex doctor inventario, senza inferenza/download
146
+ /codeindex doctor --probe --device webgpu prova reale isolata (può scaricare i pesi)
147
+ ```
148
+
149
+ La configurazione è **della macchina**, non della sessione pi:
150
+ `$XDG_CONFIG_HOME/pi-codeindex/config.json` oppure
151
+ `~/.config/pi-codeindex/config.json` (`%APPDATA%/pi-codeindex/config.json`
152
+ su Windows). `CODEINDEX_CONFIG=/percorso/config.json` sceglie un file diverso,
153
+ anche per un singolo progetto; nessun file del repository viene letto
154
+ implicitamente. Non vengono modificate le impostazioni di pi.
155
+ Precedenza: **default < file < ambiente**. Il menu salva solo i campi cambiati,
156
+ non gli override d'ambiente. JSON malformato/chiavi sconosciute producono errori,
157
+ non default silenziosi. Schema per editor: [config.schema.json](config.schema.json).
158
+
159
+ ```json
160
+ {
161
+ "device": "auto",
162
+ "dtype": "auto",
163
+ "fallback": "error",
164
+ "offline": false,
165
+ "cpuThreads": 0
166
+ }
167
+ ```
168
+
169
+ | Campo | Valori | Override ambiente |
170
+ |---|---|---|
171
+ | `device` | `auto`, `cpu`, `cuda`, `webgpu`, `dml`, `coreml` | `CODEINDEX_DEVICE` |
172
+ | `dtype` | `auto`, `q8`, `q4`, `fp16` | `CODEINDEX_DTYPE` |
173
+ | `webgpuBackend` | `auto` (default), `vulkan` forzato; riguarda solo WebGPU | `CODEINDEX_WEBGPU_BACKEND` |
174
+ | `fallback` | `error` (default), `cpu`; riguarda device forzati | `CODEINDEX_FALLBACK` |
175
+ | `offline` | booleano; niente download, errore se mancano pesi | `CODEINDEX_OFFLINE=true\|false\|1\|0` |
176
+ | `cacheDir` | cache pesi, default `~/.cache/pi-codeindex/text-model` | `CODEINDEX_MODEL_CACHE` |
177
+ | `cpuThreads` | 0 = default ORT, oppure 1–256; non limita la GPU | `CODEINDEX_CPU_THREADS` |
178
+
179
+ **Riavvia pi con un nuovo processo** dopo una modifica: non si cambia pipeline
180
+ nel mezzo di un run. Il menu non riavvia né reindicizza da solo. Device, dtype
181
+ e backend WebGPU effettivi sono salvati nell'indice; se diversi, la ricerca rifiuta di mescolare
182
+ i vettori e chiede un re-index. Gli indici precedenti senza `device` vengono
183
+ ricostruiti alla prossima indicizzazione.
184
+
185
+ Da shell, senza avviare pi:
186
+
187
+ ```bash
188
+ node src/cli.js config show
189
+ node src/cli.js config set device cpu
190
+ node src/cli.js config set cpuThreads 4
191
+ node src/cli.js doctor --probe --device cpu --json
192
+ node src/cli.js doctor --probe --all # prove sequenziali dei provider presenti
193
+ node src/cli.js doctor --probe --device webgpu --webgpu-backend vulkan
194
+ CODEINDEX_DEVICE=cuda CODEINDEX_DTYPE=fp16 pi -e ./src/extension.js
195
+ ```
196
+
197
+ `doctor` senza `--probe` non carica/scarica il modello. Le prove reali girano
198
+ in subprocessi isolati con timeout di 120 s per backend; `--all` può scaricare
199
+ più varianti. Ogni provider forzato viene provato senza fallback, per non
200
+ attribuire alla GPU un successo ottenuto in CPU. Usa `offline: true` per provarle solo dalla cache. Un crash nativo
201
+ di una prova non abbatte pi; questo isolamento riguarda `doctor`, non la normale
202
+ pipeline in-process. Smoke del menu e dei comandi via RPC, senza chiamate al
203
+ modello: `CODEINDEX_TEST_RPC=1 node --test test/config-rpc.test.js` (richiede
204
+ `pi` sul PATH; usa un file config temporaneo, non modifica quello utente).
205
+
206
+ ## Comandi
207
+
208
+ ```bash
209
+ node src/cli.js index [--root DIR] [--max-files 5000] [--max-bytes 200] [--force]
210
+ node src/cli.js status [--root DIR]
211
+ node src/cli.js search "query" [--k 8] [--path-prefix src/] [--stale] [--bundle] [--json]
212
+ node src/cli.js eval [--file eval/queries.jsonl] [--json]
213
+ node src/cli.js embed-test
214
+ ```
215
+
216
+ - `index` è incrementale: confronta l'hash di ogni file e riusa i vettori dei
217
+ chunk invariati. Salvataggio atomico; una cache di ripresa evita di
218
+ ricalcolare tutto se il run viene interrotto.
219
+ - `search` legge il codice dal disco solo per i risultati finali e non esce
220
+ mai dal repo.
221
+ - `eval` misura recall@3/@8 e latenza sulle query della fixture.
222
+
223
+ ## Uso dentro pi
224
+
225
+ ```bash
226
+ # pacchetto npm: solo questa invocazione, nessuna configurazione globale
227
+ pi -e npm:@alucard_24/pi-codeindex@1.0.0
228
+
229
+ # in sviluppo, dalla directory del progetto
230
+ pi -e ./src/extension.js
231
+ ```
232
+
233
+ Cosa aggiunge:
234
+
235
+ - **`search_code`** — ricerca semantica (`query`, `k` 1–30, `path_prefix`,
236
+ `context`) che restituisce `path:start-end`, simbolo e anteprima. Non
237
+ sostituisce `grep`.
238
+ - i risultati il cui file è cambiato dopo l'indicizzazione sono marcati
239
+ `STALE` (hash su disco, non fiducia nel watcher);
240
+ - `context: "bundle"` aggiunge import, importatori e fino a 5 chiamanti
241
+ **euristici** dei primi 3 risultati: un round-trip invece di una catena
242
+ `grep`+`read`.
243
+ - **outline su `read`** — se leggi un file > 300 righe con ≥ 5 simboli indicizzati
244
+ e senza `offset`/`limit` mirati, ricevi un blocco `[outline from code index]`
245
+ con i simboli e i loro range (max 40, ~3,5% di caratteri in più).
246
+ - **riga di stato** nel footer (`codeindex: 5.2k chunk`, `codeindex: 1.2k/4.8k`
247
+ durante l'indicizzazione).
248
+ - **`/codeindex status|reindex [--force]|enable|disable|config|configure|doctor`**. `disable` spegne
249
+ tutto: tool, outline, riga di stato, riga nel system prompt, watcher e
250
+ indicizzazione in corso. Lo stato è persistito nel branch della sessione;
251
+ una sessione nuova riparte attiva.
252
+ - **indicizzazione automatica** all'avvio della sessione, in background,
253
+ single-flight, interrompibile, con cap 5.000 file / 200 MB.
254
+ - **sincronizzazione**: watcher ricorsivo con debounce 800 ms, invalidazione
255
+ diretta da `write`/`edit` e sweep `git status` dopo `bash`. Una riconciliazione
256
+ riusa i vettori dei file intatti (misurato: 1 chunk re-embedded su 3 dopo una
257
+ modifica, 920 ms fino all'indice aggiornato).
258
+
259
+ Fuori da un repo git l'estensione si disattiva da sola; se il modello non è
260
+ scaricabile o il reindex fallisce, `search_code` risponde con lo stato reale e
261
+ la sessione continua.
262
+
263
+ ## Cosa indicizza
264
+
265
+ JS/TS/TSX/JSX, Python, Go, Rust, C, C++, C#, PowerBuilder (.srw/.sru/…),
266
+ Markdown. Chunk declaration-aware, con fusione dei chunk piccoli (Markdown fino
267
+ a 80 righe; codice solo micro-chunk ≤6 righe). Esclusi: binari, file >1 MB,
268
+ minified, lockfile, `.pbl`/`.pbt`/`.pbw`.
269
+
270
+ ## Numeri misurati (RTX 5070 Ti, fp16)
271
+
272
+ | Repo | File | Chunk | Primo indice | Re-index |
273
+ |---|---|---|---|---|
274
+ | `pi.dev-ext` | 373 | 5.230 | 44 s | 169 ms |
275
+ | `Strata` | 2.872 | 29.974 | ~2–5 min | 737 ms |
276
+
277
+ Su CPU q8 (`pi.dev-ext`): 18m28s il primo indice, 143 ms il secondo.
278
+ Ricerca a indice caldo: p50 32 ms. Recall@3 sulla fixture: 5/8.
279
+
280
+ Dentro pi: cache in-process dell'indice (stat dei due file come chiave), quindi
281
+ 11,5 ms a caldo su `pi.dev-ext` e **40 ms su `Strata`** (30k chunk), contro ~1,2 s
282
+ di caricamento modello alla prima query di un processo nuovo.
283
+ Outline su un file da 3.713 righe: **+3,5% caratteri** sulla read.
284
+
285
+ ## Misura (giro completo)
286
+
287
+ I file e i comandi di questa sezione si riferiscono al checkout di sviluppo:
288
+ piano, report completi e harness non vengono distribuiti nel pacchetto npm.
289
+ Il riepilogo e i limiti delle misure sono riportati qui senza estenderne lo scope.
290
+
291
+ `RESULTS.md` riporta l'esperimento A (pi nudo) vs C (pi + indice) su 10 task per
292
+ repo × 3 ripetizioni: mediana dei delta **−25,4% token su `Strata`** e **−9,7% su
293
+ `pi.dev-ext`**, ma costo totale **−12,3%** e **−12,9%**: sotto la soglia del 15%
294
+ del gate primario. Il verdetto è nel file: **gate non superato**, progetto fermo
295
+ per la regola scritta in `PLAN.md`. Il braccio B (OpenLore) non è stato eseguito:
296
+ nessun claim comparativo.
297
+
298
+ La Fase 6 ha ripetuto la misura con un budget di **80 tool call** per sessione
299
+ (`RESULTS-FASE6.md`): costo **−23,2%** su `Strata` e **−19,2%** su `pi.dev-ext`,
300
+ ma successo **27/30 vs 28/30** su `Strata`. Tutti i fallimenti sono run tagliati
301
+ dal cap; il criterio pre-registrato `C ≥ A` non è soddisfatto per una run, quindi
302
+ quel gate resta non superato.
303
+
304
+ La Fase 7 (`RESULTS-FASE7.md`, 80 coppie appaiate per repo, ipotesi più debole
305
+ pre-registrata prima dei run) supera il gate che chiede **solo di non spendere
306
+ più**: costo appaiato **−22,1%** su `Strata` (CI unilaterale [−29,3%, −14,1%]) e
307
+ −21,5% su `pi.dev-ext`, successo **74/80 vs 74/80** e 79/80 vs 80/80. La soglia
308
+ del **15%** resta non misurata: quello che è dimostrato è "in sessione con budget
309
+ di 80 tool call il costo non aumenta e il successo non peggiora oltre 10pp".
310
+
311
+ ```bash
312
+ node scripts/experiment.mjs --repo ~/AI-Projects/Strata --repeat 8 \
313
+ --max-tool-calls 80 --out /tmp/exp5-strata
314
+ node scripts/analyze-experiment.mjs --runs /tmp/exp5-strata --runs /tmp/exp5-pidev
315
+ ```
316
+
317
+ ## Limiti noti
318
+
319
+ - Il chunker è euristico (regex per famiglia): nessun parser AST, nessun
320
+ call graph. Gli errori di confine sono possibili e non etichettati.
321
+ - I chiamanti del bundle sono euristici (scansione dell'identificatore, non un
322
+ call graph): su un campione di 61 casi la precisione misurata è 92%. Il livello
323
+ import è più stretto: solo specifiche che risolvono a un unico file, 29/30 edge
324
+ senza ambiguità su `Strata`.
325
+ - I documenti di piano Markdown competono con il codice nella ricerca: il cap
326
+ di 2 risultati per file e il peso maggiore di path/simbolo mitigano, non
327
+ eliminano il problema.
328
+ - Query in italiano su codice con identificatori inglesi funzionano meno bene
329
+ delle query in inglese.
330
+ - Device e `dtype` fanno parte dell'identità dei vettori: cambiarli richiede
331
+ un re-index. Lo storage v2 lega manifest e vettori con SHA-256: anche un
332
+ salvataggio concorrente/incompleto non viene scambiato per un indice valido.
333
+ Gli indici vecchi senza digest richiedono ricostruzione. La directory è
334
+ unica per repo: processi con profili diversi possono sostituirla o renderla
335
+ temporaneamente indisponibile, mai riutilizzare vettori incompatibili.
336
+ - DirectML e CoreML richiedono ancora smoke test su Windows/macOS; WebGPU è
337
+ verificato solo sulla macchina Linux disponibile, non su tutte le GPU.
338
+ - I pesi sono ancora scaricati dalla revisione `main` del modello; il cache non
339
+ viene aggiornato automaticamente. Prima di un rilascio riproducibile occorre
340
+ fissare uno snapshot dei pesi e verificarne l'integrità.
341
+
342
+ ## Licenza
343
+
344
+ Il codice di pi-codeindex è distribuito sotto licenza [MIT](LICENSE).
345
+ Dipendenze, sorgenti upstream scaricati dal setup e pesi del modello mantengono
346
+ le rispettive licenze; non sono inclusi nel tarball del pacchetto.
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "pi-codeindex — configurazione embedding locali",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "$schema": { "type": "string" },
8
+ "device": { "enum": ["auto", "cpu", "cuda", "webgpu", "dml", "coreml"], "default": "auto", "description": "Per Vulkan forzato usa webgpu con webgpuBackend: vulkan. auto prova i provider presenti per piattaforma, poi CPU." },
9
+ "dtype": { "enum": ["auto", "q8", "q4", "fp16"], "default": "auto", "description": "auto: q8 su CPU, fp16 sugli altri provider. Cambiarlo richiede reindex." },
10
+ "webgpuBackend": { "enum": ["auto", "vulkan"], "default": "auto", "description": "Backend Dawn quando il provider effettivo è webgpu. vulkan forza Vulkan e richiede setup-vulkan (inizialmente Linux x64). Non influenza CPU/CUDA/DirectML/CoreML." },
11
+ "fallback": { "enum": ["error", "cpu"], "default": "error", "description": "Fallback consentito per un device forzato; auto include sempre CPU." },
12
+ "offline": { "type": "boolean", "default": false },
13
+ "cacheDir": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "Cache dei pesi. Supporta ~/; i percorsi relativi sono risolti dal cwd." },
14
+ "cpuThreads": { "type": "integer", "minimum": 0, "maximum": 256, "default": 0, "description": "0 usa il default di ONNX Runtime; non limita la GPU." }
15
+ }
16
+ }
@@ -0,0 +1,8 @@
1
+ {"query":"dove viene stimato il numero di token BPE di un messaggio","expects":["bpe-token-estimator"],"why":"estimatore token BPE dedicato"}
2
+ {"query":"come viene applicata una modifica ancorata a un file","expects":["anchored-edit"],"why":"motore delle modifiche ancorate"}
3
+ {"query":"ricerca nella history canonica delle sessioni","expects":["history-service","history-source-reader","context-history-tool"],"why":"lettura e ricerca della history"}
4
+ {"query":"coordinamento della compaction tra più sessioni","expects":["compaction-coordinator"],"why":"coordinatore della compaction"}
5
+ {"query":"lock per evitare rebase concorrenti di una sessione","expects":["rebase-lock"],"why":"lock di rebase"}
6
+ {"query":"quali artefatti vengono conservati e per quanto tempo","expects":["ARTIFACTS.md","artifacts"],"why":"documentazione sugli artefatti"}
7
+ {"query":"marker di omissione per non riesportare dati già visti","expects":["egress"],"why":"gestione egress e marker di omissione"}
8
+ {"query":"indicizzazione incrementale delle sessioni salvate","expects":["session-indexer"],"why":"indice incrementale delle sessioni"}
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@alucard_24/pi-codeindex",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Indice semantico locale del codice per pi (EmbeddingGemma 2, ONNX)",
5
+ "license": "MIT",
6
+ "author": "Alucard24",
7
+ "repository": { "type": "git", "url": "git+https://github.com/Alucard24/pi-codeindex.git" },
8
+ "homepage": "https://www.npmjs.com/package/@alucard_24/pi-codeindex",
9
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
10
+ "main": "./src/extension.js",
11
+ "bin": { "pi-codeindex": "src/cli.js" },
12
+ "files": ["src", "scripts/setup-gpu.sh", "scripts/setup-vulkan.mjs", "scripts/vulkan-sources.json", "eval/queries.jsonl", "config.schema.json", "README.md", "LICENSE"],
13
+ "engines": { "node": ">=22" },
14
+ "scripts": {
15
+ "test": "node --test 'test/*.test.js'",
16
+ "embed-test": "node src/cli.js embed-test",
17
+ "setup-gpu": "bash scripts/setup-gpu.sh",
18
+ "setup-vulkan": "node scripts/setup-vulkan.mjs",
19
+ "doctor": "node src/cli.js doctor"
20
+ },
21
+ "keywords": ["pi-package", "embeddings", "code-search"],
22
+ "type": "module",
23
+ "pi": {
24
+ "extensions": [
25
+ "./src/extension.js"
26
+ ]
27
+ },
28
+ "dependencies": {
29
+ "@huggingface/transformers": "4.3.2",
30
+ "onnxruntime-node": "1.30.0"
31
+ },
32
+ "peerDependencies": {
33
+ "@earendil-works/pi-ai": "*",
34
+ "@earendil-works/pi-coding-agent": "*"
35
+ }
6
36
  }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # Abilita il provider CUDA di onnxruntime-node (Linux x64, GPU NVIDIA).
3
+ #
4
+ # 1. scarica i binari del provider CUDA se mancano (script ufficiale ORT);
5
+ # 2. trova le librerie CUDA 13 richieste dal provider 1.30 (libcublasLt.so.13,
6
+ # libcudart.so.13, libcurand.so.10, ...);
7
+ # 3. scrive un RUNPATH nel .so del provider: così la GPU funziona senza
8
+ # esportare LD_LIBRARY_PATH (necessario perché l'estensione pi gira
9
+ # dentro un processo già avviato).
10
+ #
11
+ # Idempotente. Da rieseguire dopo `npm install` (node_modules viene ricreato).
12
+ set -euo pipefail
13
+
14
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
15
+ if ! node -e 'process.exit(process.platform === "linux" && process.arch === "x64" ? 0 : 1)'; then
16
+ echo "[gpu] setup CUDA disponibile solo su Linux x64; usa doctor per i backend della tua piattaforma." >&2
17
+ exit 1
18
+ fi
19
+ # Funziona anche con dipendenze npm hoisted, non solo con node_modules locale.
20
+ ORT="$(node --input-type=module - "$ROOT/package.json" <<'JS'
21
+ import { createRequire } from 'node:module';
22
+ import path from 'node:path';
23
+ console.log(path.dirname(createRequire(process.argv[2]).resolve('onnxruntime-node/package.json')));
24
+ JS
25
+ )"
26
+ PROVIDER="$(find "$ORT/bin" -path '*/linux/x64/libonnxruntime_providers_cuda.so' | head -1 || true)"
27
+ BIN_DIR="$(dirname "${PROVIDER:-$ORT/bin/napi-v6/linux/x64/x}")"
28
+
29
+ if [[ ! -f "$PROVIDER" ]]; then
30
+ echo "[gpu] provider CUDA assente: lo scarico (≈270 MB)…"
31
+ node "$ORT/script/install.js" --onnxruntime-node-install=cuda12
32
+ PROVIDER="$BIN_DIR/libonnxruntime_providers_cuda.so"
33
+ fi
34
+
35
+ find_cuda_lib() {
36
+ local d
37
+ for d in \
38
+ "${CUDA13_LIB:-}" \
39
+ "$HOME"/.local/cuda-13*/targets/*/lib \
40
+ /usr/local/cuda-13*/targets/*/lib \
41
+ /usr/local/cuda/lib64 \
42
+ "$HOME"/.local/venv-cu13/lib/python*/site-packages/nvidia/cu13/lib \
43
+ /opt/cuda/lib64; do
44
+ [[ -n "$d" && -e "$d/libcublasLt.so.13" ]] && { echo "$d"; return 0; }
45
+ done
46
+ return 1
47
+ }
48
+
49
+ CUDA_LIB="$(find_cuda_lib || true)"
50
+ if [[ -z "$CUDA_LIB" ]]; then
51
+ echo "[gpu] ERRORE: non trovo libcublasLt.so.13 (serve il runtime CUDA 13)." >&2
52
+ echo " Installa CUDA 13 o imposta CUDA13_LIB=/percorso/lib." >&2
53
+ exit 1
54
+ fi
55
+
56
+ command -v patchelf >/dev/null || { echo "[gpu] ERRORE: patchelf non installato." >&2; exit 1; }
57
+
58
+ echo "[gpu] CUDA libs: $CUDA_LIB"
59
+ patchelf --set-rpath "$CUDA_LIB" "$PROVIDER"
60
+ if LD_LIBRARY_PATH= ldd "$PROVIDER" | grep -q "not found"; then
61
+ echo "[gpu] ERRORE: dipendenze non risolte:" >&2
62
+ LD_LIBRARY_PATH= ldd "$PROVIDER" | grep "not found" >&2
63
+ exit 1
64
+ fi
65
+ echo "[gpu] ok: provider CUDA pronto (nessun LD_LIBRARY_PATH richiesto)"
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ // Setup esplicito, nessun postinstall. Compila solo il binding Node, non ORT/Dawn.
3
+ // Sorgenti MIT upstream fissati; copia ORT privata con SONAME distinto, senza
4
+ // toccare node_modules o il contesto WebGPU di altre estensioni.
5
+ import fs from 'node:fs/promises';
6
+ import { existsSync } from 'node:fs';
7
+ import { createHash, randomUUID } from 'node:crypto';
8
+ import { createRequire } from 'node:module';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { spawnSync } from 'node:child_process';
12
+ import { ORT_VERSION, ORT_SOURCE, VULKAN_REVISION, fileHash, ortLibrary, vulkanCacheDir,
13
+ vulkanStatus, verifyVulkanArtifacts } from '../src/vulkan.js';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const ADDON_URL = 'https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz';
17
+ const ADDON_INTEGRITY = '+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==';
18
+
19
+ export function patchBindingSource(source) {
20
+ const anchor = 'if (name == "preferredLayout" ||';
21
+ if (source.split(anchor).length !== 2) throw new Error('patch Vulkan: sorgente upstream inatteso');
22
+ return source.replace(anchor, 'if (name == "dawnBackendType" ||\n name == "preferredLayout" ||');
23
+ }
24
+
25
+ function run(command, args, options = {}) {
26
+ const result = spawnSync(command, args, { stdio: 'inherit', timeout: 180_000, ...options });
27
+ if (result.error) throw result.error;
28
+ if (result.status !== 0) throw new Error(`${command} terminato con ${result.signal ?? result.status}`);
29
+ }
30
+ async function download(url) {
31
+ const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });
32
+ if (!response.ok) throw new Error(`download ${response.status}: ${url}`);
33
+ return Buffer.from(await response.arrayBuffer());
34
+ }
35
+
36
+ export async function setupVulkan(args = []) {
37
+ let headers, force = false;
38
+ for (let i = 0; i < args.length; i++) {
39
+ if (args[i] === '--force') force = true;
40
+ else if (args[i] === '--node-headers' && args[i + 1] && !args[i + 1].startsWith('--')) headers = path.resolve(args[++i]);
41
+ else throw new Error('uso: setup-vulkan [--force] [--node-headers DIR]');
42
+ }
43
+ if (process.platform !== 'linux' || process.arch !== 'x64') throw new Error('setup-vulkan inizialmente disponibile solo su Linux x64; Windows/macOS non verificati');
44
+ if (require('onnxruntime-node/package.json').version !== ORT_VERSION) throw new Error(`setup-vulkan richiede onnxruntime-node ${ORT_VERSION}`);
45
+ if (!force) {
46
+ const status = vulkanStatus();
47
+ if (status.available) {
48
+ try { await verifyVulkanArtifacts(status); console.log(`Vulkan: binding già pronto in ${status.dir}`); return; }
49
+ catch (e) { console.log(e.message); }
50
+ }
51
+ }
52
+ const compiler = process.env.CXX || 'c++';
53
+ run(compiler, ['--version'], { stdio: 'ignore' });
54
+ run('patchelf', ['--version'], { stdio: 'ignore' });
55
+ run('tar', ['--version'], { stdio: 'ignore' });
56
+ headers ??= [path.resolve(path.dirname(process.execPath), '../include/node'), '/usr/include/node']
57
+ .find((dir) => existsSync(path.join(dir, 'node_api.h')));
58
+ if (!headers || !existsSync(path.join(headers, 'node_api.h'))) throw new Error('header Node mancanti: installa gli header di sviluppo o usa --node-headers DIR');
59
+
60
+ const dir = vulkanCacheDir();
61
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
62
+ const work = await fs.mkdtemp(path.join(dir, '.build-'));
63
+ try {
64
+ const sourceManifest = JSON.parse(await fs.readFile(new URL('./vulkan-sources.json', import.meta.url), 'utf8'));
65
+ if (sourceManifest.revision !== ORT_SOURCE) throw new Error('revision sorgenti Vulkan incoerente');
66
+ console.log(`Vulkan: sorgenti ORT ${ORT_VERSION} @ ${ORT_SOURCE.slice(0, 12)} (hash Git verificati)`);
67
+ const sourceDir = path.join(work, 'source');
68
+ const entries = Object.entries(sourceManifest.files);
69
+ // Download limitato: solo binding e header pubblici, niente clone del repository.
70
+ for (let i = 0; i < entries.length; i += 4) await Promise.all(entries.slice(i, i + 4).map(async ([file, expected]) => {
71
+ const bytes = await download(`https://raw.githubusercontent.com/microsoft/onnxruntime/${ORT_SOURCE}/${file}`);
72
+ const actual = createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
73
+ if (actual !== expected) throw new Error(`hash sorgente inatteso: ${file}`);
74
+ const dest = path.join(sourceDir, file);
75
+ await fs.mkdir(path.dirname(dest), { recursive: true });
76
+ await fs.writeFile(dest, bytes);
77
+ }));
78
+ const helper = path.join(sourceDir, 'js/node/src/session_options_helper.cc');
79
+ await fs.writeFile(helper, patchBindingSource(await fs.readFile(helper, 'utf8')));
80
+ const tarball = await download(ADDON_URL);
81
+ if (createHash('sha512').update(tarball).digest('base64') !== ADDON_INTEGRITY) throw new Error('integrità node-addon-api non valida');
82
+ const archive = path.join(work, 'addon-api.tgz');
83
+ await fs.writeFile(archive, tarball);
84
+ const addonDir = path.join(work, 'addon-api');
85
+ await fs.mkdir(addonDir);
86
+ run('tar', ['-xzf', archive, '--strip-components=1', '-C', addonDir]);
87
+
88
+ const out = path.join(work, 'artifact');
89
+ await fs.mkdir(out);
90
+ const sourceLibraryHash = await fileHash(ortLibrary());
91
+ const library = `libpi-codeindex-ort-vulkan-${sourceLibraryHash.slice(0, 16)}.so.1`;
92
+ const libraryFile = path.join(out, library);
93
+ await fs.copyFile(ortLibrary(), libraryFile);
94
+ run('patchelf', ['--set-soname', library, libraryFile]);
95
+ const nodeDir = path.join(sourceDir, 'js/node/src');
96
+ const cpp = (await fs.readdir(nodeDir)).filter((f) => f.endsWith('.cc')).sort().map((f) => path.join(nodeDir, f));
97
+ console.log('Vulkan: compilazione binding isolato (C++/N-API 6), runtime precompilato riutilizzato');
98
+ const bindingFile = path.join(out, 'onnxruntime_binding.node');
99
+ run(compiler, ['-shared', '-fPIC', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-std=c++17', '-O2',
100
+ '-DNAPI_VERSION=6', '-DUSE_WEBGPU', '-DNAPI_CPP_EXCEPTIONS', '-DORT_API_MANUAL_INIT',
101
+ `-I${headers}`, `-I${addonDir}`, `-I${path.join(sourceDir, 'include/onnxruntime')}`,
102
+ `-I${path.join(sourceDir, 'include/onnxruntime/core/session')}`, ...cpp,
103
+ `-L${out}`, `-l:${library}`, '-Wl,-rpath,$ORIGIN', '-o', bindingFile]);
104
+ await fs.copyFile(path.join(sourceDir, 'LICENSE'), path.join(out, 'LICENSE-onnxruntime'));
105
+ await fs.copyFile(path.join(addonDir, 'LICENSE.md'), path.join(out, 'LICENSE-node-addon-api'));
106
+ // Il controllo negativo non richiede GPU o pesi. Nuovo processo, nessun
107
+ // addon tenuto aperto durante la pubblicazione degli artefatti.
108
+ const moduleUrl = new URL('../src/vulkan.js', import.meta.url).href;
109
+ run(process.execPath, ['--input-type=module', '-e', `
110
+ import { createRequire } from 'node:module';
111
+ import { assertVulkanSelector } from ${JSON.stringify(moduleUrl)};
112
+ process.env.ORT_DISABLE_TELEMETRY = '1';
113
+ const req = createRequire(${JSON.stringify(import.meta.url)});
114
+ const ort = req('onnxruntime-node');
115
+ const binding = req(${JSON.stringify(bindingFile)});
116
+ binding.initOrtOnce(2, ort.Tensor, true);
117
+ assertVulkanSelector(binding);
118
+ console.log('Vulkan: controllo negativo del selettore superato');
119
+ `]);
120
+ const bindingHash = await fileHash(bindingFile);
121
+ const libraryHash = await fileHash(libraryFile);
122
+ // Directory nuova ad ogni build: non riusare/sovrascrivere artefatti
123
+ // corrotti o ancora mappati da un altro processo. Il manifest si pubblica ultimo.
124
+ const artifact = createHash('sha256').update(bindingHash + libraryHash + randomUUID()).digest('hex');
125
+ const target = path.join(dir, artifact);
126
+ await fs.rename(out, target);
127
+ const manifest = { runtimeVersion: ORT_VERSION, sourceRevision: ORT_SOURCE, revision: VULKAN_REVISION,
128
+ platform: process.platform, arch: process.arch, artifact, library, sourceLibraryHash, bindingHash, libraryHash };
129
+ const tmp = path.join(dir, `manifest.${randomUUID()}.tmp`);
130
+ try {
131
+ await fs.writeFile(tmp, JSON.stringify(manifest, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
132
+ await fs.rename(tmp, path.join(dir, 'manifest.json'));
133
+ } finally { await fs.rm(tmp, { force: true }); }
134
+ console.log(`Vulkan: installato in ${target}\nNessun file in node_modules modificato. Riavvia pi se già aperto. Ora esegui doctor --probe --device webgpu --webgpu-backend vulkan.`);
135
+ } finally { await fs.rm(work, { recursive: true, force: true }); }
136
+ }
137
+
138
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
139
+ setupVulkan(process.argv.slice(2)).catch((e) => { console.error(`setup-vulkan: ${e.message}`); process.exitCode = 1; });
140
+ }