@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 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.0.0
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`, con porta deterministica per la directory
166
- utente e autenticazione tramite token in `~/.cache/pi-codeindex/service/`
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. Una porta occupata da un altro programma dà
169
- un errore: `servicePort` consente una scelta esplicita. Usa gli stessi
170
- `serviceDir`/`servicePort` in tutti i client: cambiarli crea endpoint distinti
171
- e permette deliberatamente più modelli. Non condividere il token o la directory.
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` | 0 = deterministica, oppure 1024–65535; solo loopback | `CODEINDEX_SERVICE_PORT` |
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.0.0
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 nel branch della sessione;
318
- una sessione nuova riparte attiva.
319
- - **indicizzazione automatica** all'avvio della sessione, in background,
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
@@ -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": 0, "description": "0 sceglie una porta loopback deterministica dalla directory del servizio. Cambiarla crea un endpoint distinto." },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alucard_24/pi-codeindex",
3
- "version": "1.1.0",
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",
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: 0, serviceIdleMs: 300_000 };
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 readEnabled(ctx) {
38
- try {
39
- for (const entry of ctx.sessionManager.getBranch()) {
40
- if (entry.type === 'custom' && entry.customType === CONFIG_ENTRY) {
41
- const data = entry.data ?? {};
42
- if (typeof data.enabled === 'boolean') return data.enabled;
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
- } catch {
46
- /* nessuno stato salvato: default attivo */
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: st.abort.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 (st.abort.signal.aborted) return null;
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
- if (!state.index) await refreshMeta();
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
- state.enabled = sub === 'enable';
418
- if (state.enabled) {
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
- pi.appendEntry(CONFIG_ENTRY, { enabled: state.enabled });
430
- } catch {
431
- /* stato non persistito: vale per questa sessione */
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: workspace.autoIndex && readEnabled(ctx),
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
- // Non bloccare l'avvio: metadati, watcher e indice corrono in background.
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
+ }
@@ -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') launchError = serviceError(message.code ?? 'START_FAILED', message.error);
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 broadFolder = root === path.parse(root).root || root === path.resolve(home);
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
  }