@alucard_24/pi-codeindex 1.1.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 +20 -0
- package/README.md +61 -12
- package/config.schema.json +1 -1
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/extension.js +84 -38
- package/src/project-state.js +53 -0
- package/src/service-client.js +5 -1
- package/src/workspace.js +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
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
|
+
|
|
3
23
|
## 1.1.0
|
|
4
24
|
|
|
5
25
|
### Novità
|
package/README.md
CHANGED
|
@@ -7,6 +7,10 @@ Una sola copia del modello fra processi pi/CLI sullo stesso endpoint;
|
|
|
7
7
|
nessun servizio remoto o database. Indice: due file in
|
|
8
8
|
`~/.cache/pi-codeindex/<sha1(repo)>/`.
|
|
9
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.
|
|
13
|
+
|
|
10
14
|
## Requisiti
|
|
11
15
|
|
|
12
16
|
- Node 22+ (testato su 26.5.1).
|
|
@@ -20,7 +24,7 @@ nessun servizio remoto o database. Indice: due file in
|
|
|
20
24
|
### Da npm, solo su richiesta
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
|
-
pi -e npm:@alucard_24/pi-codeindex@1.
|
|
27
|
+
pi -e npm:@alucard_24/pi-codeindex@1.1.1
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
Carica il pacchetto per questa invocazione, senza aggiungerlo alle impostazioni
|
|
@@ -162,13 +166,25 @@ Stop viene rifiutato se ci sono richieste attive/in coda; non interrompe altri
|
|
|
162
166
|
client. Il servizio si riavvierà al prossimo utilizzo del modello. Nessun
|
|
163
167
|
avvio al login, servizio di sistema o modifica alle impostazioni globali di pi.
|
|
164
168
|
|
|
165
|
-
Il listener è solo `127.0.0.1`,
|
|
166
|
-
|
|
169
|
+
Il listener è solo `127.0.0.1`, sulla porta predefinita **`43127`**, con
|
|
170
|
+
autenticazione tramite token in `~/.cache/pi-codeindex/service/`
|
|
167
171
|
(directory 0700, record 0600 su POSIX). Il binding esclusivo della porta
|
|
168
|
-
impedisce due avvii simultanei
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
172
188
|
|
|
173
189
|
Restano per-processo le cache degli indici, dei vettori e dei grafi: il servizio
|
|
174
190
|
elimina le copie del **modello**, non tutta la memoria di pi. Dopo l'aggiornamento
|
|
@@ -236,7 +252,7 @@ non default silenziosi. Schema per editor: [config.schema.json](config.schema.js
|
|
|
236
252
|
| `cpuThreads` | 0 = default ORT, oppure 1–256; non limita la GPU | `CODEINDEX_CPU_THREADS` |
|
|
237
253
|
| `serviceIdleMs` | default 300000 (5 min), da 1000 a 86400000 ms | `CODEINDEX_SERVICE_IDLE_MS` |
|
|
238
254
|
| `serviceDir` | directory privata condivisa, default `~/.cache/pi-codeindex/service` | `CODEINDEX_SERVICE_DIR` |
|
|
239
|
-
| `servicePort` |
|
|
255
|
+
| `servicePort` | default **43127**; 1024–65535, oppure 0 per la scelta deterministica precedente; solo loopback | `CODEINDEX_SERVICE_PORT` |
|
|
240
256
|
|
|
241
257
|
**Riavvia pi con un nuovo processo** dopo una modifica: non si cambia pipeline
|
|
242
258
|
nel mezzo di un run. Se il servizio ha già un profilo/configurazione diversi,
|
|
@@ -287,11 +303,43 @@ node src/cli.js embed-test
|
|
|
287
303
|
mai dal repo.
|
|
288
304
|
- `eval` misura recall@3/@8 e latenza sulle query della fixture.
|
|
289
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
|
+
|
|
290
338
|
## Uso dentro pi
|
|
291
339
|
|
|
292
340
|
```bash
|
|
293
341
|
# pacchetto npm: solo questa invocazione, nessuna configurazione globale
|
|
294
|
-
pi -e npm:@alucard_24/pi-codeindex@1.
|
|
342
|
+
pi -e npm:@alucard_24/pi-codeindex@1.1.1
|
|
295
343
|
|
|
296
344
|
# in sviluppo, dalla directory del progetto
|
|
297
345
|
pi -e ./src/extension.js
|
|
@@ -314,9 +362,10 @@ Cosa aggiunge:
|
|
|
314
362
|
durante l'indicizzazione).
|
|
315
363
|
- **`/codeindex status|reindex [--force]|enable|disable|config|configure|doctor`**. `disable` spegne
|
|
316
364
|
tutto: tool, outline, riga di stato, riga nel system prompt, watcher e
|
|
317
|
-
indicizzazione in corso. Lo stato è persistito
|
|
318
|
-
|
|
319
|
-
|
|
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,
|
|
320
369
|
single-flight, interrompibile, con cap 5.000 file / 200 MB.
|
|
321
370
|
- **sincronizzazione**: watcher ricorsivo con debounce 800 ms, invalidazione
|
|
322
371
|
diretta da `write`/`edit` e controllo dopo `bash`: sweep `git status` nei
|
package/config.schema.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"cacheDir": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "Cache dei pesi. Supporta ~/; i percorsi relativi sono risolti dal cwd." },
|
|
14
14
|
"cpuThreads": { "type": "integer", "minimum": 0, "maximum": 256, "default": 0, "description": "0 usa il default di ONNX Runtime; non limita la GPU." },
|
|
15
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":
|
|
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
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." }
|
|
18
18
|
}
|
|
19
19
|
}
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -28,7 +28,7 @@ function expandPath(value, home = os.homedir()) {
|
|
|
28
28
|
export function defaults(home = os.homedir()) {
|
|
29
29
|
return { device: 'auto', dtype: 'auto', webgpuBackend: 'auto', fallback: 'error', offline: false,
|
|
30
30
|
cacheDir: path.join(home, '.cache', 'pi-codeindex', 'text-model'), cpuThreads: 0,
|
|
31
|
-
serviceDir: path.join(home, '.cache', 'pi-codeindex', 'service'), servicePort:
|
|
31
|
+
serviceDir: path.join(home, '.cache', 'pi-codeindex', 'service'), servicePort: 43127, serviceIdleMs: 300_000 };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function validateConfig(value, source = 'config') {
|
package/src/extension.js
CHANGED
|
@@ -13,6 +13,7 @@ import { indexRepo } from './indexer.js';
|
|
|
13
13
|
import { embeddingStatus, serviceCommand, getServiceStatus, formatServiceStatus } from './embed.js';
|
|
14
14
|
import { formatProfile } from './backend.js';
|
|
15
15
|
import { configCommand } from './config.js';
|
|
16
|
+
import { readProjectState, writeProjectState } from './project-state.js';
|
|
16
17
|
import { configure } from './config-ui.js';
|
|
17
18
|
import { doctor, formatDoctor, parseDoctorArgs } from './doctor.js';
|
|
18
19
|
import { loadIndex } from './store.js';
|
|
@@ -27,25 +28,52 @@ const STATUS_KEY = 'codeindex';
|
|
|
27
28
|
const DEFAULT_K = 8;
|
|
28
29
|
const MAX_K = 30;
|
|
29
30
|
const MAX_CAPS = { maxFiles: 5000, maxBytes: 200 * 1024 * 1024 };
|
|
30
|
-
const CONFIG_ENTRY = 'codeindex-config';
|
|
31
31
|
const DEBOUNCE_MS = 800;
|
|
32
32
|
const MAX_SWEEP_PATHS = 200;
|
|
33
33
|
|
|
34
34
|
let state = null; // { repo, mode, enabled, indexing, progress, index, error, abort, queue, watcher, rerun }
|
|
35
35
|
const running = new Map(); // repo -> promise (single-flight per repo)
|
|
36
36
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
function applyEnabled(ctx, enabled, { start = true } = {}) {
|
|
38
|
+
const st = state;
|
|
39
|
+
st.enabled = enabled;
|
|
40
|
+
if (enabled) {
|
|
41
|
+
if (st.abort.signal.aborted) st.abort = new AbortController();
|
|
42
|
+
st.error = null;
|
|
43
|
+
if (start) {
|
|
44
|
+
if (!st.watcher) startWatcher(ctx);
|
|
45
|
+
void runIndex(ctx);
|
|
44
46
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
+
} else {
|
|
48
|
+
stopBackground();
|
|
49
|
+
st.index = null;
|
|
50
|
+
st.graph = null;
|
|
51
|
+
invalidateSearcher(st.repo);
|
|
52
|
+
}
|
|
53
|
+
showStatus(ctx);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function syncProjectState(ctx, options = {}) {
|
|
57
|
+
const st = state;
|
|
58
|
+
if (!st) return false;
|
|
59
|
+
try {
|
|
60
|
+
const saved = await readProjectState(st.repo);
|
|
61
|
+
if (state !== st) return false;
|
|
62
|
+
if (st.error === st.activationError) st.error = null;
|
|
63
|
+
st.activationError = null;
|
|
64
|
+
st.activationFile = saved.file;
|
|
65
|
+
const enabled = saved.enabled && (st.autoIndex || st.manualConsent);
|
|
66
|
+
if (enabled !== st.enabled) applyEnabled(ctx, enabled, options);
|
|
67
|
+
return enabled;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (state !== st) return false;
|
|
70
|
+
const changed = st.activationError !== e.message;
|
|
71
|
+
applyEnabled(ctx, false, options);
|
|
72
|
+
st.activationError = e.message;
|
|
73
|
+
st.error = e.message;
|
|
74
|
+
if (changed) ctx.ui.notify(`codeindex: ${e.message}`, 'warning');
|
|
75
|
+
return false;
|
|
47
76
|
}
|
|
48
|
-
return true;
|
|
49
77
|
}
|
|
50
78
|
|
|
51
79
|
function showStatus(ctx) {
|
|
@@ -93,18 +121,20 @@ function runIndex(ctx, { force = false } = {}) {
|
|
|
93
121
|
return inflight;
|
|
94
122
|
}
|
|
95
123
|
|
|
124
|
+
const signal = st.abort.signal;
|
|
96
125
|
const p = (async () => {
|
|
97
|
-
st.indexing = true;
|
|
98
|
-
st.rerun = false;
|
|
99
|
-
st.progress = null;
|
|
100
|
-
st.error = null;
|
|
101
|
-
showStatus(ctx);
|
|
102
126
|
try {
|
|
127
|
+
if (!await syncProjectState(ctx, { start: false }) || state !== st || signal.aborted) return null;
|
|
128
|
+
st.indexing = true;
|
|
129
|
+
st.rerun = false;
|
|
130
|
+
st.progress = null;
|
|
131
|
+
st.error = null;
|
|
132
|
+
showStatus(ctx);
|
|
103
133
|
const r = await indexRepo({
|
|
104
134
|
root: repo,
|
|
105
135
|
...MAX_CAPS,
|
|
106
136
|
force,
|
|
107
|
-
signal
|
|
137
|
+
signal,
|
|
108
138
|
log: () => {}, // niente rumore su stderr dentro la TUI
|
|
109
139
|
onProgress: (p) => {
|
|
110
140
|
if (state !== st) return; // sessione sostituita: non toccarne lo stato
|
|
@@ -112,6 +142,7 @@ function runIndex(ctx, { force = false } = {}) {
|
|
|
112
142
|
showStatus(ctx);
|
|
113
143
|
},
|
|
114
144
|
});
|
|
145
|
+
if (state !== st || !st.enabled || signal.aborted) return null;
|
|
115
146
|
st.index = r.index;
|
|
116
147
|
const runtime = embeddingStatus();
|
|
117
148
|
const warnings = [...runtime.warnings, ...runtime.attempts.filter((a) => !a.ok).map((a) => `${a.device}: ${a.error}`)];
|
|
@@ -123,7 +154,7 @@ function runIndex(ctx, { force = false } = {}) {
|
|
|
123
154
|
invalidateSearcher(repo);
|
|
124
155
|
return r;
|
|
125
156
|
} catch (e) {
|
|
126
|
-
if (
|
|
157
|
+
if (signal.aborted) return null;
|
|
127
158
|
st.error = e.message ?? String(e);
|
|
128
159
|
if (state === st && st.enabled && !/abort/i.test(st.error)) {
|
|
129
160
|
// Degradazione silenziosa: il tool resta disponibile e risponde con lo stato.
|
|
@@ -347,8 +378,10 @@ function statusReport() {
|
|
|
347
378
|
if (state.mode === 'git') lines.push(`head : ${i.head ?? '?'}`);
|
|
348
379
|
lines.push(`aggiornato : ${i.updatedAt}`);
|
|
349
380
|
} else {
|
|
350
|
-
lines.push('indice : assente (usa /codeindex reindex)'
|
|
381
|
+
lines.push(state.enabled ? 'indice : assente (usa /codeindex reindex)'
|
|
382
|
+
: 'indice : non caricato (usa /codeindex enable)');
|
|
351
383
|
}
|
|
384
|
+
if (state.activationFile) lines.push(`attivazione progetto: ${state.activationFile}`);
|
|
352
385
|
const runtime = embeddingStatus();
|
|
353
386
|
lines.push(`profilo client: ${runtime.profile ? formatProfile(runtime.profile) : 'non ancora associato al servizio'}`);
|
|
354
387
|
if (i?.device) lines.push(`profilo indice: ${formatProfile(i)}`);
|
|
@@ -396,7 +429,8 @@ export default function codeindexExtension(pi) {
|
|
|
396
429
|
if (sub === 'status') {
|
|
397
430
|
// La sessione può chiedere lo stato prima che il refresh in background
|
|
398
431
|
// abbia finito: meglio aspettare che mentire ("indice assente").
|
|
399
|
-
|
|
432
|
+
await syncProjectState(ctx);
|
|
433
|
+
if (state.enabled && !state.index) await refreshMeta();
|
|
400
434
|
showStatus(ctx);
|
|
401
435
|
let service;
|
|
402
436
|
try { service = formatServiceStatus(await getServiceStatus()); }
|
|
@@ -405,6 +439,11 @@ export default function codeindexExtension(pi) {
|
|
|
405
439
|
return;
|
|
406
440
|
}
|
|
407
441
|
if (sub === 'reindex') {
|
|
442
|
+
await syncProjectState(ctx);
|
|
443
|
+
if (!state.enabled) {
|
|
444
|
+
ctx.ui.notify('codeindex: progetto disattivato; usa prima /codeindex enable', 'warning');
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
408
447
|
if (state.indexing) {
|
|
409
448
|
ctx.ui.notify('codeindex: indicizzazione già in corso', 'warning');
|
|
410
449
|
return;
|
|
@@ -414,24 +453,20 @@ export default function codeindexExtension(pi) {
|
|
|
414
453
|
return;
|
|
415
454
|
}
|
|
416
455
|
if (sub === 'enable' || sub === 'disable') {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// Un abort precedente resta sul vecchio controller: senza rinnovarlo
|
|
420
|
-
// ogni run futuro uscirebbe subito.
|
|
421
|
-
state.abort = new AbortController();
|
|
422
|
-
state.error = null;
|
|
423
|
-
if (!state.watcher) startWatcher(ctx);
|
|
424
|
-
} else {
|
|
425
|
-
stopBackground(); // watcher, coda, run in corso e rerun pendente
|
|
426
|
-
}
|
|
427
|
-
showStatus(ctx);
|
|
456
|
+
const st = state;
|
|
457
|
+
const enabled = sub === 'enable';
|
|
428
458
|
try {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
459
|
+
const saved = await writeProjectState(st.repo, enabled);
|
|
460
|
+
if (state !== st) return;
|
|
461
|
+
st.activationFile = saved.file;
|
|
462
|
+
st.activationError = null;
|
|
463
|
+
st.error = null;
|
|
464
|
+
st.manualConsent = enabled;
|
|
465
|
+
applyEnabled(ctx, enabled);
|
|
466
|
+
ctx.ui.notify(`codeindex: ${enabled ? 'attivo' : 'disattivato'} per questo progetto (${st.repo})`, 'info');
|
|
467
|
+
} catch (e) {
|
|
468
|
+
ctx.ui.notify(`codeindex: attivazione progetto non salvata (${e.message})`, 'error');
|
|
432
469
|
}
|
|
433
|
-
if (state.enabled) void runIndex(ctx);
|
|
434
|
-
ctx.ui.notify(`codeindex: ${state.enabled ? 'attivo' : 'disattivato'}`, 'info');
|
|
435
470
|
return;
|
|
436
471
|
}
|
|
437
472
|
ctx.ui.notify('uso: /codeindex status|reindex [--force]|enable|disable|config [show|set CHIAVE VALORE|unset CHIAVE]|configure|service [status [--json]|stop]|doctor [--probe] [--all] [--device DEVICE] [--webgpu-backend auto|vulkan]', 'warning');
|
|
@@ -440,13 +475,19 @@ export default function codeindexExtension(pi) {
|
|
|
440
475
|
|
|
441
476
|
// Risorse di sessione: watcher e timer non vanno creati nel factory.
|
|
442
477
|
pi.on('session_start', async (_event, ctx) => {
|
|
478
|
+
stopBackground();
|
|
479
|
+
invalidateSearcher();
|
|
443
480
|
const workspace = resolveWorkspace(ctx.cwd);
|
|
444
481
|
const st = {
|
|
445
482
|
repo: workspace.root,
|
|
446
483
|
mode: workspace.mode,
|
|
447
484
|
// Le cartelle protette richiedono consenso a ogni nuova sessione,
|
|
448
485
|
// anche se un enable era stato salvato nel branch in un altro progetto.
|
|
449
|
-
enabled:
|
|
486
|
+
enabled: false,
|
|
487
|
+
autoIndex: workspace.autoIndex,
|
|
488
|
+
manualConsent: false,
|
|
489
|
+
activationFile: null,
|
|
490
|
+
activationError: null,
|
|
450
491
|
indexing: false,
|
|
451
492
|
progress: null,
|
|
452
493
|
index: null,
|
|
@@ -461,11 +502,14 @@ export default function codeindexExtension(pi) {
|
|
|
461
502
|
if (state === st && st.enabled) void runIndex(ctx);
|
|
462
503
|
}, { delayMs: DEBOUNCE_MS });
|
|
463
504
|
state = st;
|
|
505
|
+
await syncProjectState(ctx, { start: false });
|
|
506
|
+
if (state !== st) return;
|
|
464
507
|
showStatus(ctx);
|
|
465
508
|
if (!workspace.autoIndex && !st.enabled) {
|
|
466
509
|
ctx.ui.notify('codeindex: indicizzazione automatica disattivata nella home o nella radice del disco; usa una cartella di progetto o /codeindex enable', 'info');
|
|
467
510
|
}
|
|
468
|
-
|
|
511
|
+
if (!st.enabled) return;
|
|
512
|
+
// Solo i progetti abilitati caricano metadati, watcher e indice.
|
|
469
513
|
void (async () => {
|
|
470
514
|
await refreshMeta();
|
|
471
515
|
if (state !== st) return;
|
|
@@ -485,6 +529,7 @@ export default function codeindexExtension(pi) {
|
|
|
485
529
|
// Una riga stabile nel system prompt rende il tool scopribile; i conteggi
|
|
486
530
|
// esatti restano nel footer per non invalidare la prompt cache a ogni run.
|
|
487
531
|
pi.on('before_agent_start', async (event, ctx) => {
|
|
532
|
+
await syncProjectState(ctx);
|
|
488
533
|
showStatus(ctx);
|
|
489
534
|
if (!state?.enabled) return undefined; // spento: nessuna riga nel prompt
|
|
490
535
|
// Un checkout può cambiare HEAD senza eventi utili del watcher.
|
|
@@ -501,6 +546,7 @@ export default function codeindexExtension(pi) {
|
|
|
501
546
|
|
|
502
547
|
// Invalidazione diretta: `write`/`edit` sanno esattamente che path hanno toccato.
|
|
503
548
|
pi.on('tool_result', async (event, ctx) => {
|
|
549
|
+
await syncProjectState(ctx);
|
|
504
550
|
if (!state?.enabled) return undefined;
|
|
505
551
|
try {
|
|
506
552
|
// Anche un comando terminato con errore può aver modificato dei file.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Consenso persistente per progetto, separato dal profilo embedding globale.
|
|
2
|
+
// Nessun file viene letto o scritto nel repository del progetto.
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
+
import { configPath } from './config.js';
|
|
7
|
+
|
|
8
|
+
export function projectIdentity(root, platform = process.platform) {
|
|
9
|
+
const resolved = (platform === 'win32' ? path.win32 : path).resolve(root);
|
|
10
|
+
return platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function location(root, options = {}) {
|
|
14
|
+
const project = await fs.realpath(path.resolve(root));
|
|
15
|
+
const identity = projectIdentity(project);
|
|
16
|
+
const hash = createHash('sha256').update(identity).digest('hex');
|
|
17
|
+
const file = path.join(path.dirname(configPath(options.env, options.home)), 'projects', `${hash}.json`);
|
|
18
|
+
return { project, identity, file };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function readProjectState(root, options = {}) {
|
|
22
|
+
const target = await location(root, options);
|
|
23
|
+
let text;
|
|
24
|
+
try { text = await fs.readFile(target.file, 'utf8'); }
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (e.code === 'ENOENT') return { ...target, enabled: false };
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
let value;
|
|
30
|
+
try { value = JSON.parse(text); }
|
|
31
|
+
catch { throw new Error(`attivazione progetto non valida: ${target.file}`); }
|
|
32
|
+
if (!value || value.version !== 1 || typeof value.enabled !== 'boolean'
|
|
33
|
+
|| typeof value.project !== 'string' || !path.isAbsolute(value.project)
|
|
34
|
+
|| projectIdentity(value.project) !== target.identity) {
|
|
35
|
+
throw new Error(`attivazione progetto non valida: ${target.file}`);
|
|
36
|
+
}
|
|
37
|
+
return { ...target, enabled: value.enabled };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function writeProjectState(root, enabled, options = {}) {
|
|
41
|
+
if (typeof enabled !== 'boolean') throw new Error('attivazione progetto: enabled deve essere booleano');
|
|
42
|
+
const target = await location(root, options);
|
|
43
|
+
await fs.mkdir(path.dirname(target.file), { recursive: true, mode: 0o700 });
|
|
44
|
+
const tmp = `${target.file}.${randomUUID()}.tmp`;
|
|
45
|
+
try {
|
|
46
|
+
await fs.writeFile(tmp, JSON.stringify({ version: 1, project: target.project, enabled }, null, 2) + '\n',
|
|
47
|
+
{ flag: 'wx', mode: 0o600 });
|
|
48
|
+
await fs.rename(tmp, target.file);
|
|
49
|
+
} finally {
|
|
50
|
+
await fs.rm(tmp, { force: true });
|
|
51
|
+
}
|
|
52
|
+
return { ...target, enabled };
|
|
53
|
+
}
|
package/src/service-client.js
CHANGED
|
@@ -43,7 +43,11 @@ export function createEmbeddingClient({ env = process.env, settings = () => load
|
|
|
43
43
|
child.on('error', (e) => { launchError = e; childRunning = false; });
|
|
44
44
|
child.on('exit', () => { childRunning = false; });
|
|
45
45
|
child.on('message', (message) => {
|
|
46
|
-
if (message?.ready === false && message.code !== 'EADDRINUSE')
|
|
46
|
+
if (message?.ready === false && message.code !== 'EADDRINUSE') {
|
|
47
|
+
const hint = message.code === 'EACCES' && /^listen\b/.test(message.error ?? '')
|
|
48
|
+
? ' Porta locale non utilizzabile: potrebbe essere esclusa dal sistema. Scegli una porta libera con /codeindex config set servicePort NUMERO e riavvia pi. Nessun fallback in-process.' : '';
|
|
49
|
+
launchError = serviceError(message.code ?? 'START_FAILED', `${message.error}${hint}`);
|
|
50
|
+
}
|
|
47
51
|
});
|
|
48
52
|
child.unref(); child.channel?.unref();
|
|
49
53
|
};
|
package/src/workspace.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// Radice del progetto: Git quando disponibile, altrimenti la cartella corrente.
|
|
2
2
|
// Nessuna configurazione di progetto viene letta e nessun repository viene creato.
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { repoRoot } from './util.js';
|
|
6
7
|
|
|
8
|
+
function canonicalPath(value) {
|
|
9
|
+
let result = path.resolve(value);
|
|
10
|
+
try { result = fs.realpathSync.native(result); } catch { /* directory non accessibile */ }
|
|
11
|
+
return process.platform === 'win32' ? result.toLowerCase() : result;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
export function resolveWorkspace(cwd = process.cwd(), {
|
|
8
15
|
findRepoRoot = repoRoot,
|
|
9
16
|
home = os.homedir(),
|
|
@@ -13,6 +20,7 @@ export function resolveWorkspace(cwd = process.cwd(), {
|
|
|
13
20
|
const mode = gitRoot ? 'git' : 'folder';
|
|
14
21
|
// Un avvio dalla home o dalla radice del disco non deve scandire l'intera
|
|
15
22
|
// macchina per errore. /codeindex enable resta un consenso esplicito.
|
|
16
|
-
const
|
|
23
|
+
const canonicalRoot = canonicalPath(root);
|
|
24
|
+
const broadFolder = canonicalRoot === path.parse(canonicalRoot).root || canonicalRoot === canonicalPath(home);
|
|
17
25
|
return { root, mode, autoIndex: mode === 'git' || !broadFolder };
|
|
18
26
|
}
|