@alucard_24/pi-codeindex 1.0.0 → 1.1.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/CHANGELOG.md +52 -0
- package/README.md +171 -25
- package/config.schema.json +4 -1
- package/package.json +2 -2
- package/src/cli.js +12 -4
- package/src/config-ui.js +6 -4
- package/src/config.js +10 -5
- package/src/doctor.js +7 -2
- package/src/embed.js +39 -70
- package/src/extension.js +159 -76
- package/src/local-embed.js +51 -0
- package/src/probe.js +10 -7
- package/src/project-state.js +53 -0
- package/src/service-client.js +130 -0
- package/src/service-policy.js +33 -0
- package/src/service-protocol.js +122 -0
- package/src/service-worker.js +3 -0
- package/src/service.js +172 -0
- package/src/sync.js +9 -5
- package/src/workspace.js +26 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
- Porta loopback predefinita `43127`, sempre configurabile; l'override esplicito
|
|
6
|
+
`0` conserva la scelta deterministica precedente. Nessuna garanzia che una
|
|
7
|
+
porta fissa sia libera o non esclusa su ogni macchina.
|
|
8
|
+
- Attivazione dell'integrazione pi persistente **per progetto**, spenta in
|
|
9
|
+
assenza di consenso. `/codeindex enable|disable` salva la scelta fuori dai
|
|
10
|
+
sorgenti; progetti e sessioni non si abilitano globalmente a vicenda.
|
|
11
|
+
- Il servizio embedding resta condiviso. Disabilitare un progetto annulla
|
|
12
|
+
watcher/indicizzazione e libera le cache del client, senza arrestare il
|
|
13
|
+
modello eventualmente usato da altri progetti.
|
|
14
|
+
- Gli errori `listen EACCES` suggeriscono una porta alternativa configurabile,
|
|
15
|
+
senza fallback in-process. Nessuna correzione dichiarata per il timeout
|
|
16
|
+
intermittente segnalato su Windows: la causa resta da diagnosticare.
|
|
17
|
+
- Nessun caricamento automatico dell'indice o reindex a progetto spento.
|
|
18
|
+
I vecchi flag di sessione non vengono migrati in consenso per un progetto.
|
|
19
|
+
- Prima di cambiare porta, arrestare il servizio con la configurazione
|
|
20
|
+
precedente e riavviare i client. Anche il nuovo default richiede questa
|
|
21
|
+
precauzione se è ancora presente un servizio della versione precedente.
|
|
22
|
+
|
|
23
|
+
## 1.1.0
|
|
24
|
+
|
|
25
|
+
### Novità
|
|
26
|
+
|
|
27
|
+
- Modello di embedding in un servizio locale condiviso tra processi pi e CLI:
|
|
28
|
+
una sola pipeline per endpoint, avvio automatico, inferenze serializzate e
|
|
29
|
+
coda limitata. Nessun fallback in-process in caso di errore del servizio.
|
|
30
|
+
- Rilascio del modello dopo inattività (default 5 minuti), configurabile con
|
|
31
|
+
`serviceIdleMs` / `CODEINDEX_SERVICE_IDLE_MS`.
|
|
32
|
+
- `/codeindex service status` e `/codeindex service stop`, disponibili anche
|
|
33
|
+
dalla CLI. Autenticazione locale, verifica del profilo dopo riconnessione e
|
|
34
|
+
rifiuto dei conflitti di configurazione senza creare un secondo modello.
|
|
35
|
+
- Indicizzazione e sincronizzazione anche nelle cartelle senza repository Git.
|
|
36
|
+
Home e radice del disco richiedono consenso esplicito in ogni sessione.
|
|
37
|
+
- Diagnostica, configurazione e documentazione aggiornate; test di concorrenza,
|
|
38
|
+
crash, shutdown e prove opt-in con modello reale.
|
|
39
|
+
|
|
40
|
+
### Aggiornamento
|
|
41
|
+
|
|
42
|
+
Chiudere e riaprire i vecchi processi pi dopo l'aggiornamento: le copie del
|
|
43
|
+
modello caricate dal vecchio runtime in-process non vengono liberate dal
|
|
44
|
+
nuovo servizio. Per cambiare il profilo di un servizio già attivo usare
|
|
45
|
+
`/codeindex service stop` quando inattivo, oppure attendere il timeout idle.
|
|
46
|
+
I probe espliciti di doctor restano isolati e possono caricare un modello
|
|
47
|
+
aggiuntivo. Endpoint diversi permettono deliberatamente istanze separate.
|
|
48
|
+
|
|
49
|
+
Il servizio è stato verificato su Linux x64 con CPU, CUDA, WebGPU automatico
|
|
50
|
+
e WebGPU/Vulkan; DirectML/Windows e CoreML/macOS restano da verificare sulle
|
|
51
|
+
rispettive piattaforme. Le misure storiche Fase 5–7 non sono nuovi benchmark
|
|
52
|
+
del servizio condiviso.
|
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# pi-codeindex
|
|
2
2
|
|
|
3
3
|
Indice semantico locale del codice per l'agente pi: embedding con
|
|
4
|
-
**EmbeddingGemma 2** (ONNX,
|
|
5
|
-
che restituisce `path:righe` + simbolo + anteprima.
|
|
6
|
-
|
|
4
|
+
**EmbeddingGemma 2** (ONNX, servizio locale condiviso) e ricerca ibrida
|
|
5
|
+
(coseno + lessico) che restituisce `path:righe` + simbolo + anteprima.
|
|
6
|
+
Una sola copia del modello fra processi pi/CLI sullo stesso endpoint;
|
|
7
|
+
nessun servizio remoto o database. Indice: due file in
|
|
8
|
+
`~/.cache/pi-codeindex/<sha1(repo)>/`.
|
|
9
|
+
|
|
10
|
+
**Default dalla versione 1.1.1:** porta locale `43127`; integrazione pi
|
|
11
|
+
spenta nei progetti mai abilitati. `/codeindex enable` salva il consenso
|
|
12
|
+
**per progetto**, mentre il modello resta condiviso fra i progetti attivi.
|
|
7
13
|
|
|
8
14
|
## Requisiti
|
|
9
15
|
|
|
@@ -18,7 +24,7 @@ database: due file in `~/.cache/pi-codeindex/<sha1(repo)>/`.
|
|
|
18
24
|
### Da npm, solo su richiesta
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
|
-
pi -e npm:@alucard_24/pi-codeindex@1.
|
|
27
|
+
pi -e npm:@alucard_24/pi-codeindex@1.1.1
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
Carica il pacchetto per questa invocazione, senza aggiungerlo alle impostazioni
|
|
@@ -50,11 +56,12 @@ modificata dal pacchetto.
|
|
|
50
56
|
|
|
51
57
|
## Embedding e backend
|
|
52
58
|
|
|
53
|
-
Il modello **EmbeddingGemma 2** gira
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
Il modello **EmbeddingGemma 2** gira in un servizio locale condiviso avviato
|
|
60
|
+
automaticamente al primo utilizzo, anche dall'indicizzazione automatica.
|
|
61
|
+
I client pi/CLI non caricano una pipeline propria. I soli pesi testo vengono
|
|
62
|
+
scaricati e riutilizzati fra repository; dopo il primo download è possibile
|
|
63
|
+
lavorare offline. Il codice da embeddare viaggia solo sul loopback locale,
|
|
64
|
+
non viene inviato al server dei pesi o a un'API di embedding remota.
|
|
58
65
|
|
|
59
66
|
| `device` | Default `dtype: auto` | Stato |
|
|
60
67
|
|---|---|---|
|
|
@@ -130,6 +137,72 @@ macchina Linux x64 / RTX 5070 Ti: EmbeddingGemma fp16, indice incrementale,
|
|
|
130
137
|
ricerca su fixture e coesistenza con i provider standard. Queste prove non
|
|
131
138
|
sostituiscono un benchmark di qualità o prestazioni su altri hardware.
|
|
132
139
|
|
|
140
|
+
## Servizio condiviso e memoria
|
|
141
|
+
|
|
142
|
+
Il primo client sceglie il profilo secondo la configurazione; gli altri
|
|
143
|
+
riutilizzano la stessa pipeline se quel profilo è consentito. Un profilo
|
|
144
|
+
incompatibile, una cache pesi diversa o valori diversi di `cpuThreads` o
|
|
145
|
+
`serviceIdleMs` danno errore: **nessun secondo modello e nessun fallback
|
|
146
|
+
in-process**. Un device forzato può riutilizzare un servizio CPU solo con
|
|
147
|
+
`fallback: cpu`, con avviso esplicito. Le richieste sono serializzate, con
|
|
148
|
+
massimo 16 richieste attive/in coda; ogni batch contiene al massimo 128 testi
|
|
149
|
+
e 2 MiB di testo. La saturazione della coda produce un errore, non un altro
|
|
150
|
+
worker. Un crash interrompe la richiesta corrente; un uso successivo può
|
|
151
|
+
riavviare il servizio, verificando il profilo già fissato dal client.
|
|
152
|
+
|
|
153
|
+
Dopo **5 minuti senza operazioni sul modello** il processo termina e l'OS
|
|
154
|
+
libera RAM/VRAM native. `status` non rinnova il timer. Chiudere/disabilitare
|
|
155
|
+
una singola sessione non arresta un modello che può servire altri processi.
|
|
156
|
+
Per liberarlo subito, quando non ci sono inferenze in corso:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
/codeindex service status
|
|
160
|
+
/codeindex service status --json
|
|
161
|
+
/codeindex service stop
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Gli stessi comandi funzionano come `node src/cli.js service status|stop`.
|
|
165
|
+
Stop viene rifiutato se ci sono richieste attive/in coda; non interrompe altri
|
|
166
|
+
client. Il servizio si riavvierà al prossimo utilizzo del modello. Nessun
|
|
167
|
+
avvio al login, servizio di sistema o modifica alle impostazioni globali di pi.
|
|
168
|
+
|
|
169
|
+
Il listener è solo `127.0.0.1`, sulla porta predefinita **`43127`**, con
|
|
170
|
+
autenticazione tramite token in `~/.cache/pi-codeindex/service/`
|
|
171
|
+
(directory 0700, record 0600 su POSIX). Il binding esclusivo della porta
|
|
172
|
+
impedisce due avvii simultanei sullo stesso endpoint. Nessuna porta è garantita
|
|
173
|
+
su tutte le macchine: se occupata o esclusa dal sistema, scegli una porta
|
|
174
|
+
libera con `/codeindex config set servicePort NUMERO` e riavvia pi.
|
|
175
|
+
Su Windows `netsh interface ipv4 show excludedportrange protocol=tcp` mostra
|
|
176
|
+
gli intervalli esclusi; non cancellare le prenotazioni del sistema.
|
|
177
|
+
|
|
178
|
+
Usa gli stessi `serviceDir`/`servicePort` in tutti i client per condividere il
|
|
179
|
+
modello. **Arresta il vecchio servizio inattivo prima di cambiare porta**
|
|
180
|
+
(`/codeindex service stop` con la configurazione precedente), poi chiudi e
|
|
181
|
+
riapri i client. Questo vale anche per il passaggio dalla porta automatica
|
|
182
|
+
della 1.1.0 al nuovo default: un vecchio record può riferirsi alla porta
|
|
183
|
+
precedente. Gli override già salvati non vengono sovrascritti; `servicePort: 0`
|
|
184
|
+
mantiene esplicitamente la scelta deterministica precedente.
|
|
185
|
+
Directory diverse e porte diverse permettono deliberatamente più modelli;
|
|
186
|
+
una stessa directory non va usata contemporaneamente con porte diverse.
|
|
187
|
+
Non condividere il token o la directory.
|
|
188
|
+
|
|
189
|
+
Restano per-processo le cache degli indici, dei vettori e dei grafi: il servizio
|
|
190
|
+
elimina le copie del **modello**, non tutta la memoria di pi. Dopo l'aggiornamento
|
|
191
|
+
**chiudi e riapri i vecchi processi pi**: i modelli già caricati dal vecchio
|
|
192
|
+
codice in-process non possono essere liberati dal nuovo servizio.
|
|
193
|
+
|
|
194
|
+
Verifica reale opt-in, con pesi già in cache (offline), due processi client,
|
|
195
|
+
indice/ricerca e uscita idle:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
CODEINDEX_TEST_SERVICE=1 CODEINDEX_DEVICE=cuda node --test test/service-integration.test.js
|
|
199
|
+
# Oppure cpu/q8, oppure WebGPU con CODEINDEX_WEBGPU_BACKEND=vulkan dopo setup-vulkan.
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Questi test non sono un nuovo benchmark Fase 5–7. La piattaforma iniziale di
|
|
203
|
+
verifica del servizio è Linux x64; Windows/DirectML e macOS/CoreML richiedono
|
|
204
|
+
verifica sulle rispettive macchine.
|
|
205
|
+
|
|
133
206
|
## Configurazione
|
|
134
207
|
|
|
135
208
|
Dentro pi:
|
|
@@ -142,6 +215,8 @@ Dentro pi:
|
|
|
142
215
|
/codeindex config set webgpuBackend vulkan solo WebGPU, setup-vulkan richiesto
|
|
143
216
|
/codeindex config set fallback cpu
|
|
144
217
|
/codeindex config unset device ripristina la scelta automatica
|
|
218
|
+
/codeindex service status processo condiviso, profilo e memoria
|
|
219
|
+
/codeindex service stop rilascia il modello quando inattivo
|
|
145
220
|
/codeindex doctor inventario, senza inferenza/download
|
|
146
221
|
/codeindex doctor --probe --device webgpu prova reale isolata (può scaricare i pesi)
|
|
147
222
|
```
|
|
@@ -175,9 +250,15 @@ non default silenziosi. Schema per editor: [config.schema.json](config.schema.js
|
|
|
175
250
|
| `offline` | booleano; niente download, errore se mancano pesi | `CODEINDEX_OFFLINE=true\|false\|1\|0` |
|
|
176
251
|
| `cacheDir` | cache pesi, default `~/.cache/pi-codeindex/text-model` | `CODEINDEX_MODEL_CACHE` |
|
|
177
252
|
| `cpuThreads` | 0 = default ORT, oppure 1–256; non limita la GPU | `CODEINDEX_CPU_THREADS` |
|
|
253
|
+
| `serviceIdleMs` | default 300000 (5 min), da 1000 a 86400000 ms | `CODEINDEX_SERVICE_IDLE_MS` |
|
|
254
|
+
| `serviceDir` | directory privata condivisa, default `~/.cache/pi-codeindex/service` | `CODEINDEX_SERVICE_DIR` |
|
|
255
|
+
| `servicePort` | default **43127**; 1024–65535, oppure 0 per la scelta deterministica precedente; solo loopback | `CODEINDEX_SERVICE_PORT` |
|
|
178
256
|
|
|
179
257
|
**Riavvia pi con un nuovo processo** dopo una modifica: non si cambia pipeline
|
|
180
|
-
nel mezzo di un run.
|
|
258
|
+
nel mezzo di un run. Se il servizio ha già un profilo/configurazione diversi,
|
|
259
|
+
usa anche `service stop` quando inattivo, oppure attendi l'uscita idle.
|
|
260
|
+
Prima di aggiornare a un protocollo incompatibile, arresta il vecchio servizio.
|
|
261
|
+
Il menu non riavvia né reindicizza da solo. Device, dtype
|
|
181
262
|
e backend WebGPU effettivi sono salvati nell'indice; se diversi, la ricerca rifiuta di mescolare
|
|
182
263
|
i vettori e chiede un re-index. Gli indici precedenti senza `device` vengono
|
|
183
264
|
ricostruiti alla prossima indicizzazione.
|
|
@@ -198,9 +279,11 @@ CODEINDEX_DEVICE=cuda CODEINDEX_DTYPE=fp16 pi -e ./src/extension.js
|
|
|
198
279
|
in subprocessi isolati con timeout di 120 s per backend; `--all` può scaricare
|
|
199
280
|
più varianti. Ogni provider forzato viene provato senza fallback, per non
|
|
200
281
|
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
|
|
202
|
-
|
|
203
|
-
|
|
282
|
+
di una prova non abbatte pi. I probe restano esplicitamente isolati e **possono
|
|
283
|
+
caricare una copia aggiuntiva del modello** mentre il servizio è attivo: arresta
|
|
284
|
+
prima il servizio se vuoi evitare questo picco. Anche la pipeline ordinaria è
|
|
285
|
+
ora isolata, nel worker condiviso. Smoke del menu e dei comandi via RPC, senza
|
|
286
|
+
chiamate LLM e con cache modello temporanea/offline: `CODEINDEX_TEST_RPC=1 node --test test/config-rpc.test.js` (richiede
|
|
204
287
|
`pi` sul PATH; usa un file config temporaneo, non modifica quello utente).
|
|
205
288
|
|
|
206
289
|
## Comandi
|
|
@@ -220,11 +303,43 @@ node src/cli.js embed-test
|
|
|
220
303
|
mai dal repo.
|
|
221
304
|
- `eval` misura recall@3/@8 e latenza sulle query della fixture.
|
|
222
305
|
|
|
306
|
+
## Attivazione per progetto
|
|
307
|
+
|
|
308
|
+
Il caricamento dell'estensione non abilita automaticamente un progetto.
|
|
309
|
+
Prima del consenso non vengono avviati modello, download, indicizzazione,
|
|
310
|
+
watcher o caricamento automatico dell'indice. Configurazione, stato e
|
|
311
|
+
`doctor` restano disponibili; `doctor --probe` richiede esplicitamente il modello.
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
/codeindex enable # abilita solo il progetto corrente, anche per le sessioni future
|
|
315
|
+
/codeindex disable # disabilita solo quel progetto, senza fermare il modello condiviso
|
|
316
|
+
/codeindex status # mostra radice, stato e percorso del file di attivazione
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
La scelta è salvata fuori dai sorgenti, in
|
|
320
|
+
`projects/<sha256(radice canonica)>.json` accanto al file restituito da
|
|
321
|
+
`/codeindex config path` (normalmente `~/.config/pi-codeindex/projects/`,
|
|
322
|
+
oppure `%APPDATA%\\pi-codeindex\\projects\\` su Windows).
|
|
323
|
+
Non viene aggiunto un flag globale di attivazione alla configurazione di pi
|
|
324
|
+
o al profilo embedding. Symlink e differenze di maiuscole su Windows non
|
|
325
|
+
creano consensi separati per lo stesso percorso canonico.
|
|
326
|
+
|
|
327
|
+
Nei repository Git la scelta vale per l'intera radice Git, anche dalle
|
|
328
|
+
sottocartelle. Senza Git vale per la cartella di avvio. Gli altri client dello
|
|
329
|
+
stesso progetto rileggono la scelta al prompt, alle richieste di stato/reindex
|
|
330
|
+
e prima di indicizzare; i progetti diversi mantengono il proprio stato.
|
|
331
|
+
|
|
332
|
+
I vecchi flag nel branch della sessione non abilitano implicitamente un
|
|
333
|
+
progetto: dopo l'aggiornamento usa `/codeindex enable` una volta per progetto.
|
|
334
|
+
Stato corrotto o illeggibile significa spento con avviso, non consenso implicito.
|
|
335
|
+
`/codeindex reindex` a progetto spento invita prima ad abilitarlo; i comandi CLI
|
|
336
|
+
espliciti di indice/ricerca non cambiano il consenso dell'integrazione pi.
|
|
337
|
+
|
|
223
338
|
## Uso dentro pi
|
|
224
339
|
|
|
225
340
|
```bash
|
|
226
341
|
# pacchetto npm: solo questa invocazione, nessuna configurazione globale
|
|
227
|
-
pi -e npm:@alucard_24/pi-codeindex@1.
|
|
342
|
+
pi -e npm:@alucard_24/pi-codeindex@1.1.1
|
|
228
343
|
|
|
229
344
|
# in sviluppo, dalla directory del progetto
|
|
230
345
|
pi -e ./src/extension.js
|
|
@@ -247,18 +362,46 @@ Cosa aggiunge:
|
|
|
247
362
|
durante l'indicizzazione).
|
|
248
363
|
- **`/codeindex status|reindex [--force]|enable|disable|config|configure|doctor`**. `disable` spegne
|
|
249
364
|
tutto: tool, outline, riga di stato, riga nel system prompt, watcher e
|
|
250
|
-
indicizzazione in corso. Lo stato è persistito
|
|
251
|
-
|
|
252
|
-
|
|
365
|
+
indicizzazione in corso. Lo stato è persistito **per progetto**, non nel
|
|
366
|
+
branch della sessione e non globalmente. Disabilitare libera anche le cache
|
|
367
|
+
dell'indice e del grafo di quel client, senza fermare il servizio condiviso.
|
|
368
|
+
- **indicizzazione automatica solo nei progetti già abilitati**, in background,
|
|
253
369
|
single-flight, interrompibile, con cap 5.000 file / 200 MB.
|
|
254
370
|
- **sincronizzazione**: watcher ricorsivo con debounce 800 ms, invalidazione
|
|
255
|
-
diretta da `write`/`edit` e sweep `git status`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
scaricabile o il reindex fallisce, `search_code` risponde
|
|
261
|
-
la sessione continua.
|
|
371
|
+
diretta da `write`/`edit` e controllo dopo `bash`: sweep `git status` nei
|
|
372
|
+
repository, riconciliazione incrementale nelle cartelle senza Git. Una
|
|
373
|
+
riconciliazione riusa i vettori dei file intatti (misurato nei repository:
|
|
374
|
+
1 chunk re-embedded su 3 dopo una modifica, 920 ms fino all'indice aggiornato).
|
|
375
|
+
|
|
376
|
+
Se il modello non è scaricabile o il reindex fallisce, `search_code` risponde
|
|
377
|
+
con lo stato reale e la sessione continua.
|
|
378
|
+
|
|
379
|
+
### Cartelle senza Git
|
|
380
|
+
|
|
381
|
+
Git è opzionale. Nei repository la radice resta quella restituita da Git,
|
|
382
|
+
anche avviando pi da una sottocartella. Altrimenti viene usata **esattamente
|
|
383
|
+
la directory da cui avvii pi**: non viene creato `.git`, non servono commit
|
|
384
|
+
né un remote. `/codeindex status` mostra radice e modalità.
|
|
385
|
+
|
|
386
|
+
In modalità cartella la scansione esclude le directory nascoste e le consuete
|
|
387
|
+
directory di dipendenze/output (`node_modules`, `dist`, `build`, `out`,
|
|
388
|
+
`vendor`, `target`, `coverage`, `venv`, `__pycache__`); non segue i symlink.
|
|
389
|
+
Restano i limiti di estensione, dimensione, 5.000 file e 200 MB. Le esclusioni
|
|
390
|
+
sono quelle fisse della scansione locale: **senza Git i file `.gitignore`
|
|
391
|
+
non vengono interpretati**. Nei repository continua a valere `git ls-files`
|
|
392
|
+
con le esclusioni Git.
|
|
393
|
+
|
|
394
|
+
Il watcher gestisce anche rename/cancellazioni di directory; dopo `bash`
|
|
395
|
+
viene accodata una riconciliazione senza ricalcolare i vettori invariati.
|
|
396
|
+
Se il watcher non è disponibile, la riconciliazione viene accodata anche
|
|
397
|
+
all'inizio di un nuovo prompt. `/codeindex reindex` resta disponibile per
|
|
398
|
+
aggiornare manualmente l'indice; `--force` ricalcola tutti i vettori.
|
|
399
|
+
|
|
400
|
+
Per evitare scansioni involontarie dell'intera macchina, nella **home** e
|
|
401
|
+
nella **radice del disco**, se non sono repository Git, l'indice parte spento.
|
|
402
|
+
Avvia pi dalla cartella del progetto oppure abilitalo esplicitamente con
|
|
403
|
+
`/codeindex enable`; nelle due cartelle protette il consenso va ripetuto a
|
|
404
|
+
ogni avvio della sessione. Nessuna nuova configurazione globale è necessaria.
|
|
262
405
|
|
|
263
406
|
## Cosa indicizza
|
|
264
407
|
|
|
@@ -267,7 +410,10 @@ Markdown. Chunk declaration-aware, con fusione dei chunk piccoli (Markdown fino
|
|
|
267
410
|
a 80 righe; codice solo micro-chunk ≤6 righe). Esclusi: binari, file >1 MB,
|
|
268
411
|
minified, lockfile, `.pbl`/`.pbt`/`.pbw`.
|
|
269
412
|
|
|
270
|
-
## Numeri misurati (RTX 5070 Ti, fp16)
|
|
413
|
+
## Numeri misurati (runtime originale in-process, RTX 5070 Ti, fp16)
|
|
414
|
+
|
|
415
|
+
Queste misure precedono il servizio condiviso e non ne misurano memoria,
|
|
416
|
+
latenza IPC o prestazioni con client concorrenti.
|
|
271
417
|
|
|
272
418
|
| Repo | File | Chunk | Primo indice | Re-index |
|
|
273
419
|
|---|---|---|---|---|
|
package/config.schema.json
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"fallback": { "enum": ["error", "cpu"], "default": "error", "description": "Fallback consentito per un device forzato; auto include sempre CPU." },
|
|
12
12
|
"offline": { "type": "boolean", "default": false },
|
|
13
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." }
|
|
14
|
+
"cpuThreads": { "type": "integer", "minimum": 0, "maximum": 256, "default": 0, "description": "0 usa il default di ONNX Runtime; non limita la GPU." },
|
|
15
|
+
"serviceDir": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "Directory privata del servizio condiviso. Default ~/.cache/pi-codeindex/service; non cambiarla fra sessioni se vuoi condividere il modello." },
|
|
16
|
+
"servicePort": { "type": "integer", "anyOf": [{ "const": 0 }, { "minimum": 1024, "maximum": 65535 }], "default": 43127, "description": "Porta TCP loopback predefinita 43127, da cambiare se occupata o esclusa dal sistema. 0 mantiene la scelta deterministica precedente. Arrestare il vecchio servizio prima di cambiare porta nella stessa directory." },
|
|
17
|
+
"serviceIdleMs": { "type": "integer", "minimum": 1000, "maximum": 86400000, "default": 300000, "description": "Inattività prima di terminare il processo e liberare il modello (default 5 minuti). Status non rinnova il timer." }
|
|
15
18
|
}
|
|
16
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alucard_24/pi-codeindex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Indice semantico locale del codice per pi (EmbeddingGemma 2, ONNX)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alucard24",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
|
10
10
|
"main": "./src/extension.js",
|
|
11
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"],
|
|
12
|
+
"files": ["src", "scripts/setup-gpu.sh", "scripts/setup-vulkan.mjs", "scripts/vulkan-sources.json", "eval/queries.jsonl", "config.schema.json", "README.md", "CHANGELOG.md", "LICENSE"],
|
|
13
13
|
"engines": { "node": ">=22" },
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "node --test 'test/*.test.js'",
|
package/src/cli.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { embed, embedQuery, embedDocs, cosine, MODEL_ID, getEmbeddingProfile, modelDir } from './embed.js';
|
|
8
|
+
import { embed, embedQuery, embedDocs, cosine, MODEL_ID, getEmbeddingProfile, modelDir, getServiceStatus, serviceCommand, embeddingStatus } from './embed.js';
|
|
9
9
|
import { configCommand } from './config.js';
|
|
10
10
|
import { formatProfile } from './backend.js';
|
|
11
11
|
import { doctor, formatDoctor, parseDoctorArgs } from './doctor.js';
|
|
@@ -44,7 +44,9 @@ async function embedTest() {
|
|
|
44
44
|
console.log('cos(same) :', cosine(sameA, sameB).toFixed(6), '(atteso ~1.0)');
|
|
45
45
|
console.log('cos(diverse) :', cosine(sameA, other).toFixed(6), '(atteso < 0.95)');
|
|
46
46
|
console.log('latency cold :', tCold, 'ms per 3 testi');
|
|
47
|
-
console.log('rss
|
|
47
|
+
console.log('rss client :', rssMb(), 'MiB');
|
|
48
|
+
const service = await getServiceStatus();
|
|
49
|
+
console.log('servizio :', service.pid, '· RSS', Math.round(service.rssBytes / 1024 / 1024), 'MiB');
|
|
48
50
|
|
|
49
51
|
const batch = Array.from({ length: 16 }, (_, i) => `${SAME} // variante ${i}`);
|
|
50
52
|
const t1 = performance.now();
|
|
@@ -59,7 +61,7 @@ async function embedTest() {
|
|
|
59
61
|
]);
|
|
60
62
|
console.log('query->args :', cosine(q[0], docs[0]).toFixed(4));
|
|
61
63
|
console.log('query->idle :', cosine(q[0], docs[1]).toFixed(4));
|
|
62
|
-
console.log('rss finale
|
|
64
|
+
console.log('rss client finale:', rssMb(), 'MiB');
|
|
63
65
|
|
|
64
66
|
const dump = arg('dump');
|
|
65
67
|
if (dump && dump !== true) {
|
|
@@ -78,6 +80,7 @@ async function cmdIndex() {
|
|
|
78
80
|
const r = await indexRepo({ root, maxFiles, maxBytes, batchSize, force });
|
|
79
81
|
const out = {
|
|
80
82
|
repo: r.repo,
|
|
83
|
+
profile: { model: r.index.model, dims: r.index.dims, device: r.index.device, dtype: r.index.dtype, webgpuBackend: r.index.webgpuBackend },
|
|
81
84
|
files: r.index.stats.files,
|
|
82
85
|
chunks: r.total,
|
|
83
86
|
embedded: r.embedded,
|
|
@@ -91,6 +94,7 @@ async function cmdIndex() {
|
|
|
91
94
|
console.log(JSON.stringify(out));
|
|
92
95
|
} else {
|
|
93
96
|
console.log(`repo : ${r.repo}`);
|
|
97
|
+
console.log(`runtime : ${formatProfile(r.index)} · servizio condiviso`);
|
|
94
98
|
console.log(`file : ${out.files} (${fmtBytes(out.bytes)}) · invariati ${out.unchangedFiles}`);
|
|
95
99
|
console.log(`chunk : ${out.chunks} · riusati ${out.reused} · embeddati ${out.embedded}`);
|
|
96
100
|
console.log(`esclusi : estensione ${out.skip.ext}, dimensione ${out.skip.big}, binari ${out.skip.binary}, minified ${out.skip.minified}, cap ${out.skip.cap}`);
|
|
@@ -164,6 +168,7 @@ async function cmdEval() {
|
|
|
164
168
|
const cmd = process.argv[2];
|
|
165
169
|
try {
|
|
166
170
|
if (cmd === 'config') console.log(await configCommand(process.argv.slice(3)));
|
|
171
|
+
else if (cmd === 'service') console.log(await serviceCommand(process.argv.slice(3)));
|
|
167
172
|
else if (cmd === 'setup-vulkan') {
|
|
168
173
|
const { setupVulkan } = await import('../scripts/setup-vulkan.mjs');
|
|
169
174
|
await setupVulkan(process.argv.slice(3));
|
|
@@ -186,9 +191,12 @@ try {
|
|
|
186
191
|
else if (cmd === 'search') await cmdSearch();
|
|
187
192
|
else if (cmd === 'eval') await cmdEval();
|
|
188
193
|
else {
|
|
189
|
-
console.error('comandi: embed-test | index | status | search | eval | setup-gpu | setup-vulkan | config [show|path|set CHIAVE VALORE|unset CHIAVE] | doctor [--probe] [--all] [--device DEVICE] [--dtype DTYPE] [--webgpu-backend auto|vulkan] [--json]');
|
|
194
|
+
console.error('comandi: embed-test | index | status | search | eval | setup-gpu | setup-vulkan | service [status [--json]|stop] | config [show|path|set CHIAVE VALORE|unset CHIAVE] | doctor [--probe] [--all] [--device DEVICE] [--dtype DTYPE] [--webgpu-backend auto|vulkan] [--json]');
|
|
190
195
|
process.exit(1);
|
|
191
196
|
}
|
|
197
|
+
const runtime = embeddingStatus();
|
|
198
|
+
const warnings = [...runtime.warnings, ...runtime.attempts.filter((a) => !a.ok).map((a) => `${a.device}: ${a.error}`)];
|
|
199
|
+
if (runtime.profile && warnings.length) console.error(`codeindex: selezionato ${formatProfile(runtime.profile)}; ${warnings.join('; ')}`);
|
|
192
200
|
} catch (e) {
|
|
193
201
|
console.error(`errore: ${e.message}`);
|
|
194
202
|
if (process.env.CODEINDEX_DEBUG) console.error(e.stack);
|
package/src/config-ui.js
CHANGED
|
@@ -14,10 +14,10 @@ export async function configure(ctx, options = {}) {
|
|
|
14
14
|
if (!selected || selected === 'Annulla') return null;
|
|
15
15
|
if (selected === 'Salva') {
|
|
16
16
|
if (!Object.keys(patch).length) return null;
|
|
17
|
-
if (!(await ctx.ui.confirm('Salvare la configurazione?', `${loaded.file}\n${JSON.stringify(patch, null, 2)}\nRiavvia pi dopo il salvataggio. Cambiare device/dtype/backend WebGPU richiede un re-index.`))) return null;
|
|
17
|
+
if (!(await ctx.ui.confirm('Salvare la configurazione?', `${loaded.file}\n${JSON.stringify(patch, null, 2)}\nRiavvia pi dopo il salvataggio. Per cambiare un servizio già attivo usa /codeindex service stop quando inattivo. Cambiare device/dtype/backend WebGPU richiede un re-index.`))) return null;
|
|
18
18
|
const file = await updateConfig(patch, options);
|
|
19
19
|
const overrides = Object.keys(patch).filter((key) => loaded.sources[key] === ENV_KEYS[key]);
|
|
20
|
-
return `salvato: ${file}\nRiavvia pi (nuovo processo) per applicare.` +
|
|
20
|
+
return `salvato: ${file}\nRiavvia pi (nuovo processo) per applicare; per cambiare un servizio già attivo usa /codeindex service stop quando inattivo.` +
|
|
21
21
|
(overrides.length ? `\nAttenzione: override d'ambiente attivi per ${overrides.join(', ')}.` : '') +
|
|
22
22
|
`\n${formatConfig(await loadConfig(options))}`;
|
|
23
23
|
}
|
|
@@ -25,9 +25,11 @@ export async function configure(ctx, options = {}) {
|
|
|
25
25
|
if (!key) continue;
|
|
26
26
|
const choices = key === 'device' ? DEVICES : key === 'dtype' ? DTYPES
|
|
27
27
|
: key === 'webgpuBackend' ? WEBGPU_BACKENDS : key === 'fallback' ? ['error', 'cpu'] : key === 'offline' ? ['false', 'true'] : null;
|
|
28
|
-
const hint = key === 'webgpuBackend' ? ' — solo WebGPU; vulkan richiede setup-vulkan'
|
|
28
|
+
const hint = key === 'webgpuBackend' ? ' — solo WebGPU; vulkan richiede setup-vulkan'
|
|
29
|
+
: key === 'serviceIdleMs' ? ' — millisecondi; 300000 = 5 minuti'
|
|
30
|
+
: key === 'serviceDir' || key === 'servicePort' ? ' — lascia uguale fra sessioni per condividere un solo modello' : '';
|
|
29
31
|
const text = choices ? await ctx.ui.select(`${key}${hint} (attuale: ${draft[key]})`, choices)
|
|
30
|
-
: await ctx.ui.input(`${key} (attuale: ${draft[key]})`, String(draft[key]));
|
|
32
|
+
: await ctx.ui.input(`${key}${hint} (attuale: ${draft[key]})`, String(draft[key]));
|
|
31
33
|
if (text === undefined) continue;
|
|
32
34
|
try { draft[key] = patch[key] = parseSetting(key, text); }
|
|
33
35
|
catch (e) { ctx.ui.notify(e.message, 'error'); }
|
package/src/config.js
CHANGED
|
@@ -12,6 +12,7 @@ export const ENV_KEYS = {
|
|
|
12
12
|
device: 'CODEINDEX_DEVICE', dtype: 'CODEINDEX_DTYPE', fallback: 'CODEINDEX_FALLBACK',
|
|
13
13
|
offline: 'CODEINDEX_OFFLINE', cacheDir: 'CODEINDEX_MODEL_CACHE', cpuThreads: 'CODEINDEX_CPU_THREADS',
|
|
14
14
|
webgpuBackend: 'CODEINDEX_WEBGPU_BACKEND',
|
|
15
|
+
serviceDir: 'CODEINDEX_SERVICE_DIR', servicePort: 'CODEINDEX_SERVICE_PORT', serviceIdleMs: 'CODEINDEX_SERVICE_IDLE_MS',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export function configPath(env = process.env, home = os.homedir()) {
|
|
@@ -26,7 +27,8 @@ function expandPath(value, home = os.homedir()) {
|
|
|
26
27
|
|
|
27
28
|
export function defaults(home = os.homedir()) {
|
|
28
29
|
return { device: 'auto', dtype: 'auto', webgpuBackend: 'auto', fallback: 'error', offline: false,
|
|
29
|
-
cacheDir: path.join(home, '.cache', 'pi-codeindex', 'text-model'), cpuThreads: 0
|
|
30
|
+
cacheDir: path.join(home, '.cache', 'pi-codeindex', 'text-model'), cpuThreads: 0,
|
|
31
|
+
serviceDir: path.join(home, '.cache', 'pi-codeindex', 'service'), servicePort: 43127, serviceIdleMs: 300_000 };
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export function validateConfig(value, source = 'config') {
|
|
@@ -44,8 +46,10 @@ export function validateConfig(value, source = 'config') {
|
|
|
44
46
|
: key === 'webgpuBackend' ? WEBGPU_BACKENDS : key === 'fallback' ? ['error', 'cpu'] : null;
|
|
45
47
|
if (choices && !choices.includes(v)) throw new Error(`${source}: ${key} deve essere uno di: ${choices.join(', ')}`);
|
|
46
48
|
if (key === 'offline' && typeof v !== 'boolean') throw new Error(`${source}: offline deve essere booleano`);
|
|
47
|
-
if (
|
|
49
|
+
if (['cacheDir', 'serviceDir'].includes(key) && (typeof v !== 'string' || !v.trim())) throw new Error(`${source}: ${key} deve essere un percorso non vuoto`);
|
|
48
50
|
if (key === 'cpuThreads' && (!Number.isInteger(v) || v < 0 || v > 256)) throw new Error(`${source}: cpuThreads deve essere un intero tra 0 e 256`);
|
|
51
|
+
if (key === 'servicePort' && (!Number.isInteger(v) || (v !== 0 && (v < 1024 || v > 65535)))) throw new Error(`${source}: servicePort deve essere 0 (automatico) oppure un intero tra 1024 e 65535`);
|
|
52
|
+
if (key === 'serviceIdleMs' && (!Number.isInteger(v) || v < 1000 || v > 86_400_000)) throw new Error(`${source}: serviceIdleMs deve essere un intero tra 1000 e 86400000`);
|
|
49
53
|
}
|
|
50
54
|
return value;
|
|
51
55
|
}
|
|
@@ -56,8 +60,8 @@ export function parseSetting(key, text) {
|
|
|
56
60
|
if (key === 'offline') {
|
|
57
61
|
if (!['true', 'false', '1', '0'].includes(text)) throw new Error('offline: usa true oppure false');
|
|
58
62
|
value = text === 'true' || text === '1';
|
|
59
|
-
} else if (
|
|
60
|
-
if (!/^\d+$/.test(text)) throw new Error(
|
|
63
|
+
} else if (['cpuThreads', 'servicePort', 'serviceIdleMs'].includes(key)) {
|
|
64
|
+
if (!/^\d+$/.test(text)) throw new Error(`${key}: atteso un intero`);
|
|
61
65
|
value = Number(text);
|
|
62
66
|
}
|
|
63
67
|
validateConfig({ [key]: value });
|
|
@@ -86,6 +90,7 @@ export async function loadConfig({ env = process.env, home = os.homedir() } = {}
|
|
|
86
90
|
}
|
|
87
91
|
validateConfig(config, file);
|
|
88
92
|
config.cacheDir = expandPath(config.cacheDir, home);
|
|
93
|
+
config.serviceDir = expandPath(config.serviceDir, home);
|
|
89
94
|
return { config: Object.freeze(config), sources, file };
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -122,5 +127,5 @@ export async function configCommand(args, options = {}) {
|
|
|
122
127
|
if (action === 'set' && key && rest.length) file = await updateConfig({ [key]: parseSetting(key, rest.join(' ')) }, options);
|
|
123
128
|
else if (action === 'unset' && key && !rest.length) file = await updateConfig({}, { ...options, unset: [key] });
|
|
124
129
|
else throw new Error('uso: config [show|path|set CHIAVE VALORE|unset CHIAVE]');
|
|
125
|
-
return `salvato: ${file}\nRiavvia pi (nuovo processo) per applicare. Gli override CODEINDEX_* hanno precedenza.\n${formatConfig(await loadConfig(options))}`;
|
|
130
|
+
return `salvato: ${file}\nRiavvia pi (nuovo processo) per applicare. Per cambiare un profilo già attivo usa anche /codeindex service stop quando il servizio è inattivo. Gli override CODEINDEX_* hanno precedenza.\n${formatConfig(await loadConfig(options))}`;
|
|
126
131
|
}
|
package/src/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { loadConfig, parseSetting } from './config.js';
|
|
6
6
|
import { nativeBackends, formatProfile } from './backend.js';
|
|
7
7
|
import { vulkanStatus } from './vulkan.js';
|
|
8
|
+
import { createEmbeddingClient } from './service-client.js';
|
|
8
9
|
|
|
9
10
|
export function parseDoctorArgs(args) {
|
|
10
11
|
const options = { probe: false, all: false, json: false };
|
|
@@ -63,7 +64,9 @@ export async function doctor({ probe = false, all = false, device, dtype, webgpu
|
|
|
63
64
|
}
|
|
64
65
|
const settings = await loadConfig({ env });
|
|
65
66
|
const native = nativeBackends();
|
|
66
|
-
const
|
|
67
|
+
const service = await createEmbeddingClient({ settings: () => settings, env }).status()
|
|
68
|
+
.catch((e) => ({ running: false, error: e.message }));
|
|
69
|
+
const report = { ok: !service.error, service, platform: process.platform, arch: process.arch, node: process.version,
|
|
67
70
|
runtime: native.version, settings,
|
|
68
71
|
requested: { device: device ?? settings.config.device, dtype: dtype ?? settings.config.dtype,
|
|
69
72
|
webgpuBackend: webgpuBackend ?? settings.config.webgpuBackend },
|
|
@@ -73,7 +76,7 @@ export async function doctor({ probe = false, all = false, device, dtype, webgpu
|
|
|
73
76
|
for (const selected of devices) {
|
|
74
77
|
report.probes.push(await probeBackend({ device: selected, dtype, webgpuBackend, timeoutMs, env }));
|
|
75
78
|
}
|
|
76
|
-
report.ok = report.probes.length > 0 && report.probes.every((p) => p.ok);
|
|
79
|
+
report.ok = !service.error && report.probes.length > 0 && report.probes.every((p) => p.ok);
|
|
77
80
|
}
|
|
78
81
|
return report;
|
|
79
82
|
}
|
|
@@ -85,12 +88,14 @@ export function formatDoctor(report) {
|
|
|
85
88
|
...report.backends.map((b) => `${b.device.padEnd(8)}: ${b.available ? 'presente' : 'assente'} — ${b.reason}`),
|
|
86
89
|
`backend WebGPU richiesto: ${(report.requested ?? report.settings.config).webgpuBackend} (solo quando il provider selezionato è WebGPU)`,
|
|
87
90
|
`Vulkan forzato: ${report.vulkan?.available ? 'presente' : 'assente'} — ${report.vulkan?.reason ?? 'non verificato'}`];
|
|
91
|
+
lines.push(`servizio condiviso: ${report.service?.error ?? (report.service?.running ? `PID ${report.service.pid} · ${report.service.pending} richieste · ${Math.round(report.service.rssBytes / 1024 / 1024)} MiB RSS` : report.service?.stopping ? 'in arresto' : 'fermo (nessun avvio da doctor)')}`);
|
|
88
92
|
if (!report.probes.length) lines.push('Nessuna inferenza eseguita. Usa doctor --probe (può scaricare i pesi), oppure --probe --all.');
|
|
89
93
|
for (const p of report.probes) {
|
|
90
94
|
lines.push(p.ok ? `PASS ${p.requested}: ${formatProfile(p.profile)} · ${p.profile.dims}d · ${p.elapsedMs} ms · cos(stesso) ${p.cosSame.toFixed(6)}`
|
|
91
95
|
: `FAIL ${p.requested}: ${p.error}`);
|
|
92
96
|
for (const a of p.attempts ?? []) if (!a.ok) lines.push(` ${formatProfile(a)}: ${a.error}`);
|
|
93
97
|
}
|
|
98
|
+
if (report.probes.length) lines.push('Probe isolati: possono caricare una copia aggiuntiva del modello mentre il servizio condiviso è attivo.');
|
|
94
99
|
lines.push('Un provider presente non prova compatibilità/velocità del modello; ORT può eseguire alcuni operatori su CPU.');
|
|
95
100
|
return lines.join('\n');
|
|
96
101
|
}
|