kiro-memory 1.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/package.json +5 -5
  3. package/plugin/dist/cli/contextkit.js +2611 -345
  4. package/plugin/dist/hooks/agentSpawn.js +853 -223
  5. package/plugin/dist/hooks/kiro-hooks.js +841 -211
  6. package/plugin/dist/hooks/postToolUse.js +853 -222
  7. package/plugin/dist/hooks/stop.js +850 -220
  8. package/plugin/dist/hooks/userPromptSubmit.js +848 -216
  9. package/plugin/dist/index.js +843 -340
  10. package/plugin/dist/plugins/github/github-client.js +152 -0
  11. package/plugin/dist/plugins/github/index.js +412 -0
  12. package/plugin/dist/plugins/github/issue-parser.js +54 -0
  13. package/plugin/dist/plugins/slack/formatter.js +90 -0
  14. package/plugin/dist/plugins/slack/index.js +215 -0
  15. package/plugin/dist/sdk/index.js +841 -215
  16. package/plugin/dist/servers/mcp-server.js +4461 -397
  17. package/plugin/dist/services/search/EmbeddingService.js +146 -37
  18. package/plugin/dist/services/search/HybridSearch.js +564 -116
  19. package/plugin/dist/services/search/VectorSearch.js +187 -60
  20. package/plugin/dist/services/search/index.js +565 -254
  21. package/plugin/dist/services/sqlite/Backup.js +416 -0
  22. package/plugin/dist/services/sqlite/Database.js +126 -153
  23. package/plugin/dist/services/sqlite/ImportExport.js +452 -0
  24. package/plugin/dist/services/sqlite/Observations.js +314 -19
  25. package/plugin/dist/services/sqlite/Prompts.js +1 -1
  26. package/plugin/dist/services/sqlite/Search.js +41 -29
  27. package/plugin/dist/services/sqlite/Summaries.js +4 -4
  28. package/plugin/dist/services/sqlite/index.js +1428 -208
  29. package/plugin/dist/viewer.css +1 -0
  30. package/plugin/dist/viewer.html +2 -179
  31. package/plugin/dist/viewer.js +23 -24942
  32. package/plugin/dist/viewer.js.map +7 -0
  33. package/plugin/dist/worker-service.js +427 -5569
  34. package/plugin/dist/worker-service.js.map +7 -0
@@ -241,6 +241,290 @@ ${data.stack}` : ` ${data.message}`;
241
241
  }
242
242
  });
243
243
 
244
+ // src/utils/secrets.ts
245
+ function redactSecrets(text) {
246
+ if (!text) return text;
247
+ let redacted = text;
248
+ for (const { pattern } of SECRET_PATTERNS) {
249
+ pattern.lastIndex = 0;
250
+ redacted = redacted.replace(pattern, (match) => {
251
+ const prefix = match.substring(0, Math.min(4, match.length));
252
+ return `${prefix}***REDACTED***`;
253
+ });
254
+ }
255
+ return redacted;
256
+ }
257
+ var SECRET_PATTERNS;
258
+ var init_secrets = __esm({
259
+ "src/utils/secrets.ts"() {
260
+ "use strict";
261
+ SECRET_PATTERNS = [
262
+ // AWS Access Keys (AKIA, ABIA, ACCA, ASIA prefixes + 16 alphanumeric chars)
263
+ { name: "aws-key", pattern: /(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/g },
264
+ // JWT tokens (three base64url segments separated by dots)
265
+ { name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
266
+ // Generic API keys in key=value or key: value assignments
267
+ { name: "api-key", pattern: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi },
268
+ // Password/secret/token in variable assignments
269
+ { name: "credential", pattern: /(?:password|passwd|pwd|secret|token|auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
270
+ // Credentials embedded in URLs (user:pass@host)
271
+ { name: "url-credential", pattern: /(?:https?:\/\/)([^:]+):([^@]+)@/g },
272
+ // PEM-encoded private keys (RSA, EC, DSA, OpenSSH)
273
+ { name: "private-key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
274
+ // GitHub personal access tokens (ghp_, gho_, ghu_, ghs_, ghr_ prefixes)
275
+ { name: "github-token", pattern: /gh[pousr]_[a-zA-Z0-9]{36,}/g },
276
+ // Slack bot/user/app tokens
277
+ { name: "slack-token", pattern: /xox[bpoas]-[a-zA-Z0-9-]{10,}/g },
278
+ // HTTP Authorization Bearer header values
279
+ { name: "bearer-header", pattern: /\bBearer\s+([a-zA-Z0-9_\-\.]{20,})/g },
280
+ // Generic hex secrets (32+ hex chars after a key/secret/token/password label)
281
+ { name: "hex-secret", pattern: /(?:key|secret|token|password)\s*[:=]\s*['"]?([0-9a-f]{32,})['"]?/gi }
282
+ ];
283
+ }
284
+ });
285
+
286
+ // src/utils/categorizer.ts
287
+ function categorize(input) {
288
+ const scores = /* @__PURE__ */ new Map();
289
+ const searchText = [
290
+ input.title,
291
+ input.text || "",
292
+ input.narrative || "",
293
+ input.concepts || ""
294
+ ].join(" ").toLowerCase();
295
+ const allFiles = [input.filesModified || "", input.filesRead || ""].join(",");
296
+ for (const rule of CATEGORY_RULES) {
297
+ let score = 0;
298
+ for (const kw of rule.keywords) {
299
+ if (searchText.includes(kw.toLowerCase())) {
300
+ score += rule.weight;
301
+ }
302
+ }
303
+ if (rule.types && rule.types.includes(input.type)) {
304
+ score += rule.weight * 2;
305
+ }
306
+ if (rule.filePatterns && allFiles) {
307
+ for (const pattern of rule.filePatterns) {
308
+ if (pattern.test(allFiles)) {
309
+ score += rule.weight;
310
+ }
311
+ }
312
+ }
313
+ if (score > 0) {
314
+ scores.set(rule.category, (scores.get(rule.category) || 0) + score);
315
+ }
316
+ }
317
+ let bestCategory = "general";
318
+ let bestScore = 0;
319
+ for (const [category, score] of scores) {
320
+ if (score > bestScore) {
321
+ bestScore = score;
322
+ bestCategory = category;
323
+ }
324
+ }
325
+ return bestCategory;
326
+ }
327
+ var CATEGORY_RULES;
328
+ var init_categorizer = __esm({
329
+ "src/utils/categorizer.ts"() {
330
+ "use strict";
331
+ CATEGORY_RULES = [
332
+ {
333
+ category: "security",
334
+ keywords: [
335
+ "security",
336
+ "vulnerability",
337
+ "cve",
338
+ "xss",
339
+ "csrf",
340
+ "injection",
341
+ "sanitize",
342
+ "escape",
343
+ "auth",
344
+ "authentication",
345
+ "authorization",
346
+ "permission",
347
+ "helmet",
348
+ "cors",
349
+ "rate-limit",
350
+ "token",
351
+ "encrypt",
352
+ "decrypt",
353
+ "secret",
354
+ "redact",
355
+ "owasp"
356
+ ],
357
+ filePatterns: [/security/i, /auth/i, /secrets?\.ts/i],
358
+ weight: 10
359
+ },
360
+ {
361
+ category: "testing",
362
+ keywords: [
363
+ "test",
364
+ "spec",
365
+ "expect",
366
+ "assert",
367
+ "mock",
368
+ "stub",
369
+ "fixture",
370
+ "coverage",
371
+ "jest",
372
+ "vitest",
373
+ "bun test",
374
+ "unit test",
375
+ "integration test",
376
+ "e2e"
377
+ ],
378
+ types: ["test"],
379
+ filePatterns: [/\.test\./i, /\.spec\./i, /tests?\//i, /__tests__/i],
380
+ weight: 8
381
+ },
382
+ {
383
+ category: "debugging",
384
+ keywords: [
385
+ "debug",
386
+ "fix",
387
+ "bug",
388
+ "error",
389
+ "crash",
390
+ "stacktrace",
391
+ "stack trace",
392
+ "exception",
393
+ "breakpoint",
394
+ "investigate",
395
+ "root cause",
396
+ "troubleshoot",
397
+ "diagnose",
398
+ "bisect",
399
+ "regression"
400
+ ],
401
+ types: ["bugfix"],
402
+ weight: 8
403
+ },
404
+ {
405
+ category: "architecture",
406
+ keywords: [
407
+ "architect",
408
+ "design",
409
+ "pattern",
410
+ "modular",
411
+ "migration",
412
+ "schema",
413
+ "database",
414
+ "api design",
415
+ "abstract",
416
+ "dependency injection",
417
+ "singleton",
418
+ "factory",
419
+ "observer",
420
+ "middleware",
421
+ "pipeline",
422
+ "microservice",
423
+ "monolith"
424
+ ],
425
+ types: ["decision", "constraint"],
426
+ weight: 7
427
+ },
428
+ {
429
+ category: "refactoring",
430
+ keywords: [
431
+ "refactor",
432
+ "rename",
433
+ "extract",
434
+ "inline",
435
+ "move",
436
+ "split",
437
+ "merge",
438
+ "simplify",
439
+ "cleanup",
440
+ "clean up",
441
+ "dead code",
442
+ "consolidate",
443
+ "reorganize",
444
+ "restructure",
445
+ "decouple"
446
+ ],
447
+ weight: 6
448
+ },
449
+ {
450
+ category: "config",
451
+ keywords: [
452
+ "config",
453
+ "configuration",
454
+ "env",
455
+ "environment",
456
+ "dotenv",
457
+ ".env",
458
+ "settings",
459
+ "tsconfig",
460
+ "eslint",
461
+ "prettier",
462
+ "webpack",
463
+ "vite",
464
+ "esbuild",
465
+ "docker",
466
+ "ci/cd",
467
+ "github actions",
468
+ "deploy",
469
+ "build",
470
+ "bundle",
471
+ "package.json"
472
+ ],
473
+ filePatterns: [
474
+ /\.config\./i,
475
+ /\.env/i,
476
+ /tsconfig/i,
477
+ /\.ya?ml/i,
478
+ /Dockerfile/i,
479
+ /docker-compose/i
480
+ ],
481
+ weight: 5
482
+ },
483
+ {
484
+ category: "docs",
485
+ keywords: [
486
+ "document",
487
+ "readme",
488
+ "changelog",
489
+ "jsdoc",
490
+ "comment",
491
+ "explain",
492
+ "guide",
493
+ "tutorial",
494
+ "api doc",
495
+ "openapi",
496
+ "swagger"
497
+ ],
498
+ types: ["docs"],
499
+ filePatterns: [/\.md$/i, /docs?\//i, /readme/i, /changelog/i],
500
+ weight: 5
501
+ },
502
+ {
503
+ category: "feature-dev",
504
+ keywords: [
505
+ "feature",
506
+ "implement",
507
+ "add",
508
+ "create",
509
+ "new",
510
+ "endpoint",
511
+ "component",
512
+ "module",
513
+ "service",
514
+ "handler",
515
+ "route",
516
+ "hook",
517
+ "plugin",
518
+ "integration"
519
+ ],
520
+ types: ["feature", "file-write"],
521
+ weight: 3
522
+ // lowest — generic catch-all for development
523
+ }
524
+ ];
525
+ }
526
+ });
527
+
244
528
  // src/services/sqlite/Observations.ts
245
529
  var Observations_exports = {};
246
530
  __export(Observations_exports, {
@@ -266,11 +550,23 @@ function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
266
550
  }
267
551
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
268
552
  const now = /* @__PURE__ */ new Date();
553
+ const safeTitle = redactSecrets(title);
554
+ const safeText = text ? redactSecrets(text) : text;
555
+ const safeNarrative = narrative ? redactSecrets(narrative) : narrative;
556
+ const autoCategory = categorize({
557
+ type,
558
+ title: safeTitle,
559
+ text: safeText,
560
+ narrative: safeNarrative,
561
+ concepts,
562
+ filesModified,
563
+ filesRead
564
+ });
269
565
  const result = db.run(
270
566
  `INSERT INTO observations
271
- (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
272
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
273
- [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
567
+ (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens, auto_category)
568
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
569
+ [memorySessionId, project, type, safeTitle, subtitle, safeText, safeNarrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens, autoCategory]
274
570
  );
275
571
  return Number(result.lastInsertRowid);
276
572
  }
@@ -282,16 +578,16 @@ function getObservationsBySession(db, memorySessionId) {
282
578
  }
283
579
  function getObservationsByProject(db, project, limit = 100) {
284
580
  const query = db.query(
285
- "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
581
+ "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
286
582
  );
287
583
  return query.all(project, limit);
288
584
  }
289
585
  function searchObservations(db, searchTerm, project) {
290
586
  const sql = project ? `SELECT * FROM observations
291
587
  WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
292
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
588
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM observations
293
589
  WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
294
- ORDER BY created_at_epoch DESC`;
590
+ ORDER BY created_at_epoch DESC, id DESC`;
295
591
  const pattern = `%${escapeLikePattern(searchTerm)}%`;
296
592
  const query = db.query(sql);
297
593
  if (project) {
@@ -324,21 +620,32 @@ function consolidateObservations(db, project, options = {}) {
324
620
  ORDER BY cnt DESC
325
621
  `).all(project, minGroupSize);
326
622
  if (groups.length === 0) return { merged: 0, removed: 0 };
327
- let totalMerged = 0;
328
- let totalRemoved = 0;
623
+ if (options.dryRun) {
624
+ let totalMerged = 0;
625
+ let totalRemoved = 0;
626
+ for (const group of groups) {
627
+ const obsIds = group.ids.split(",").map(Number);
628
+ const placeholders = obsIds.map(() => "?").join(",");
629
+ const count = db.query(
630
+ `SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
631
+ ).get(...obsIds)?.cnt || 0;
632
+ if (count >= minGroupSize) {
633
+ totalMerged += 1;
634
+ totalRemoved += count - 1;
635
+ }
636
+ }
637
+ return { merged: totalMerged, removed: totalRemoved };
638
+ }
329
639
  const runConsolidation = db.transaction(() => {
640
+ let merged = 0;
641
+ let removed = 0;
330
642
  for (const group of groups) {
331
643
  const obsIds = group.ids.split(",").map(Number);
332
644
  const placeholders = obsIds.map(() => "?").join(",");
333
645
  const observations = db.query(
334
- `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
646
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`
335
647
  ).all(...obsIds);
336
648
  if (observations.length < minGroupSize) continue;
337
- if (options.dryRun) {
338
- totalMerged += 1;
339
- totalRemoved += observations.length - 1;
340
- continue;
341
- }
342
649
  const keeper = observations[0];
343
650
  const others = observations.slice(1);
344
651
  const uniqueTexts = /* @__PURE__ */ new Set();
@@ -351,22 +658,24 @@ function consolidateObservations(db, project, options = {}) {
351
658
  const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
352
659
  db.run(
353
660
  "UPDATE observations SET text = ?, title = ? WHERE id = ?",
354
- [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
661
+ [consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
355
662
  );
356
663
  const removeIds = others.map((o) => o.id);
357
664
  const removePlaceholders = removeIds.map(() => "?").join(",");
358
665
  db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
359
666
  db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
360
- totalMerged += 1;
361
- totalRemoved += removeIds.length;
667
+ merged += 1;
668
+ removed += removeIds.length;
362
669
  }
670
+ return { merged, removed };
363
671
  });
364
- runConsolidation();
365
- return { merged: totalMerged, removed: totalRemoved };
672
+ return runConsolidation();
366
673
  }
367
674
  var init_Observations = __esm({
368
675
  "src/services/sqlite/Observations.ts"() {
369
676
  "use strict";
677
+ init_secrets();
678
+ init_categorizer();
370
679
  }
371
680
  });
372
681
 
@@ -389,7 +698,7 @@ function escapeLikePattern3(input) {
389
698
  }
390
699
  function sanitizeFTS5Query(query) {
391
700
  const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
392
- const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
701
+ const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
393
702
  return terms.join(" ");
394
703
  }
395
704
  function searchObservationsFTS(db, query, filters = {}) {
@@ -486,7 +795,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
486
795
  sql += " AND created_at_epoch <= ?";
487
796
  params.push(filters.dateEnd);
488
797
  }
489
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
798
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
490
799
  params.push(limit);
491
800
  const stmt = db.query(sql);
492
801
  return stmt.all(...params);
@@ -511,7 +820,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
511
820
  sql += " AND created_at_epoch <= ?";
512
821
  params.push(filters.dateEnd);
513
822
  }
514
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
823
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
515
824
  params.push(limit);
516
825
  const stmt = db.query(sql);
517
826
  return stmt.all(...params);
@@ -521,7 +830,7 @@ function getObservationsByIds(db, ids) {
521
830
  const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
522
831
  if (validIds.length === 0) return [];
523
832
  const placeholders = validIds.map(() => "?").join(",");
524
- const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
833
+ const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`;
525
834
  const stmt = db.query(sql);
526
835
  return stmt.all(...validIds);
527
836
  }
@@ -533,11 +842,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
533
842
  const beforeStmt = db.query(`
534
843
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
535
844
  FROM observations
536
- WHERE created_at_epoch < ?
537
- ORDER BY created_at_epoch DESC
845
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
846
+ ORDER BY created_at_epoch DESC, id DESC
538
847
  LIMIT ?
539
848
  `);
540
- const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
849
+ const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
541
850
  const selfStmt = db.query(`
542
851
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
543
852
  FROM observations WHERE id = ?
@@ -546,34 +855,46 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
546
855
  const afterStmt = db.query(`
547
856
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
548
857
  FROM observations
549
- WHERE created_at_epoch > ?
550
- ORDER BY created_at_epoch ASC
858
+ WHERE (created_at_epoch > ? OR (created_at_epoch = ? AND id > ?))
859
+ ORDER BY created_at_epoch ASC, id ASC
551
860
  LIMIT ?
552
861
  `);
553
- const after = afterStmt.all(anchorEpoch, depthAfter);
862
+ const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
554
863
  return [...before, ...self, ...after];
555
864
  }
556
865
  function getProjectStats(db, project) {
557
- const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
558
- const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
559
- const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
560
- const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
561
- const discoveryStmt = db.query(
562
- "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
563
- );
564
- const discoveryTokens = discoveryStmt.get(project)?.total || 0;
565
- const readStmt = db.query(
566
- `SELECT COALESCE(SUM(
567
- CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
568
- ), 0) as total FROM observations WHERE project = ?`
569
- );
570
- const readTokens = readStmt.get(project)?.total || 0;
866
+ const sql = `
867
+ WITH
868
+ obs_stats AS (
869
+ SELECT
870
+ COUNT(*) as count,
871
+ COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
872
+ COALESCE(SUM(
873
+ CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
874
+ ), 0) as read_tokens
875
+ FROM observations WHERE project = ?
876
+ ),
877
+ sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
878
+ ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
879
+ prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
880
+ SELECT
881
+ obs_stats.count as observations,
882
+ obs_stats.discovery_tokens,
883
+ obs_stats.read_tokens,
884
+ sum_count.count as summaries,
885
+ ses_count.count as sessions,
886
+ prm_count.count as prompts
887
+ FROM obs_stats, sum_count, ses_count, prm_count
888
+ `;
889
+ const row = db.query(sql).get(project, project, project, project);
890
+ const discoveryTokens = row?.discovery_tokens || 0;
891
+ const readTokens = row?.read_tokens || 0;
571
892
  const savings = Math.max(0, discoveryTokens - readTokens);
572
893
  return {
573
- observations: obsStmt.get(project)?.count || 0,
574
- summaries: sumStmt.get(project)?.count || 0,
575
- sessions: sesStmt.get(project)?.count || 0,
576
- prompts: prmStmt.get(project)?.count || 0,
894
+ observations: row?.observations || 0,
895
+ summaries: row?.summaries || 0,
896
+ sessions: row?.sessions || 0,
897
+ prompts: row?.prompts || 0,
577
898
  tokenEconomics: { discoveryTokens, readTokens, savings }
578
899
  };
579
900
  }
@@ -581,7 +902,7 @@ function getStaleObservations(db, project) {
581
902
  const rows = db.query(`
582
903
  SELECT * FROM observations
583
904
  WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
584
- ORDER BY created_at_epoch DESC
905
+ ORDER BY created_at_epoch DESC, id DESC
585
906
  LIMIT 500
586
907
  `).all(project);
587
908
  const staleObs = [];
@@ -624,6 +945,464 @@ var init_Search = __esm({
624
945
  }
625
946
  });
626
947
 
948
+ // src/services/sqlite/ImportExport.ts
949
+ var ImportExport_exports = {};
950
+ __export(ImportExport_exports, {
951
+ JSONL_SCHEMA_VERSION: () => JSONL_SCHEMA_VERSION,
952
+ computeImportHash: () => computeImportHash,
953
+ countExportRecords: () => countExportRecords,
954
+ exportObservationsStreaming: () => exportObservationsStreaming,
955
+ exportPromptsStreaming: () => exportPromptsStreaming,
956
+ exportSummariesStreaming: () => exportSummariesStreaming,
957
+ generateMetaRecord: () => generateMetaRecord,
958
+ hashExistsInObservations: () => hashExistsInObservations,
959
+ importJsonl: () => importJsonl,
960
+ validateJsonlRow: () => validateJsonlRow
961
+ });
962
+ import { createHash } from "crypto";
963
+ function countExportRecords(db, filters) {
964
+ const { fromEpoch, toEpoch } = filtersToEpoch(filters);
965
+ const obsConds = buildConditions({ project: filters.project, type: filters.type, fromEpoch, toEpoch });
966
+ const sumConds = buildConditions({ project: filters.project, fromEpoch, toEpoch });
967
+ const promptConds = buildConditions({ project: filters.project, fromEpoch, toEpoch });
968
+ const obsCount = db.query(
969
+ `SELECT COUNT(*) as c FROM observations WHERE ${obsConds.where}`
970
+ ).get(...obsConds.params).c;
971
+ const sumCount = db.query(
972
+ `SELECT COUNT(*) as c FROM summaries WHERE ${sumConds.where}`
973
+ ).get(...sumConds.params).c;
974
+ const promptCount = db.query(
975
+ `SELECT COUNT(*) as c FROM prompts WHERE ${promptConds.where}`
976
+ ).get(...promptConds.params).c;
977
+ return { observations: obsCount, summaries: sumCount, prompts: promptCount };
978
+ }
979
+ function generateMetaRecord(db, filters) {
980
+ const counts = countExportRecords(db, filters);
981
+ const meta = {
982
+ _meta: {
983
+ version: JSONL_SCHEMA_VERSION,
984
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
985
+ counts,
986
+ filters: Object.keys(filters).length > 0 ? filters : void 0
987
+ }
988
+ };
989
+ return JSON.stringify(meta);
990
+ }
991
+ function exportObservationsStreaming(db, filters, onRow, batchSize = 200) {
992
+ const { fromEpoch, toEpoch } = filtersToEpoch(filters);
993
+ const conds = buildConditions({ project: filters.project, type: filters.type, fromEpoch, toEpoch });
994
+ let offset = 0;
995
+ let total = 0;
996
+ while (true) {
997
+ const rows = db.query(
998
+ `SELECT id, memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts,
999
+ files_read, files_modified, prompt_number, content_hash, discovery_tokens, auto_category,
1000
+ created_at, created_at_epoch
1001
+ FROM observations
1002
+ WHERE ${conds.where}
1003
+ ORDER BY created_at_epoch ASC, id ASC
1004
+ LIMIT ? OFFSET ?`
1005
+ ).all(...conds.params, batchSize, offset);
1006
+ if (rows.length === 0) break;
1007
+ for (const row of rows) {
1008
+ const record = {
1009
+ _type: "observation",
1010
+ id: row.id,
1011
+ memory_session_id: row.memory_session_id,
1012
+ project: row.project,
1013
+ type: row.type,
1014
+ title: row.title,
1015
+ subtitle: row.subtitle,
1016
+ text: row.text,
1017
+ narrative: row.narrative,
1018
+ facts: row.facts,
1019
+ concepts: row.concepts,
1020
+ files_read: row.files_read,
1021
+ files_modified: row.files_modified,
1022
+ prompt_number: row.prompt_number,
1023
+ content_hash: row.content_hash,
1024
+ discovery_tokens: row.discovery_tokens ?? 0,
1025
+ auto_category: row.auto_category,
1026
+ created_at: row.created_at,
1027
+ created_at_epoch: row.created_at_epoch
1028
+ };
1029
+ onRow(JSON.stringify(record));
1030
+ total++;
1031
+ }
1032
+ offset += rows.length;
1033
+ if (rows.length < batchSize) break;
1034
+ }
1035
+ return total;
1036
+ }
1037
+ function exportSummariesStreaming(db, filters, onRow, batchSize = 200) {
1038
+ const { fromEpoch, toEpoch } = filtersToEpoch(filters);
1039
+ const conds = buildConditions({ project: filters.project, fromEpoch, toEpoch });
1040
+ let offset = 0;
1041
+ let total = 0;
1042
+ while (true) {
1043
+ const rows = db.query(
1044
+ `SELECT id, session_id, project, request, investigated, learned, completed, next_steps, notes,
1045
+ discovery_tokens, created_at, created_at_epoch
1046
+ FROM summaries
1047
+ WHERE ${conds.where}
1048
+ ORDER BY created_at_epoch ASC, id ASC
1049
+ LIMIT ? OFFSET ?`
1050
+ ).all(...conds.params, batchSize, offset);
1051
+ if (rows.length === 0) break;
1052
+ for (const row of rows) {
1053
+ const record = {
1054
+ _type: "summary",
1055
+ id: row.id,
1056
+ session_id: row.session_id,
1057
+ project: row.project,
1058
+ request: row.request,
1059
+ investigated: row.investigated,
1060
+ learned: row.learned,
1061
+ completed: row.completed,
1062
+ next_steps: row.next_steps,
1063
+ notes: row.notes,
1064
+ discovery_tokens: row.discovery_tokens ?? 0,
1065
+ created_at: row.created_at,
1066
+ created_at_epoch: row.created_at_epoch
1067
+ };
1068
+ onRow(JSON.stringify(record));
1069
+ total++;
1070
+ }
1071
+ offset += rows.length;
1072
+ if (rows.length < batchSize) break;
1073
+ }
1074
+ return total;
1075
+ }
1076
+ function exportPromptsStreaming(db, filters, onRow, batchSize = 200) {
1077
+ const { fromEpoch, toEpoch } = filtersToEpoch(filters);
1078
+ const conds = buildConditions({ project: filters.project, fromEpoch, toEpoch });
1079
+ let offset = 0;
1080
+ let total = 0;
1081
+ while (true) {
1082
+ const rows = db.query(
1083
+ `SELECT id, content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch
1084
+ FROM prompts
1085
+ WHERE ${conds.where}
1086
+ ORDER BY created_at_epoch ASC, id ASC
1087
+ LIMIT ? OFFSET ?`
1088
+ ).all(...conds.params, batchSize, offset);
1089
+ if (rows.length === 0) break;
1090
+ for (const row of rows) {
1091
+ const record = {
1092
+ _type: "prompt",
1093
+ id: row.id,
1094
+ content_session_id: row.content_session_id,
1095
+ project: row.project,
1096
+ prompt_number: row.prompt_number,
1097
+ prompt_text: row.prompt_text,
1098
+ created_at: row.created_at,
1099
+ created_at_epoch: row.created_at_epoch
1100
+ };
1101
+ onRow(JSON.stringify(record));
1102
+ total++;
1103
+ }
1104
+ offset += rows.length;
1105
+ if (rows.length < batchSize) break;
1106
+ }
1107
+ return total;
1108
+ }
1109
+ function validateJsonlRow(raw) {
1110
+ if (!raw || typeof raw !== "object") {
1111
+ return "Il record non \xE8 un oggetto JSON valido";
1112
+ }
1113
+ const rec = raw;
1114
+ if ("_meta" in rec) return null;
1115
+ const validTypes = ["observation", "summary", "prompt"];
1116
+ if (!rec._type || typeof rec._type !== "string" || !validTypes.includes(rec._type)) {
1117
+ return `Campo "_type" obbligatorio, uno di: ${validTypes.join(", ")}`;
1118
+ }
1119
+ if (rec._type === "observation") {
1120
+ if (!rec.project || typeof rec.project !== "string") return 'observation: campo "project" obbligatorio';
1121
+ if (!rec.type || typeof rec.type !== "string") return 'observation: campo "type" obbligatorio';
1122
+ if (!rec.title || typeof rec.title !== "string") return 'observation: campo "title" obbligatorio';
1123
+ if (rec.project.length > 200) return 'observation: "project" troppo lungo (max 200)';
1124
+ if (rec.title.length > 500) return 'observation: "title" troppo lungo (max 500)';
1125
+ } else if (rec._type === "summary") {
1126
+ if (!rec.project || typeof rec.project !== "string") return 'summary: campo "project" obbligatorio';
1127
+ if (!rec.session_id || typeof rec.session_id !== "string") return 'summary: campo "session_id" obbligatorio';
1128
+ } else if (rec._type === "prompt") {
1129
+ if (!rec.project || typeof rec.project !== "string") return 'prompt: campo "project" obbligatorio';
1130
+ if (!rec.content_session_id || typeof rec.content_session_id !== "string") return 'prompt: campo "content_session_id" obbligatorio';
1131
+ if (!rec.prompt_text || typeof rec.prompt_text !== "string") return 'prompt: campo "prompt_text" obbligatorio';
1132
+ }
1133
+ return null;
1134
+ }
1135
+ function computeImportHash(rec) {
1136
+ const payload = [
1137
+ rec.project ?? "",
1138
+ rec.type ?? "",
1139
+ rec.title ?? "",
1140
+ rec.narrative ?? ""
1141
+ ].join("|");
1142
+ return createHash("sha256").update(payload).digest("hex");
1143
+ }
1144
+ function hashExistsInObservations(db, hash) {
1145
+ const result = db.query(
1146
+ "SELECT id FROM observations WHERE content_hash = ? LIMIT 1"
1147
+ ).get(hash);
1148
+ return !!result;
1149
+ }
1150
+ function importObservationBatch(db, records, dryRun) {
1151
+ let imported = 0;
1152
+ let skipped = 0;
1153
+ for (let i = 0; i < records.length; i += IMPORT_BATCH_SIZE) {
1154
+ const batch = records.slice(i, i + IMPORT_BATCH_SIZE);
1155
+ if (dryRun) {
1156
+ for (const rec of batch) {
1157
+ const hash = rec.content_hash || computeImportHash(rec);
1158
+ if (hashExistsInObservations(db, hash)) {
1159
+ skipped++;
1160
+ } else {
1161
+ imported++;
1162
+ }
1163
+ }
1164
+ continue;
1165
+ }
1166
+ const insertBatch = db.transaction(() => {
1167
+ for (const rec of batch) {
1168
+ const hash = rec.content_hash || computeImportHash(rec);
1169
+ if (hashExistsInObservations(db, hash)) {
1170
+ skipped++;
1171
+ continue;
1172
+ }
1173
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1174
+ db.run(
1175
+ `INSERT INTO observations
1176
+ (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts,
1177
+ files_read, files_modified, prompt_number, content_hash, discovery_tokens, auto_category,
1178
+ created_at, created_at_epoch)
1179
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1180
+ [
1181
+ rec.memory_session_id || "imported",
1182
+ rec.project,
1183
+ rec.type,
1184
+ rec.title,
1185
+ rec.subtitle ?? null,
1186
+ rec.text ?? null,
1187
+ rec.narrative ?? null,
1188
+ rec.facts ?? null,
1189
+ rec.concepts ?? null,
1190
+ rec.files_read ?? null,
1191
+ rec.files_modified ?? null,
1192
+ rec.prompt_number ?? 0,
1193
+ hash,
1194
+ rec.discovery_tokens ?? 0,
1195
+ rec.auto_category ?? null,
1196
+ rec.created_at || now,
1197
+ rec.created_at_epoch || Date.now()
1198
+ ]
1199
+ );
1200
+ imported++;
1201
+ }
1202
+ });
1203
+ insertBatch();
1204
+ }
1205
+ return { imported, skipped };
1206
+ }
1207
+ function importSummaryBatch(db, records, dryRun) {
1208
+ let imported = 0;
1209
+ let skipped = 0;
1210
+ for (let i = 0; i < records.length; i += IMPORT_BATCH_SIZE) {
1211
+ const batch = records.slice(i, i + IMPORT_BATCH_SIZE);
1212
+ if (dryRun) {
1213
+ for (const rec of batch) {
1214
+ const exists = db.query(
1215
+ "SELECT id FROM summaries WHERE session_id = ? AND project = ? AND created_at_epoch = ? LIMIT 1"
1216
+ ).get(rec.session_id, rec.project, rec.created_at_epoch ?? 0);
1217
+ if (exists) skipped++;
1218
+ else imported++;
1219
+ }
1220
+ continue;
1221
+ }
1222
+ const insertBatch = db.transaction(() => {
1223
+ for (const rec of batch) {
1224
+ const exists = db.query(
1225
+ "SELECT id FROM summaries WHERE session_id = ? AND project = ? AND created_at_epoch = ? LIMIT 1"
1226
+ ).get(rec.session_id, rec.project, rec.created_at_epoch ?? 0);
1227
+ if (exists) {
1228
+ skipped++;
1229
+ continue;
1230
+ }
1231
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1232
+ db.run(
1233
+ `INSERT INTO summaries
1234
+ (session_id, project, request, investigated, learned, completed, next_steps, notes,
1235
+ discovery_tokens, created_at, created_at_epoch)
1236
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1237
+ [
1238
+ rec.session_id,
1239
+ rec.project,
1240
+ rec.request ?? null,
1241
+ rec.investigated ?? null,
1242
+ rec.learned ?? null,
1243
+ rec.completed ?? null,
1244
+ rec.next_steps ?? null,
1245
+ rec.notes ?? null,
1246
+ rec.discovery_tokens ?? 0,
1247
+ rec.created_at || now,
1248
+ rec.created_at_epoch || Date.now()
1249
+ ]
1250
+ );
1251
+ imported++;
1252
+ }
1253
+ });
1254
+ insertBatch();
1255
+ }
1256
+ return { imported, skipped };
1257
+ }
1258
+ function importPromptBatch(db, records, dryRun) {
1259
+ let imported = 0;
1260
+ let skipped = 0;
1261
+ for (let i = 0; i < records.length; i += IMPORT_BATCH_SIZE) {
1262
+ const batch = records.slice(i, i + IMPORT_BATCH_SIZE);
1263
+ if (dryRun) {
1264
+ for (const rec of batch) {
1265
+ const exists = db.query(
1266
+ "SELECT id FROM prompts WHERE content_session_id = ? AND prompt_number = ? LIMIT 1"
1267
+ ).get(rec.content_session_id, rec.prompt_number ?? 0);
1268
+ if (exists) skipped++;
1269
+ else imported++;
1270
+ }
1271
+ continue;
1272
+ }
1273
+ const insertBatch = db.transaction(() => {
1274
+ for (const rec of batch) {
1275
+ const exists = db.query(
1276
+ "SELECT id FROM prompts WHERE content_session_id = ? AND prompt_number = ? LIMIT 1"
1277
+ ).get(rec.content_session_id, rec.prompt_number ?? 0);
1278
+ if (exists) {
1279
+ skipped++;
1280
+ continue;
1281
+ }
1282
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1283
+ db.run(
1284
+ `INSERT INTO prompts
1285
+ (content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
1286
+ VALUES (?, ?, ?, ?, ?, ?)`,
1287
+ [
1288
+ rec.content_session_id,
1289
+ rec.project,
1290
+ rec.prompt_number ?? 0,
1291
+ rec.prompt_text,
1292
+ rec.created_at || now,
1293
+ rec.created_at_epoch || Date.now()
1294
+ ]
1295
+ );
1296
+ imported++;
1297
+ }
1298
+ });
1299
+ insertBatch();
1300
+ }
1301
+ return { imported, skipped };
1302
+ }
1303
+ function importJsonl(db, content, dryRun = false) {
1304
+ const lines = content.split("\n");
1305
+ const result = {
1306
+ imported: 0,
1307
+ skipped: 0,
1308
+ errors: 0,
1309
+ total: 0,
1310
+ errorDetails: []
1311
+ };
1312
+ const obsBuf = [];
1313
+ const sumBuf = [];
1314
+ const promptBuf = [];
1315
+ const flushBuffers = () => {
1316
+ if (obsBuf.length > 0) {
1317
+ const r = importObservationBatch(db, obsBuf.splice(0), dryRun);
1318
+ result.imported += r.imported;
1319
+ result.skipped += r.skipped;
1320
+ }
1321
+ if (sumBuf.length > 0) {
1322
+ const r = importSummaryBatch(db, sumBuf.splice(0), dryRun);
1323
+ result.imported += r.imported;
1324
+ result.skipped += r.skipped;
1325
+ }
1326
+ if (promptBuf.length > 0) {
1327
+ const r = importPromptBatch(db, promptBuf.splice(0), dryRun);
1328
+ result.imported += r.imported;
1329
+ result.skipped += r.skipped;
1330
+ }
1331
+ };
1332
+ for (let i = 0; i < lines.length; i++) {
1333
+ const raw = lines[i].trim();
1334
+ if (!raw || raw.startsWith("#")) continue;
1335
+ result.total++;
1336
+ let parsed;
1337
+ try {
1338
+ parsed = JSON.parse(raw);
1339
+ } catch {
1340
+ result.errors++;
1341
+ result.errorDetails.push({ line: i + 1, error: `JSON non valido: ${raw.substring(0, 60)}` });
1342
+ continue;
1343
+ }
1344
+ if (parsed && typeof parsed === "object" && "_meta" in parsed) {
1345
+ result.total--;
1346
+ continue;
1347
+ }
1348
+ const validErr = validateJsonlRow(parsed);
1349
+ if (validErr) {
1350
+ result.errors++;
1351
+ result.errorDetails.push({ line: i + 1, error: validErr });
1352
+ continue;
1353
+ }
1354
+ const rec = parsed;
1355
+ if (rec._type === "observation") {
1356
+ obsBuf.push(rec);
1357
+ } else if (rec._type === "summary") {
1358
+ sumBuf.push(rec);
1359
+ } else if (rec._type === "prompt") {
1360
+ promptBuf.push(rec);
1361
+ }
1362
+ const totalBuf = obsBuf.length + sumBuf.length + promptBuf.length;
1363
+ if (totalBuf >= IMPORT_BATCH_SIZE) {
1364
+ flushBuffers();
1365
+ }
1366
+ }
1367
+ flushBuffers();
1368
+ return result;
1369
+ }
1370
+ function filtersToEpoch(filters) {
1371
+ return {
1372
+ fromEpoch: filters.from ? new Date(filters.from).getTime() : void 0,
1373
+ toEpoch: filters.to ? new Date(filters.to).getTime() : void 0
1374
+ };
1375
+ }
1376
+ function buildConditions(params) {
1377
+ const conditions = ["1=1"];
1378
+ const values = [];
1379
+ if (params.project) {
1380
+ conditions.push("project = ?");
1381
+ values.push(params.project);
1382
+ }
1383
+ if (params.type) {
1384
+ conditions.push("type = ?");
1385
+ values.push(params.type);
1386
+ }
1387
+ if (params.fromEpoch !== void 0) {
1388
+ conditions.push("created_at_epoch >= ?");
1389
+ values.push(params.fromEpoch);
1390
+ }
1391
+ if (params.toEpoch !== void 0) {
1392
+ conditions.push("created_at_epoch <= ?");
1393
+ values.push(params.toEpoch);
1394
+ }
1395
+ return { where: conditions.join(" AND "), params: values };
1396
+ }
1397
+ var JSONL_SCHEMA_VERSION, IMPORT_BATCH_SIZE;
1398
+ var init_ImportExport = __esm({
1399
+ "src/services/sqlite/ImportExport.ts"() {
1400
+ "use strict";
1401
+ JSONL_SCHEMA_VERSION = "2.5.0";
1402
+ IMPORT_BATCH_SIZE = 100;
1403
+ }
1404
+ });
1405
+
627
1406
  // src/services/search/EmbeddingService.ts
628
1407
  var EmbeddingService_exports = {};
629
1408
  __export(EmbeddingService_exports, {
@@ -636,19 +1415,53 @@ function getEmbeddingService() {
636
1415
  }
637
1416
  return embeddingService;
638
1417
  }
639
- var EmbeddingService, embeddingService;
1418
+ var MODEL_CONFIGS, FASTEMBED_COMPATIBLE_MODELS, EmbeddingService, embeddingService;
640
1419
  var init_EmbeddingService = __esm({
641
1420
  "src/services/search/EmbeddingService.ts"() {
642
1421
  "use strict";
643
1422
  init_logger();
1423
+ MODEL_CONFIGS = {
1424
+ "all-MiniLM-L6-v2": {
1425
+ modelId: "Xenova/all-MiniLM-L6-v2",
1426
+ dimensions: 384
1427
+ },
1428
+ "jina-code-v2": {
1429
+ modelId: "jinaai/jina-embeddings-v2-base-code",
1430
+ dimensions: 768
1431
+ },
1432
+ "bge-small-en": {
1433
+ modelId: "BAAI/bge-small-en-v1.5",
1434
+ dimensions: 384
1435
+ }
1436
+ };
1437
+ FASTEMBED_COMPATIBLE_MODELS = /* @__PURE__ */ new Set(["all-MiniLM-L6-v2", "bge-small-en"]);
644
1438
  EmbeddingService = class {
645
1439
  provider = null;
646
1440
  model = null;
647
1441
  initialized = false;
648
1442
  initializing = null;
1443
+ config;
1444
+ configName;
1445
+ constructor() {
1446
+ const envModel = process.env.KIRO_MEMORY_EMBEDDING_MODEL || "all-MiniLM-L6-v2";
1447
+ this.configName = envModel;
1448
+ if (MODEL_CONFIGS[envModel]) {
1449
+ this.config = MODEL_CONFIGS[envModel];
1450
+ } else if (envModel.includes("/")) {
1451
+ const dimensions = parseInt(process.env.KIRO_MEMORY_EMBEDDING_DIMENSIONS || "384", 10);
1452
+ this.config = {
1453
+ modelId: envModel,
1454
+ dimensions: isNaN(dimensions) ? 384 : dimensions
1455
+ };
1456
+ } else {
1457
+ logger.warn("EMBEDDING", `Unknown model name '${envModel}', falling back to 'all-MiniLM-L6-v2'`);
1458
+ this.configName = "all-MiniLM-L6-v2";
1459
+ this.config = MODEL_CONFIGS["all-MiniLM-L6-v2"];
1460
+ }
1461
+ }
649
1462
  /**
650
- * Inizializza il servizio di embedding.
651
- * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
1463
+ * Initialize the embedding service.
1464
+ * Tries fastembed (when compatible), then @huggingface/transformers, then falls back to null.
652
1465
  */
653
1466
  async initialize() {
654
1467
  if (this.initialized) return this.provider !== null;
@@ -659,45 +1472,48 @@ var init_EmbeddingService = __esm({
659
1472
  return result;
660
1473
  }
661
1474
  async _doInitialize() {
662
- try {
663
- const fastembed = await import("fastembed");
664
- const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
665
- const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
666
- if (FlagEmbedding && EmbeddingModel) {
667
- this.model = await FlagEmbedding.init({
668
- model: EmbeddingModel.BGESmallENV15
669
- });
670
- this.provider = "fastembed";
671
- this.initialized = true;
672
- logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
673
- return true;
1475
+ const fastembedCompatible = FASTEMBED_COMPATIBLE_MODELS.has(this.configName);
1476
+ if (fastembedCompatible) {
1477
+ try {
1478
+ const fastembed = await import("fastembed");
1479
+ const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1480
+ const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1481
+ if (FlagEmbedding && EmbeddingModel) {
1482
+ this.model = await FlagEmbedding.init({
1483
+ model: EmbeddingModel.BGESmallENV15
1484
+ });
1485
+ this.provider = "fastembed";
1486
+ this.initialized = true;
1487
+ logger.info("EMBEDDING", `Initialized with fastembed (BGE-small-en-v1.5) for model '${this.configName}'`);
1488
+ return true;
1489
+ }
1490
+ } catch (error) {
1491
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
674
1492
  }
675
- } catch (error) {
676
- logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
677
1493
  }
678
1494
  try {
679
1495
  const transformers = await import("@huggingface/transformers");
680
1496
  const pipeline = transformers.pipeline || transformers.default?.pipeline;
681
1497
  if (pipeline) {
682
- this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
1498
+ this.model = await pipeline("feature-extraction", this.config.modelId, {
683
1499
  quantized: true
684
1500
  });
685
1501
  this.provider = "transformers";
686
1502
  this.initialized = true;
687
- logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
1503
+ logger.info("EMBEDDING", `Initialized with @huggingface/transformers (${this.config.modelId})`);
688
1504
  return true;
689
1505
  }
690
1506
  } catch (error) {
691
- logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
1507
+ logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
692
1508
  }
693
1509
  this.provider = null;
694
1510
  this.initialized = true;
695
- logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
1511
+ logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
696
1512
  return false;
697
1513
  }
698
1514
  /**
699
- * Genera embedding per un singolo testo.
700
- * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
1515
+ * Generate embedding for a single text.
1516
+ * Returns Float32Array with configured dimensions, or null if not available.
701
1517
  */
702
1518
  async embed(text) {
703
1519
  if (!this.initialized) await this.initialize();
@@ -710,46 +1526,118 @@ var init_EmbeddingService = __esm({
710
1526
  return await this._embedTransformers(truncated);
711
1527
  }
712
1528
  } catch (error) {
713
- logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
1529
+ logger.error("EMBEDDING", `Error generating embedding: ${error}`);
714
1530
  }
715
1531
  return null;
716
1532
  }
717
1533
  /**
718
- * Genera embeddings in batch.
1534
+ * Generate embeddings in batch.
1535
+ * Uses native batch support when available (fastembed, transformers),
1536
+ * falls back to serial processing on batch failure.
719
1537
  */
720
1538
  async embedBatch(texts) {
721
1539
  if (!this.initialized) await this.initialize();
722
1540
  if (!this.provider || !this.model) return texts.map(() => null);
723
- const results = [];
724
- for (const text of texts) {
725
- try {
726
- const embedding = await this.embed(text);
727
- results.push(embedding);
728
- } catch {
729
- results.push(null);
1541
+ if (texts.length === 0) return [];
1542
+ const truncated = texts.map((t) => t.substring(0, 2e3));
1543
+ try {
1544
+ if (this.provider === "fastembed") {
1545
+ return await this._embedBatchFastembed(truncated);
1546
+ } else if (this.provider === "transformers") {
1547
+ return await this._embedBatchTransformers(truncated);
730
1548
  }
1549
+ } catch (error) {
1550
+ logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
731
1551
  }
732
- return results;
1552
+ return this._embedBatchSerial(truncated);
733
1553
  }
734
1554
  /**
735
- * Verifica se il servizio è disponibile.
1555
+ * Check if the service is available.
736
1556
  */
737
1557
  isAvailable() {
738
1558
  return this.initialized && this.provider !== null;
739
1559
  }
740
1560
  /**
741
- * Nome del provider attivo.
1561
+ * Name of the active provider.
742
1562
  */
743
1563
  getProvider() {
744
1564
  return this.provider;
745
1565
  }
746
1566
  /**
747
- * Dimensioni del vettore embedding.
1567
+ * Embedding vector dimensions for the active model configuration.
748
1568
  */
749
1569
  getDimensions() {
750
- return 384;
1570
+ return this.config.dimensions;
1571
+ }
1572
+ /**
1573
+ * Human-readable model name used as identifier in the observation_embeddings table.
1574
+ * Returns the short name (e.g., 'all-MiniLM-L6-v2') or the full HF model ID for custom models.
1575
+ */
1576
+ getModelName() {
1577
+ return this.configName;
1578
+ }
1579
+ // --- Batch implementations ---
1580
+ /**
1581
+ * Native batch embedding with fastembed.
1582
+ * FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
1583
+ */
1584
+ async _embedBatchFastembed(texts) {
1585
+ const results = [];
1586
+ const embeddings = this.model.embed(texts, texts.length);
1587
+ for await (const batch of embeddings) {
1588
+ if (batch) {
1589
+ for (const vec of batch) {
1590
+ results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
1591
+ }
1592
+ }
1593
+ }
1594
+ while (results.length < texts.length) {
1595
+ results.push(null);
1596
+ }
1597
+ return results;
1598
+ }
1599
+ /**
1600
+ * Batch embedding with @huggingface/transformers pipeline.
1601
+ * The pipeline accepts string[] and returns a Tensor with shape [N, dims].
1602
+ */
1603
+ async _embedBatchTransformers(texts) {
1604
+ const output = await this.model(texts, {
1605
+ pooling: "mean",
1606
+ normalize: true
1607
+ });
1608
+ if (!output?.data) {
1609
+ return texts.map(() => null);
1610
+ }
1611
+ const dims = this.getDimensions();
1612
+ const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
1613
+ const results = [];
1614
+ for (let i = 0; i < texts.length; i++) {
1615
+ const offset = i * dims;
1616
+ if (offset + dims <= data.length) {
1617
+ results.push(data.slice(offset, offset + dims));
1618
+ } else {
1619
+ results.push(null);
1620
+ }
1621
+ }
1622
+ return results;
751
1623
  }
752
- // --- Provider specifici ---
1624
+ /**
1625
+ * Serial fallback: embed texts one at a time.
1626
+ * Used when native batch fails.
1627
+ */
1628
+ async _embedBatchSerial(texts) {
1629
+ const results = [];
1630
+ for (const text of texts) {
1631
+ try {
1632
+ const embedding = await this.embed(text);
1633
+ results.push(embedding);
1634
+ } catch {
1635
+ results.push(null);
1636
+ }
1637
+ }
1638
+ return results;
1639
+ }
1640
+ // --- Single-text provider implementations ---
753
1641
  async _embedFastembed(text) {
754
1642
  const embeddings = this.model.embed([text], 1);
755
1643
  for await (const batch of embeddings) {
@@ -775,18 +1663,334 @@ var init_EmbeddingService = __esm({
775
1663
  }
776
1664
  });
777
1665
 
1666
+ // src/cli/cli-utils.ts
1667
+ var cli_utils_exports = {};
1668
+ __export(cli_utils_exports, {
1669
+ CONFIG_DEFAULTS: () => CONFIG_DEFAULTS,
1670
+ buildProgressBar: () => buildProgressBar,
1671
+ checkFtsIntegrity: () => checkFtsIntegrity,
1672
+ formatBytes: () => formatBytes,
1673
+ formatImportResult: () => formatImportResult,
1674
+ formatStatsOutput: () => formatStatsOutput,
1675
+ generateExportOutput: () => generateExportOutput,
1676
+ generateJsonOutput: () => generateJsonOutput,
1677
+ generateJsonlOutput: () => generateJsonlOutput,
1678
+ generateMarkdownOutput: () => generateMarkdownOutput,
1679
+ getConfigPath: () => getConfigPath,
1680
+ getConfigValue: () => getConfigValue,
1681
+ getDbFileSize: () => getDbFileSize,
1682
+ listConfig: () => listConfig,
1683
+ observationToJsonl: () => observationToJsonl,
1684
+ observationToMarkdown: () => observationToMarkdown,
1685
+ parseJsonlFile: () => parseJsonlFile,
1686
+ readConfig: () => readConfig,
1687
+ rebuildFtsIndex: () => rebuildFtsIndex,
1688
+ removeOrphanedEmbeddings: () => removeOrphanedEmbeddings,
1689
+ setConfigValue: () => setConfigValue,
1690
+ vacuumDatabase: () => vacuumDatabase,
1691
+ validateImportRecord: () => validateImportRecord,
1692
+ writeConfig: () => writeConfig
1693
+ });
1694
+ import { existsSync as existsSync5, statSync as statSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
1695
+ import { join as join4 } from "path";
1696
+ import { homedir as homedir3 } from "os";
1697
+ function observationToJsonl(obs) {
1698
+ return JSON.stringify(obs);
1699
+ }
1700
+ function generateJsonlOutput(observations) {
1701
+ return observations.map(observationToJsonl).join("\n");
1702
+ }
1703
+ function generateJsonOutput(observations) {
1704
+ return JSON.stringify(observations, null, 2);
1705
+ }
1706
+ function observationToMarkdown(obs) {
1707
+ const date = new Date(obs.created_at).toLocaleDateString("it-IT", {
1708
+ year: "numeric",
1709
+ month: "long",
1710
+ day: "numeric"
1711
+ });
1712
+ const lines = [
1713
+ `## ${obs.title}`,
1714
+ "",
1715
+ `- **Tipo:** ${obs.type}`,
1716
+ `- **Progetto:** ${obs.project}`,
1717
+ `- **Data:** ${date}`
1718
+ ];
1719
+ if (obs.subtitle) lines.push(`- **Sottotitolo:** ${obs.subtitle}`);
1720
+ if (obs.files_modified) lines.push(`- **File modificati:** ${obs.files_modified}`);
1721
+ if (obs.files_read) lines.push(`- **File letti:** ${obs.files_read}`);
1722
+ if (obs.text) {
1723
+ lines.push("", "### Contenuto", "", obs.text);
1724
+ }
1725
+ if (obs.narrative) {
1726
+ lines.push("", "### Narrativa", "", obs.narrative);
1727
+ }
1728
+ if (obs.facts) {
1729
+ lines.push("", "### Fatti", "", obs.facts);
1730
+ }
1731
+ lines.push("");
1732
+ return lines.join("\n");
1733
+ }
1734
+ function generateMarkdownOutput(observations) {
1735
+ if (observations.length === 0) return "# Nessuna observation trovata\n";
1736
+ const header = [
1737
+ "# Kiro Memory \u2014 Export Observations",
1738
+ "",
1739
+ `> Progetto: ${observations[0].project} | Totale: ${observations.length}`,
1740
+ "",
1741
+ "---",
1742
+ ""
1743
+ ].join("\n");
1744
+ return header + observations.map(observationToMarkdown).join("\n---\n\n");
1745
+ }
1746
+ function generateExportOutput(observations, format) {
1747
+ switch (format) {
1748
+ case "jsonl":
1749
+ return generateJsonlOutput(observations);
1750
+ case "json":
1751
+ return generateJsonOutput(observations);
1752
+ case "md":
1753
+ return generateMarkdownOutput(observations);
1754
+ }
1755
+ }
1756
+ function validateImportRecord(raw) {
1757
+ if (!raw || typeof raw !== "object") {
1758
+ return "Record non \xE8 un oggetto JSON valido";
1759
+ }
1760
+ const rec = raw;
1761
+ if (!rec.project || typeof rec.project !== "string" || rec.project.trim() === "") {
1762
+ return 'Campo "project" obbligatorio (stringa non vuota)';
1763
+ }
1764
+ if (!rec.type || typeof rec.type !== "string" || rec.type.trim() === "") {
1765
+ return 'Campo "type" obbligatorio (stringa non vuota)';
1766
+ }
1767
+ if (!rec.title || typeof rec.title !== "string" || rec.title.trim() === "") {
1768
+ return 'Campo "title" obbligatorio (stringa non vuota)';
1769
+ }
1770
+ if (rec.project.length > 200) return '"project" troppo lungo (max 200 caratteri)';
1771
+ if (rec.type.length > 100) return '"type" troppo lungo (max 100 caratteri)';
1772
+ if (rec.title.length > 500) return '"title" troppo lungo (max 500 caratteri)';
1773
+ for (const field of ["subtitle", "text", "narrative", "facts", "concepts", "files_read", "files_modified", "content_hash"]) {
1774
+ const val = rec[field];
1775
+ if (val !== void 0 && val !== null && typeof val !== "string") {
1776
+ return `Campo "${field}" deve essere stringa o null`;
1777
+ }
1778
+ }
1779
+ return null;
1780
+ }
1781
+ function parseJsonlFile(content) {
1782
+ const lines = content.split("\n");
1783
+ const results = [];
1784
+ for (let i = 0; i < lines.length; i++) {
1785
+ const raw = lines[i].trim();
1786
+ if (!raw || raw.startsWith("#")) continue;
1787
+ let parsed;
1788
+ try {
1789
+ parsed = JSON.parse(raw);
1790
+ } catch {
1791
+ results.push({ line: i + 1, error: `JSON non valido: ${raw.substring(0, 50)}` });
1792
+ continue;
1793
+ }
1794
+ const validationError = validateImportRecord(parsed);
1795
+ if (validationError) {
1796
+ results.push({ line: i + 1, error: validationError });
1797
+ continue;
1798
+ }
1799
+ results.push({ line: i + 1, record: parsed });
1800
+ }
1801
+ return results;
1802
+ }
1803
+ function getConfigPath() {
1804
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join4(homedir3(), ".contextkit");
1805
+ return join4(dataDir, "config.json");
1806
+ }
1807
+ function readConfig(configPath) {
1808
+ const path = configPath || getConfigPath();
1809
+ if (!existsSync5(path)) return {};
1810
+ try {
1811
+ const raw = readFileSync3(path, "utf8");
1812
+ const parsed = JSON.parse(raw);
1813
+ if (typeof parsed === "object" && parsed !== null) return parsed;
1814
+ return {};
1815
+ } catch {
1816
+ return {};
1817
+ }
1818
+ }
1819
+ function writeConfig(config, configPath) {
1820
+ const path = configPath || getConfigPath();
1821
+ const dir = path.substring(0, path.lastIndexOf("/"));
1822
+ mkdirSync4(dir, { recursive: true });
1823
+ writeFileSync2(path, JSON.stringify(config, null, 2), "utf8");
1824
+ }
1825
+ function getConfigValue(key, configPath) {
1826
+ const config = readConfig(configPath);
1827
+ if (key in config) return config[key];
1828
+ if (key in CONFIG_DEFAULTS) return CONFIG_DEFAULTS[key];
1829
+ return null;
1830
+ }
1831
+ function setConfigValue(key, rawValue, configPath) {
1832
+ const config = readConfig(configPath);
1833
+ let value = rawValue;
1834
+ if (rawValue === "true") value = true;
1835
+ else if (rawValue === "false") value = false;
1836
+ else {
1837
+ const num = Number(rawValue);
1838
+ if (!isNaN(num) && rawValue.trim() !== "") value = num;
1839
+ }
1840
+ config[key] = value;
1841
+ writeConfig(config, configPath);
1842
+ return value;
1843
+ }
1844
+ function listConfig(configPath) {
1845
+ const config = readConfig(configPath);
1846
+ const merged = { ...CONFIG_DEFAULTS };
1847
+ for (const [k, v] of Object.entries(config)) {
1848
+ merged[k] = v;
1849
+ }
1850
+ return merged;
1851
+ }
1852
+ function formatBytes(bytes) {
1853
+ if (bytes < 1024) return `${bytes} B`;
1854
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1855
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1856
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
1857
+ }
1858
+ function getDbFileSize(dbPath) {
1859
+ try {
1860
+ if (!existsSync5(dbPath)) return 0;
1861
+ return statSync3(dbPath).size;
1862
+ } catch {
1863
+ return 0;
1864
+ }
1865
+ }
1866
+ function formatStatsOutput(stats) {
1867
+ const lines = [
1868
+ "",
1869
+ "=== Kiro Memory \u2014 Statistiche Database ===",
1870
+ "",
1871
+ ` Observations totali: ${stats.totalObservations}`,
1872
+ ` Sessioni totali: ${stats.totalSessions}`,
1873
+ ` Progetti distinti: ${stats.totalProjects}`,
1874
+ ` Dimensione DB: ${formatBytes(stats.dbSizeBytes)}`
1875
+ ];
1876
+ if (stats.mostActiveProject) {
1877
+ lines.push(` Progetto piu' attivo: ${stats.mostActiveProject}`);
1878
+ }
1879
+ const coverage = stats.embeddingCoverage;
1880
+ const coverageBar = buildProgressBar(coverage, 20);
1881
+ lines.push(` Copertura embeddings: ${coverageBar} ${coverage}%`);
1882
+ lines.push("");
1883
+ return lines.join("\n");
1884
+ }
1885
+ function buildProgressBar(percent, width = 20) {
1886
+ const filled = Math.round(percent / 100 * width);
1887
+ const empty = width - filled;
1888
+ return `[${"#".repeat(filled)}${"-".repeat(empty)}]`;
1889
+ }
1890
+ function formatImportResult(result) {
1891
+ const prefix = result.dryRun ? "[DRY RUN] " : "";
1892
+ const lines = [
1893
+ "",
1894
+ `=== ${prefix}Kiro Memory \u2014 Import JSONL ===`,
1895
+ "",
1896
+ ` Record totali analizzati: ${result.total}`,
1897
+ ` Importati: ${result.imported}`,
1898
+ ` Saltati (duplicati): ${result.skipped}`,
1899
+ ` Errori di validazione: ${result.errors}`
1900
+ ];
1901
+ if (result.dryRun) {
1902
+ lines.push("");
1903
+ lines.push(" (Dry run: nessun dato inserito. Rimuovi --dry-run per applicare.)");
1904
+ }
1905
+ if (result.errorDetails && result.errorDetails.length > 0) {
1906
+ lines.push("");
1907
+ lines.push(" Errori:");
1908
+ for (const err of result.errorDetails.slice(0, 20)) {
1909
+ lines.push(` Riga ${err.line}: ${err.error}`);
1910
+ }
1911
+ if (result.errorDetails.length > 20) {
1912
+ lines.push(` ... e altri ${result.errorDetails.length - 20} errori`);
1913
+ }
1914
+ }
1915
+ lines.push("");
1916
+ return lines.join("\n");
1917
+ }
1918
+ function checkFtsIntegrity(db) {
1919
+ try {
1920
+ db.query("INSERT INTO observations_fts(observations_fts) VALUES('integrity-check')").run();
1921
+ return true;
1922
+ } catch {
1923
+ return false;
1924
+ }
1925
+ }
1926
+ function rebuildFtsIndex(db) {
1927
+ try {
1928
+ db.run("INSERT INTO observations_fts(observations_fts) VALUES('rebuild')");
1929
+ return true;
1930
+ } catch {
1931
+ return false;
1932
+ }
1933
+ }
1934
+ function removeOrphanedEmbeddings(db) {
1935
+ try {
1936
+ const result = db.run(
1937
+ `DELETE FROM observation_embeddings
1938
+ WHERE observation_id NOT IN (SELECT id FROM observations)`
1939
+ );
1940
+ return Number(result.changes);
1941
+ } catch {
1942
+ return 0;
1943
+ }
1944
+ }
1945
+ function vacuumDatabase(db) {
1946
+ try {
1947
+ db.run("VACUUM");
1948
+ return true;
1949
+ } catch {
1950
+ return false;
1951
+ }
1952
+ }
1953
+ var CONFIG_DEFAULTS;
1954
+ var init_cli_utils = __esm({
1955
+ "src/cli/cli-utils.ts"() {
1956
+ "use strict";
1957
+ CONFIG_DEFAULTS = {
1958
+ "worker.port": 3001,
1959
+ "worker.host": "127.0.0.1",
1960
+ "log.level": "INFO",
1961
+ "search.limit": 20,
1962
+ "embeddings.enabled": false,
1963
+ "decay.staleThresholdDays": 30,
1964
+ // Politiche di retention: età massima in giorni (0 = mai eliminare)
1965
+ "retention.observations.maxAgeDays": 90,
1966
+ "retention.summaries.maxAgeDays": 365,
1967
+ "retention.prompts.maxAgeDays": 30,
1968
+ "retention.knowledge.maxAgeDays": 0,
1969
+ // Cleanup automatico schedulato
1970
+ "retention.autoCleanupEnabled": true,
1971
+ "retention.autoCleanupIntervalHours": 24,
1972
+ // Backup automatico schedulato
1973
+ "backup.enabled": true,
1974
+ "backup.intervalHours": 24,
1975
+ "backup.maxKeep": 7,
1976
+ "backup.compress": false
1977
+ };
1978
+ }
1979
+ });
1980
+
778
1981
  // src/shims/bun-sqlite.ts
779
1982
  import BetterSqlite3 from "better-sqlite3";
780
1983
  var Database = class {
781
1984
  _db;
1985
+ _stmtCache = /* @__PURE__ */ new Map();
782
1986
  constructor(path, options) {
783
1987
  this._db = new BetterSqlite3(path, {
784
- // better-sqlite3 crea il file di default (non serve 'create')
1988
+ // better-sqlite3 creates the file by default ('create' not needed)
785
1989
  readonly: options?.readwrite === false ? true : false
786
1990
  });
787
1991
  }
788
1992
  /**
789
- * Esegui una query SQL senza risultati
1993
+ * Execute a SQL query without results
790
1994
  */
791
1995
  run(sql, params) {
792
1996
  const stmt = this._db.prepare(sql);
@@ -794,51 +1998,53 @@ var Database = class {
794
1998
  return result;
795
1999
  }
796
2000
  /**
797
- * Prepara una query con interfaccia compatibile bun:sqlite
2001
+ * Prepare a query with bun:sqlite-compatible interface.
2002
+ * Returns a cached prepared statement for repeated queries.
798
2003
  */
799
2004
  query(sql) {
800
- return new BunQueryCompat(this._db, sql);
2005
+ let cached = this._stmtCache.get(sql);
2006
+ if (!cached) {
2007
+ cached = new BunQueryCompat(this._db, sql);
2008
+ this._stmtCache.set(sql, cached);
2009
+ }
2010
+ return cached;
801
2011
  }
802
2012
  /**
803
- * Crea una transazione
2013
+ * Create a transaction
804
2014
  */
805
2015
  transaction(fn) {
806
2016
  return this._db.transaction(fn);
807
2017
  }
808
2018
  /**
809
- * Chiudi la connessione
2019
+ * Close the connection
810
2020
  */
811
2021
  close() {
2022
+ this._stmtCache.clear();
812
2023
  this._db.close();
813
2024
  }
814
2025
  };
815
2026
  var BunQueryCompat = class {
816
- _db;
817
- _sql;
2027
+ _stmt;
818
2028
  constructor(db, sql) {
819
- this._db = db;
820
- this._sql = sql;
2029
+ this._stmt = db.prepare(sql);
821
2030
  }
822
2031
  /**
823
- * Restituisce tutte le righe
2032
+ * Returns all rows
824
2033
  */
825
2034
  all(...params) {
826
- const stmt = this._db.prepare(this._sql);
827
- return params.length > 0 ? stmt.all(...params) : stmt.all();
2035
+ return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
828
2036
  }
829
2037
  /**
830
- * Restituisce la prima riga o null
2038
+ * Returns the first row or null
831
2039
  */
832
2040
  get(...params) {
833
- const stmt = this._db.prepare(this._sql);
834
- return params.length > 0 ? stmt.get(...params) : stmt.get();
2041
+ return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
835
2042
  }
836
2043
  /**
837
- * Esegui senza risultati
2044
+ * Execute without results
838
2045
  */
839
2046
  run(...params) {
840
- const stmt = this._db.prepare(this._sql);
841
- return params.length > 0 ? stmt.run(...params) : stmt.run();
2047
+ return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
842
2048
  }
843
2049
  };
844
2050
 
@@ -881,40 +2087,62 @@ init_logger();
881
2087
  var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
882
2088
  var SQLITE_CACHE_SIZE_PAGES = 1e4;
883
2089
  var KiroMemoryDatabase = class {
884
- db;
2090
+ _db;
2091
+ /**
2092
+ * Readonly accessor for the underlying Database instance.
2093
+ * Prefer using query() and run() proxy methods directly.
2094
+ */
2095
+ get db() {
2096
+ return this._db;
2097
+ }
885
2098
  /**
886
- * @param dbPath - Percorso al file SQLite (default: DB_PATH)
887
- * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
2099
+ * @param dbPath - Path to the SQLite file (default: DB_PATH)
2100
+ * @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
888
2101
  */
889
2102
  constructor(dbPath = DB_PATH, skipMigrations = false) {
890
2103
  if (dbPath !== ":memory:") {
891
2104
  ensureDir(DATA_DIR);
892
2105
  }
893
- this.db = new Database(dbPath, { create: true, readwrite: true });
894
- this.db.run("PRAGMA journal_mode = WAL");
895
- this.db.run("PRAGMA synchronous = NORMAL");
896
- this.db.run("PRAGMA foreign_keys = ON");
897
- this.db.run("PRAGMA temp_store = memory");
898
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
899
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
2106
+ this._db = new Database(dbPath, { create: true, readwrite: true });
2107
+ this._db.run("PRAGMA journal_mode = WAL");
2108
+ this._db.run("PRAGMA busy_timeout = 5000");
2109
+ this._db.run("PRAGMA synchronous = NORMAL");
2110
+ this._db.run("PRAGMA foreign_keys = ON");
2111
+ this._db.run("PRAGMA temp_store = memory");
2112
+ this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
2113
+ this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
900
2114
  if (!skipMigrations) {
901
- const migrationRunner = new MigrationRunner(this.db);
2115
+ const migrationRunner = new MigrationRunner(this._db);
902
2116
  migrationRunner.runAllMigrations();
903
2117
  }
904
2118
  }
905
2119
  /**
906
- * Esegue una funzione all'interno di una transazione atomica.
907
- * Se fn() lancia un errore, la transazione viene annullata automaticamente.
2120
+ * Prepare a query (delegates to underlying Database).
2121
+ * Proxy method to avoid ctx.db.db.query() double access.
2122
+ */
2123
+ query(sql) {
2124
+ return this._db.query(sql);
2125
+ }
2126
+ /**
2127
+ * Execute a SQL statement without results (delegates to underlying Database).
2128
+ * Proxy method to avoid ctx.db.db.run() double access.
2129
+ */
2130
+ run(sql, params) {
2131
+ return this._db.run(sql, params);
2132
+ }
2133
+ /**
2134
+ * Executes a function within an atomic transaction.
2135
+ * If fn() throws an error, the transaction is automatically rolled back.
908
2136
  */
909
2137
  withTransaction(fn) {
910
- const transaction = this.db.transaction(fn);
911
- return transaction(this.db);
2138
+ const transaction = this._db.transaction(fn);
2139
+ return transaction(this._db);
912
2140
  }
913
2141
  /**
914
2142
  * Close the database connection
915
2143
  */
916
2144
  close() {
917
- this.db.close();
2145
+ this._db.close();
918
2146
  }
919
2147
  };
920
2148
  var MigrationRunner = class {
@@ -1155,11 +2383,104 @@ var MigrationRunner = class {
1155
2383
  db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
1156
2384
  db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
1157
2385
  }
2386
+ },
2387
+ {
2388
+ version: 10,
2389
+ up: (db) => {
2390
+ db.run(`
2391
+ CREATE TABLE IF NOT EXISTS job_queue (
2392
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2393
+ type TEXT NOT NULL,
2394
+ status TEXT NOT NULL DEFAULT 'pending',
2395
+ payload TEXT,
2396
+ result TEXT,
2397
+ error TEXT,
2398
+ retry_count INTEGER DEFAULT 0,
2399
+ max_retries INTEGER DEFAULT 3,
2400
+ priority INTEGER DEFAULT 0,
2401
+ created_at TEXT NOT NULL,
2402
+ created_at_epoch INTEGER NOT NULL,
2403
+ started_at_epoch INTEGER,
2404
+ completed_at_epoch INTEGER
2405
+ )
2406
+ `);
2407
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_status ON job_queue(status)");
2408
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_type ON job_queue(type)");
2409
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_priority ON job_queue(status, priority DESC, created_at_epoch ASC)");
2410
+ }
2411
+ },
2412
+ {
2413
+ version: 11,
2414
+ up: (db) => {
2415
+ db.run("ALTER TABLE observations ADD COLUMN auto_category TEXT");
2416
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(auto_category)");
2417
+ }
2418
+ },
2419
+ {
2420
+ version: 12,
2421
+ up: (db) => {
2422
+ db.run(`
2423
+ CREATE TABLE IF NOT EXISTS github_links (
2424
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2425
+ observation_id INTEGER,
2426
+ session_id TEXT,
2427
+ repo TEXT NOT NULL,
2428
+ issue_number INTEGER,
2429
+ pr_number INTEGER,
2430
+ event_type TEXT NOT NULL,
2431
+ action TEXT,
2432
+ title TEXT,
2433
+ url TEXT,
2434
+ author TEXT,
2435
+ created_at TEXT NOT NULL,
2436
+ created_at_epoch INTEGER NOT NULL,
2437
+ FOREIGN KEY (observation_id) REFERENCES observations(id)
2438
+ )
2439
+ `);
2440
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo ON github_links(repo)");
2441
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_obs ON github_links(observation_id)");
2442
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_event ON github_links(event_type)");
2443
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_issue ON github_links(repo, issue_number)");
2444
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_pr ON github_links(repo, pr_number)");
2445
+ }
2446
+ },
2447
+ {
2448
+ version: 13,
2449
+ up: (db) => {
2450
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_keyset ON observations(created_at_epoch DESC, id DESC)");
2451
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_keyset ON observations(project, created_at_epoch DESC, id DESC)");
2452
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_keyset ON summaries(created_at_epoch DESC, id DESC)");
2453
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_keyset ON summaries(project, created_at_epoch DESC, id DESC)");
2454
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_keyset ON prompts(created_at_epoch DESC, id DESC)");
2455
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_keyset ON prompts(project, created_at_epoch DESC, id DESC)");
2456
+ }
1158
2457
  }
1159
2458
  ];
1160
2459
  }
1161
2460
  };
1162
2461
 
2462
+ // src/services/sqlite/cursor.ts
2463
+ function encodeCursor(id, epoch) {
2464
+ const raw = `${epoch}:${id}`;
2465
+ return Buffer.from(raw, "utf8").toString("base64url");
2466
+ }
2467
+ function decodeCursor(cursor) {
2468
+ try {
2469
+ const raw = Buffer.from(cursor, "base64url").toString("utf8");
2470
+ const colonIdx = raw.indexOf(":");
2471
+ if (colonIdx === -1) return null;
2472
+ const epochStr = raw.substring(0, colonIdx);
2473
+ const idStr = raw.substring(colonIdx + 1);
2474
+ const epoch = parseInt(epochStr, 10);
2475
+ const id = parseInt(idStr, 10);
2476
+ if (!Number.isInteger(epoch) || epoch <= 0) return null;
2477
+ if (!Number.isInteger(id) || id <= 0) return null;
2478
+ return { epoch, id };
2479
+ } catch {
2480
+ return null;
2481
+ }
2482
+ }
2483
+
1163
2484
  // src/services/sqlite/Sessions.ts
1164
2485
  function createSession(db, contentSessionId, project, userPrompt) {
1165
2486
  const now = /* @__PURE__ */ new Date();
@@ -1191,28 +2512,28 @@ init_Observations();
1191
2512
  function escapeLikePattern2(input) {
1192
2513
  return input.replace(/[%_\\]/g, "\\$&");
1193
2514
  }
1194
- function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
2515
+ function createSummary(db, sessionId, project, request2, investigated, learned, completed, nextSteps, notes) {
1195
2516
  const now = /* @__PURE__ */ new Date();
1196
2517
  const result = db.run(
1197
2518
  `INSERT INTO summaries
1198
2519
  (session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
1199
2520
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1200
- [sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
2521
+ [sessionId, project, request2, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
1201
2522
  );
1202
2523
  return Number(result.lastInsertRowid);
1203
2524
  }
1204
2525
  function getSummariesByProject(db, project, limit = 50) {
1205
2526
  const query = db.query(
1206
- "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
2527
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1207
2528
  );
1208
2529
  return query.all(project, limit);
1209
2530
  }
1210
2531
  function searchSummaries(db, searchTerm, project) {
1211
2532
  const sql = project ? `SELECT * FROM summaries
1212
2533
  WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1213
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
2534
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
1214
2535
  WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1215
- ORDER BY created_at_epoch DESC`;
2536
+ ORDER BY created_at_epoch DESC, id DESC`;
1216
2537
  const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1217
2538
  const query = db.query(sql);
1218
2539
  if (project) {
@@ -1234,7 +2555,7 @@ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1234
2555
  }
1235
2556
  function getPromptsByProject(db, project, limit = 100) {
1236
2557
  const query = db.query(
1237
- "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
2558
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1238
2559
  );
1239
2560
  return query.all(project, limit);
1240
2561
  }
@@ -1262,13 +2583,13 @@ function createCheckpoint(db, sessionId, project, data) {
1262
2583
  }
1263
2584
  function getLatestCheckpoint(db, sessionId) {
1264
2585
  const query = db.query(
1265
- "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
2586
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1266
2587
  );
1267
2588
  return query.get(sessionId);
1268
2589
  }
1269
2590
  function getLatestCheckpointByProject(db, project) {
1270
2591
  const query = db.query(
1271
- "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
2592
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1272
2593
  );
1273
2594
  return query.get(project);
1274
2595
  }
@@ -1330,9 +2651,9 @@ function getReportData(db, project, startEpoch, endEpoch) {
1330
2651
  const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1331
2652
  const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1332
2653
  WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1333
- ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
2654
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT learned, completed, next_steps FROM summaries
1334
2655
  WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1335
- ORDER BY created_at_epoch DESC`;
2656
+ ORDER BY created_at_epoch DESC, id DESC`;
1336
2657
  const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1337
2658
  const topLearnings = [];
1338
2659
  const completedTasks = [];
@@ -1396,10 +2717,207 @@ function getReportData(db, project, startEpoch, endEpoch) {
1396
2717
 
1397
2718
  // src/services/sqlite/index.ts
1398
2719
  init_Search();
2720
+ init_ImportExport();
2721
+
2722
+ // src/types/worker-types.ts
2723
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
2724
+
2725
+ // src/services/sqlite/Retention.ts
2726
+ var KNOWLEDGE_TYPE_LIST = KNOWLEDGE_TYPES;
2727
+ var KNOWLEDGE_PLACEHOLDERS = KNOWLEDGE_TYPE_LIST.map(() => "?").join(", ");
2728
+
2729
+ // src/services/sqlite/Backup.ts
2730
+ init_logger();
2731
+ import {
2732
+ existsSync as existsSync4,
2733
+ mkdirSync as mkdirSync3,
2734
+ copyFileSync,
2735
+ readdirSync,
2736
+ statSync as statSync2,
2737
+ unlinkSync,
2738
+ readFileSync as readFileSync2,
2739
+ writeFileSync
2740
+ } from "fs";
2741
+ import { join as join3, basename as basename2 } from "path";
2742
+ function formatTimestamp(date) {
2743
+ const pad = (n, len = 2) => String(n).padStart(len, "0");
2744
+ const year = date.getFullYear();
2745
+ const month = pad(date.getMonth() + 1);
2746
+ const day = pad(date.getDate());
2747
+ const hours = pad(date.getHours());
2748
+ const mins = pad(date.getMinutes());
2749
+ const secs = pad(date.getSeconds());
2750
+ const ms = pad(date.getMilliseconds(), 3);
2751
+ return `${year}-${month}-${day}-${hours}${mins}${secs}-${ms}`;
2752
+ }
2753
+ function collectStats(db, dbPath) {
2754
+ const countTable = (table) => {
2755
+ try {
2756
+ const row = db.query(`SELECT COUNT(*) as c FROM ${table}`).get();
2757
+ return row?.c ?? 0;
2758
+ } catch {
2759
+ return 0;
2760
+ }
2761
+ };
2762
+ const dbSizeBytes = existsSync4(dbPath) ? statSync2(dbPath).size : 0;
2763
+ return {
2764
+ observations: countTable("observations"),
2765
+ sessions: countTable("sessions"),
2766
+ summaries: countTable("summaries"),
2767
+ prompts: countTable("prompts"),
2768
+ dbSizeBytes
2769
+ };
2770
+ }
2771
+ function getSchemaVersion(db) {
2772
+ try {
2773
+ const row = db.query("SELECT MAX(version) as v FROM schema_versions").get();
2774
+ return row?.v ?? 0;
2775
+ } catch {
2776
+ return 0;
2777
+ }
2778
+ }
2779
+ function createBackup(dbPath, backupDir, db) {
2780
+ mkdirSync3(backupDir, { recursive: true });
2781
+ const now = /* @__PURE__ */ new Date();
2782
+ const ts = formatTimestamp(now);
2783
+ const filename = `backup-${ts}.db`;
2784
+ const destPath = join3(backupDir, filename);
2785
+ const metaFilename = `backup-${ts}.meta.json`;
2786
+ const metaPath = join3(backupDir, metaFilename);
2787
+ if (!existsSync4(dbPath)) {
2788
+ throw new Error(`Database non trovato: ${dbPath}`);
2789
+ }
2790
+ copyFileSync(dbPath, destPath);
2791
+ logger.info("BACKUP", `File DB copiato: ${dbPath} \u2192 ${destPath}`);
2792
+ const walPath = `${dbPath}-wal`;
2793
+ const shmPath = `${dbPath}-shm`;
2794
+ if (existsSync4(walPath)) {
2795
+ copyFileSync(walPath, `${destPath}-wal`);
2796
+ logger.debug("BACKUP", "File WAL copiato");
2797
+ }
2798
+ if (existsSync4(shmPath)) {
2799
+ copyFileSync(shmPath, `${destPath}-shm`);
2800
+ logger.debug("BACKUP", "File SHM copiato");
2801
+ }
2802
+ const stats = collectStats(db, dbPath);
2803
+ const schemaVersion = getSchemaVersion(db);
2804
+ const metadata = {
2805
+ timestamp: now.toISOString(),
2806
+ timestampEpoch: now.getTime(),
2807
+ schemaVersion,
2808
+ stats,
2809
+ sourcePath: dbPath,
2810
+ filename
2811
+ };
2812
+ writeFileSync(metaPath, JSON.stringify(metadata, null, 2), "utf8");
2813
+ logger.info("BACKUP", `Metadata scritto: ${metaPath}`);
2814
+ return {
2815
+ filePath: destPath,
2816
+ metaPath,
2817
+ metadata
2818
+ };
2819
+ }
2820
+ function listBackups(backupDir) {
2821
+ if (!existsSync4(backupDir)) {
2822
+ return [];
2823
+ }
2824
+ const entries = [];
2825
+ let files;
2826
+ try {
2827
+ files = readdirSync(backupDir);
2828
+ } catch (err) {
2829
+ logger.warn("BACKUP", `Impossibile leggere la directory backup: ${backupDir}`, {}, err);
2830
+ return [];
2831
+ }
2832
+ const metaFiles = files.filter((f) => f.startsWith("backup-") && f.endsWith(".meta.json"));
2833
+ for (const metaFile of metaFiles) {
2834
+ const metaPath = join3(backupDir, metaFile);
2835
+ const dbFilename = metaFile.replace(/\.meta\.json$/, ".db");
2836
+ const filePath = join3(backupDir, dbFilename);
2837
+ let metadata;
2838
+ try {
2839
+ const raw = readFileSync2(metaPath, "utf8");
2840
+ metadata = JSON.parse(raw);
2841
+ } catch (err) {
2842
+ logger.warn("BACKUP", `Metadata non leggibile: ${metaPath}`, {}, err);
2843
+ continue;
2844
+ }
2845
+ if (!existsSync4(filePath)) {
2846
+ logger.warn("BACKUP", `File backup mancante per metadata: ${filePath}`);
2847
+ continue;
2848
+ }
2849
+ entries.push({ filePath, metaPath, metadata });
2850
+ }
2851
+ entries.sort((a, b) => b.metadata.timestampEpoch - a.metadata.timestampEpoch);
2852
+ return entries;
2853
+ }
2854
+ function restoreBackup(backupFile, dbPath) {
2855
+ if (!existsSync4(backupFile)) {
2856
+ throw new Error(`File backup non trovato: ${backupFile}`);
2857
+ }
2858
+ copyFileSync(backupFile, dbPath);
2859
+ logger.info("BACKUP", `Database ripristinato: ${backupFile} \u2192 ${dbPath}`);
2860
+ const walBackup = `${backupFile}-wal`;
2861
+ const shmBackup = `${backupFile}-shm`;
2862
+ const walDest = `${dbPath}-wal`;
2863
+ const shmDest = `${dbPath}-shm`;
2864
+ if (existsSync4(walBackup)) {
2865
+ copyFileSync(walBackup, walDest);
2866
+ logger.debug("BACKUP", "File WAL ripristinato");
2867
+ } else if (existsSync4(walDest)) {
2868
+ unlinkSync(walDest);
2869
+ logger.debug("BACKUP", "File WAL corrente rimosso (non presente nel backup)");
2870
+ }
2871
+ if (existsSync4(shmBackup)) {
2872
+ copyFileSync(shmBackup, shmDest);
2873
+ logger.debug("BACKUP", "File SHM ripristinato");
2874
+ } else if (existsSync4(shmDest)) {
2875
+ unlinkSync(shmDest);
2876
+ logger.debug("BACKUP", "File SHM corrente rimosso (non presente nel backup)");
2877
+ }
2878
+ }
2879
+ function rotateBackups(backupDir, maxKeep) {
2880
+ if (maxKeep <= 0) {
2881
+ throw new Error(`maxKeep deve essere > 0, ricevuto: ${maxKeep}`);
2882
+ }
2883
+ const entries = listBackups(backupDir);
2884
+ if (entries.length <= maxKeep) {
2885
+ logger.debug("BACKUP", `Rotazione non necessaria: ${entries.length}/${maxKeep} backup presenti`);
2886
+ return 0;
2887
+ }
2888
+ const toDelete = entries.slice(maxKeep);
2889
+ let deleted = 0;
2890
+ for (const entry of toDelete) {
2891
+ try {
2892
+ if (existsSync4(entry.filePath)) {
2893
+ unlinkSync(entry.filePath);
2894
+ }
2895
+ } catch (err) {
2896
+ logger.warn("BACKUP", `Impossibile eliminare: ${entry.filePath}`, {}, err);
2897
+ }
2898
+ for (const extra of [`${entry.filePath}-wal`, `${entry.filePath}-shm`]) {
2899
+ try {
2900
+ if (existsSync4(extra)) unlinkSync(extra);
2901
+ } catch {
2902
+ }
2903
+ }
2904
+ try {
2905
+ if (existsSync4(entry.metaPath)) {
2906
+ unlinkSync(entry.metaPath);
2907
+ }
2908
+ } catch (err) {
2909
+ logger.warn("BACKUP", `Impossibile eliminare metadata: ${entry.metaPath}`, {}, err);
2910
+ }
2911
+ logger.info("BACKUP", `Backup rimosso (rotazione): ${basename2(entry.filePath)}`);
2912
+ deleted++;
2913
+ }
2914
+ logger.info("BACKUP", `Rotazione completata: ${deleted} backup eliminati, ${maxKeep} mantenuti`);
2915
+ return deleted;
2916
+ }
1399
2917
 
1400
2918
  // src/sdk/index.ts
1401
2919
  init_Observations();
1402
- import { createHash } from "crypto";
2920
+ import { createHash as createHash2 } from "crypto";
1403
2921
  init_Search();
1404
2922
 
1405
2923
  // src/services/search/HybridSearch.ts
@@ -1408,17 +2926,21 @@ init_EmbeddingService();
1408
2926
  // src/services/search/VectorSearch.ts
1409
2927
  init_EmbeddingService();
1410
2928
  init_logger();
2929
+ var DEFAULT_MAX_CANDIDATES = 2e3;
1411
2930
  function cosineSimilarity(a, b) {
1412
- if (a.length !== b.length) return 0;
2931
+ const len = a.length;
2932
+ if (len !== b.length) return 0;
1413
2933
  let dotProduct = 0;
1414
2934
  let normA = 0;
1415
2935
  let normB = 0;
1416
- for (let i = 0; i < a.length; i++) {
1417
- dotProduct += a[i] * b[i];
1418
- normA += a[i] * a[i];
1419
- normB += b[i] * b[i];
1420
- }
1421
- const denominator = Math.sqrt(normA) * Math.sqrt(normB);
2936
+ for (let i = 0; i < len; i++) {
2937
+ const ai = a[i];
2938
+ const bi = b[i];
2939
+ dotProduct += ai * bi;
2940
+ normA += ai * ai;
2941
+ normB += bi * bi;
2942
+ }
2943
+ const denominator = Math.sqrt(normA * normB);
1422
2944
  if (denominator === 0) return 0;
1423
2945
  return dotProduct / denominator;
1424
2946
  }
@@ -1431,23 +2953,36 @@ function bufferToFloat32(buf) {
1431
2953
  }
1432
2954
  var VectorSearch = class {
1433
2955
  /**
1434
- * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
2956
+ * Semantic search with SQL pre-filtering for scalability.
2957
+ *
2958
+ * 2-phase strategy:
2959
+ * 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
2960
+ * 2. JS computes cosine similarity only on filtered candidates
2961
+ *
2962
+ * With 50k observations and maxCandidates=2000, loads only ~4% of data.
1435
2963
  */
1436
2964
  async search(db, queryEmbedding, options = {}) {
1437
2965
  const limit = options.limit || 10;
1438
2966
  const threshold = options.threshold || 0.3;
2967
+ const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
1439
2968
  try {
1440
- let sql = `
2969
+ const conditions = [];
2970
+ const params = [];
2971
+ if (options.project) {
2972
+ conditions.push("o.project = ?");
2973
+ params.push(options.project);
2974
+ }
2975
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2976
+ const sql = `
1441
2977
  SELECT e.observation_id, e.embedding,
1442
2978
  o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1443
2979
  FROM observation_embeddings e
1444
2980
  JOIN observations o ON o.id = e.observation_id
2981
+ ${whereClause}
2982
+ ORDER BY o.created_at_epoch DESC
2983
+ LIMIT ?
1445
2984
  `;
1446
- const params = [];
1447
- if (options.project) {
1448
- sql += " WHERE o.project = ?";
1449
- params.push(options.project);
1450
- }
2985
+ params.push(maxCandidates);
1451
2986
  const rows = db.query(sql).all(...params);
1452
2987
  const scored = [];
1453
2988
  for (const row of rows) {
@@ -1468,14 +3003,15 @@ var VectorSearch = class {
1468
3003
  }
1469
3004
  }
1470
3005
  scored.sort((a, b) => b.similarity - a.similarity);
3006
+ logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
1471
3007
  return scored.slice(0, limit);
1472
3008
  } catch (error) {
1473
- logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
3009
+ logger.error("VECTOR", `Vector search error: ${error}`);
1474
3010
  return [];
1475
3011
  }
1476
3012
  }
1477
3013
  /**
1478
- * Salva embedding per un'osservazione.
3014
+ * Store embedding for an observation.
1479
3015
  */
1480
3016
  async storeEmbedding(db, observationId, embedding, model) {
1481
3017
  try {
@@ -1491,18 +3027,18 @@ var VectorSearch = class {
1491
3027
  embedding.length,
1492
3028
  (/* @__PURE__ */ new Date()).toISOString()
1493
3029
  );
1494
- logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
3030
+ logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
1495
3031
  } catch (error) {
1496
- logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
3032
+ logger.error("VECTOR", `Error saving embedding: ${error}`);
1497
3033
  }
1498
3034
  }
1499
3035
  /**
1500
- * Genera embeddings per osservazioni che non li hanno ancora.
3036
+ * Generate embeddings for observations that don't have them yet.
1501
3037
  */
1502
3038
  async backfillEmbeddings(db, batchSize = 50) {
1503
3039
  const embeddingService2 = getEmbeddingService();
1504
3040
  if (!await embeddingService2.initialize()) {
1505
- logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
3041
+ logger.warn("VECTOR", "Embedding service not available, backfill skipped");
1506
3042
  return 0;
1507
3043
  }
1508
3044
  const rows = db.query(`
@@ -1515,7 +3051,7 @@ var VectorSearch = class {
1515
3051
  `).all(batchSize);
1516
3052
  if (rows.length === 0) return 0;
1517
3053
  let count = 0;
1518
- const model = embeddingService2.getProvider() || "unknown";
3054
+ const model = embeddingService2.getModelName();
1519
3055
  for (const row of rows) {
1520
3056
  const parts = [row.title];
1521
3057
  if (row.text) parts.push(row.text);
@@ -1528,11 +3064,11 @@ var VectorSearch = class {
1528
3064
  count++;
1529
3065
  }
1530
3066
  }
1531
- logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
3067
+ logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
1532
3068
  return count;
1533
3069
  }
1534
3070
  /**
1535
- * Statistiche sugli embeddings.
3071
+ * Embedding statistics.
1536
3072
  */
1537
3073
  getStats(db) {
1538
3074
  try {
@@ -1606,21 +3142,21 @@ init_logger();
1606
3142
  var HybridSearch = class {
1607
3143
  embeddingInitialized = false;
1608
3144
  /**
1609
- * Inizializza il servizio di embedding (lazy, non bloccante)
3145
+ * Initialize the embedding service (lazy, non-blocking)
1610
3146
  */
1611
3147
  async initialize() {
1612
3148
  try {
1613
3149
  const embeddingService2 = getEmbeddingService();
1614
3150
  await embeddingService2.initialize();
1615
3151
  this.embeddingInitialized = embeddingService2.isAvailable();
1616
- logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
3152
+ logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
1617
3153
  } catch (error) {
1618
- logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
3154
+ logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
1619
3155
  this.embeddingInitialized = false;
1620
3156
  }
1621
3157
  }
1622
3158
  /**
1623
- * Ricerca ibrida con scoring a 4 segnali
3159
+ * Hybrid search with 4-signal scoring
1624
3160
  */
1625
3161
  async search(db, query, options = {}) {
1626
3162
  const limit = options.limit || 10;
@@ -1636,7 +3172,7 @@ var HybridSearch = class {
1636
3172
  const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1637
3173
  project: options.project,
1638
3174
  limit: limit * 2,
1639
- // Prendiamo piu risultati per il ranking
3175
+ // Fetch more results for ranking
1640
3176
  threshold: 0.3
1641
3177
  });
1642
3178
  for (const hit of vectorResults) {
@@ -1653,10 +3189,10 @@ var HybridSearch = class {
1653
3189
  source: "vector"
1654
3190
  });
1655
3191
  }
1656
- logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
3192
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
1657
3193
  }
1658
3194
  } catch (error) {
1659
- logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
3195
+ logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
1660
3196
  }
1661
3197
  }
1662
3198
  try {
@@ -1686,9 +3222,9 @@ var HybridSearch = class {
1686
3222
  });
1687
3223
  }
1688
3224
  }
1689
- logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
3225
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
1690
3226
  } catch (error) {
1691
- logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
3227
+ logger.error("SEARCH", "Keyword search failed", {}, error);
1692
3228
  }
1693
3229
  if (rawItems.size === 0) return [];
1694
3230
  const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
@@ -1743,11 +3279,6 @@ function getHybridSearch() {
1743
3279
  // src/sdk/index.ts
1744
3280
  init_EmbeddingService();
1745
3281
  init_logger();
1746
-
1747
- // src/types/worker-types.ts
1748
- var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1749
-
1750
- // src/sdk/index.ts
1751
3282
  var KiroMemorySDK = class {
1752
3283
  db;
1753
3284
  project;
@@ -1780,33 +3311,33 @@ var KiroMemorySDK = class {
1780
3311
  };
1781
3312
  }
1782
3313
  /**
1783
- * Valida input per storeObservation
3314
+ * Validate input for storeObservation
1784
3315
  */
1785
3316
  validateObservationInput(data) {
1786
3317
  if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1787
- throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
3318
+ throw new Error("type is required (string, max 100 chars)");
1788
3319
  }
1789
3320
  if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1790
- throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
3321
+ throw new Error("title is required (string, max 500 chars)");
1791
3322
  }
1792
3323
  if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1793
- throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
3324
+ throw new Error("content is required (string, max 100KB)");
1794
3325
  }
1795
3326
  }
1796
3327
  /**
1797
- * Valida input per storeSummary
3328
+ * Validate input for storeSummary
1798
3329
  */
1799
3330
  validateSummaryInput(data) {
1800
3331
  const MAX = 5e4;
1801
3332
  for (const [key, val] of Object.entries(data)) {
1802
3333
  if (val !== void 0 && val !== null) {
1803
- if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1804
- if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
3334
+ if (typeof val !== "string") throw new Error(`${key} must be a string`);
3335
+ if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
1805
3336
  }
1806
3337
  }
1807
3338
  }
1808
3339
  /**
1809
- * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
3340
+ * Generate and store embedding for an observation (fire-and-forget, non-blocking)
1810
3341
  */
1811
3342
  async generateEmbeddingAsync(observationId, title, content, concepts) {
1812
3343
  try {
@@ -1826,39 +3357,39 @@ var KiroMemorySDK = class {
1826
3357
  );
1827
3358
  }
1828
3359
  } catch (error) {
1829
- logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
3360
+ logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
1830
3361
  }
1831
3362
  }
1832
3363
  /**
1833
- * Genera content hash SHA256 per deduplicazione basata su contenuto.
1834
- * Usa (project + type + title + narrative) come tupla di identità semantica.
1835
- * NON include sessionId perché è unico ad ogni invocazione.
3364
+ * Generate SHA256 content hash for content-based deduplication.
3365
+ * Uses (project + type + title + narrative) as semantic identity tuple.
3366
+ * Does NOT include sessionId since it's unique per invocation.
1836
3367
  */
1837
3368
  generateContentHash(type, title, narrative) {
1838
3369
  const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
1839
- return createHash("sha256").update(payload).digest("hex");
3370
+ return createHash2("sha256").update(payload).digest("hex");
1840
3371
  }
1841
3372
  /**
1842
- * Finestre di deduplicazione per tipo (ms).
1843
- * Tipi con molte ripetizioni hanno finestre più ampie.
3373
+ * Deduplication windows per type (ms).
3374
+ * Types with many repetitions have wider windows.
1844
3375
  */
1845
3376
  getDeduplicationWindow(type) {
1846
3377
  switch (type) {
1847
3378
  case "file-read":
1848
3379
  return 6e4;
1849
- // 60s — letture frequenti sugli stessi file
3380
+ // 60s — frequent reads on the same files
1850
3381
  case "file-write":
1851
3382
  return 1e4;
1852
- // 10s — scritture rapide consecutive
3383
+ // 10s — rapid consecutive writes
1853
3384
  case "command":
1854
3385
  return 3e4;
1855
3386
  // 30s — standard
1856
3387
  case "research":
1857
3388
  return 12e4;
1858
- // 120s — web search e fetch ripetuti
3389
+ // 120s — repeated web search and fetch
1859
3390
  case "delegation":
1860
3391
  return 6e4;
1861
- // 60s — delegazioni rapide
3392
+ // 60s — rapid delegations
1862
3393
  default:
1863
3394
  return 3e4;
1864
3395
  }
@@ -1872,7 +3403,7 @@ var KiroMemorySDK = class {
1872
3403
  const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
1873
3404
  const dedupWindow = this.getDeduplicationWindow(data.type);
1874
3405
  if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
1875
- logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
3406
+ logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
1876
3407
  return -1;
1877
3408
  }
1878
3409
  const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
@@ -1900,12 +3431,12 @@ var KiroMemorySDK = class {
1900
3431
  return observationId;
1901
3432
  }
1902
3433
  /**
1903
- * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1904
- * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
3434
+ * Store structured knowledge (constraint, decision, heuristic, rejected).
3435
+ * Uses the `type` field for knowledgeType and `facts` for JSON metadata.
1905
3436
  */
1906
3437
  async storeKnowledge(data) {
1907
3438
  if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1908
- throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
3439
+ throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
1909
3440
  }
1910
3441
  this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
1911
3442
  const metadata = (() => {
@@ -1937,9 +3468,9 @@ var KiroMemorySDK = class {
1937
3468
  }
1938
3469
  })();
1939
3470
  const sessionId = "sdk-" + Date.now();
1940
- const contentHash = this.generateContentHash(data.type, data.title);
3471
+ const contentHash = this.generateContentHash(data.knowledgeType, data.title);
1941
3472
  if (isDuplicateObservation(this.db.db, contentHash)) {
1942
- logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
3473
+ logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
1943
3474
  return -1;
1944
3475
  }
1945
3476
  const discoveryTokens = Math.ceil(data.content.length / 4);
@@ -1956,11 +3487,11 @@ var KiroMemorySDK = class {
1956
3487
  null,
1957
3488
  // narrative
1958
3489
  JSON.stringify(metadata),
1959
- // facts = metadati JSON
3490
+ // facts = JSON metadata
1960
3491
  data.concepts?.join(", ") || null,
1961
3492
  data.files?.join(", ") || null,
1962
3493
  null,
1963
- // filesModified: knowledge non modifica file
3494
+ // filesModified: knowledge doesn't modify files
1964
3495
  0,
1965
3496
  // prompt_number
1966
3497
  contentHash,
@@ -2071,8 +3602,8 @@ var KiroMemorySDK = class {
2071
3602
  return this.project;
2072
3603
  }
2073
3604
  /**
2074
- * Ricerca ibrida: vector search + keyword FTS5
2075
- * Richiede inizializzazione HybridSearch (embedding service)
3605
+ * Hybrid search: vector search + keyword FTS5
3606
+ * Requires HybridSearch initialization (embedding service)
2076
3607
  */
2077
3608
  async hybridSearch(query, options = {}) {
2078
3609
  const hybridSearch2 = getHybridSearch();
@@ -2082,8 +3613,8 @@ var KiroMemorySDK = class {
2082
3613
  });
2083
3614
  }
2084
3615
  /**
2085
- * Ricerca solo semantica (vector search)
2086
- * Ritorna risultati basati su similarità coseno con gli embeddings
3616
+ * Semantic-only search (vector search)
3617
+ * Returns results based on cosine similarity with embeddings
2087
3618
  */
2088
3619
  async semanticSearch(query, options = {}) {
2089
3620
  const embeddingService2 = getEmbeddingService();
@@ -2118,21 +3649,21 @@ var KiroMemorySDK = class {
2118
3649
  }));
2119
3650
  }
2120
3651
  /**
2121
- * Genera embeddings per osservazioni che non li hanno ancora
3652
+ * Generate embeddings for observations that don't have them yet
2122
3653
  */
2123
3654
  async backfillEmbeddings(batchSize = 50) {
2124
3655
  const vectorSearch2 = getVectorSearch();
2125
3656
  return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2126
3657
  }
2127
3658
  /**
2128
- * Statistiche sugli embeddings nel database
3659
+ * Embedding statistics in the database
2129
3660
  */
2130
3661
  getEmbeddingStats() {
2131
3662
  const vectorSearch2 = getVectorSearch();
2132
3663
  return vectorSearch2.getStats(this.db.db);
2133
3664
  }
2134
3665
  /**
2135
- * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
3666
+ * Initialize the embedding service (lazy, call before hybridSearch)
2136
3667
  */
2137
3668
  async initializeEmbeddings() {
2138
3669
  const hybridSearch2 = getHybridSearch();
@@ -2140,10 +3671,10 @@ var KiroMemorySDK = class {
2140
3671
  return getEmbeddingService().isAvailable();
2141
3672
  }
2142
3673
  /**
2143
- * Contesto smart con ranking a 4 segnali e budget token.
3674
+ * Smart context with 4-signal ranking and token budget.
2144
3675
  *
2145
- * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2146
- * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
3676
+ * If query present: uses HybridSearch with SEARCH_WEIGHTS.
3677
+ * If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
2147
3678
  */
2148
3679
  async getSmartContext(options = {}) {
2149
3680
  const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
@@ -2217,8 +3748,8 @@ var KiroMemorySDK = class {
2217
3748
  };
2218
3749
  }
2219
3750
  /**
2220
- * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2221
- * Ritorna il numero di osservazioni marcate come stale.
3751
+ * Detect stale observations (files modified after creation) and mark them in DB.
3752
+ * Returns the number of observations marked as stale.
2222
3753
  */
2223
3754
  async detectStaleObservations() {
2224
3755
  const staleObs = getStaleObservations(this.db.db, this.project);
@@ -2229,14 +3760,14 @@ var KiroMemorySDK = class {
2229
3760
  return staleObs.length;
2230
3761
  }
2231
3762
  /**
2232
- * Consolida osservazioni duplicate sullo stesso file e tipo.
2233
- * Raggruppa per (project, type, files_modified), mantiene la piu recente.
3763
+ * Consolidate duplicate observations on the same file and type.
3764
+ * Groups by (project, type, files_modified), keeps the most recent.
2234
3765
  */
2235
3766
  async consolidateObservations(options = {}) {
2236
3767
  return consolidateObservations(this.db.db, this.project, options);
2237
3768
  }
2238
3769
  /**
2239
- * Statistiche decay: totale, stale, mai accedute, accedute di recente.
3770
+ * Decay statistics: total, stale, never accessed, recently accessed.
2240
3771
  */
2241
3772
  async getDecayStats() {
2242
3773
  const total = this.db.db.query(
@@ -2255,8 +3786,8 @@ var KiroMemorySDK = class {
2255
3786
  return { total, stale, neverAccessed, recentlyAccessed };
2256
3787
  }
2257
3788
  /**
2258
- * Crea un checkpoint strutturato per resume sessione.
2259
- * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
3789
+ * Create a structured checkpoint for session resume.
3790
+ * Automatically saves a context_snapshot with the last 10 observations.
2260
3791
  */
2261
3792
  async createCheckpoint(sessionId, data) {
2262
3793
  const recentObs = getObservationsByProject(this.db.db, this.project, 10);
@@ -2273,21 +3804,21 @@ var KiroMemorySDK = class {
2273
3804
  });
2274
3805
  }
2275
3806
  /**
2276
- * Recupera l'ultimo checkpoint di una sessione specifica.
3807
+ * Retrieve the latest checkpoint of a specific session.
2277
3808
  */
2278
3809
  async getCheckpoint(sessionId) {
2279
3810
  return getLatestCheckpoint(this.db.db, sessionId);
2280
3811
  }
2281
3812
  /**
2282
- * Recupera l'ultimo checkpoint per il progetto corrente.
2283
- * Utile per resume automatico senza specificare session ID.
3813
+ * Retrieve the latest checkpoint for the current project.
3814
+ * Useful for automatic resume without specifying session ID.
2284
3815
  */
2285
3816
  async getLatestProjectCheckpoint() {
2286
3817
  return getLatestCheckpointByProject(this.db.db, this.project);
2287
3818
  }
2288
3819
  /**
2289
- * Genera un report di attività per il progetto corrente.
2290
- * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
3820
+ * Generate an activity report for the current project.
3821
+ * Aggregates observations, sessions, summaries and files for a time period.
2291
3822
  */
2292
3823
  async generateReport(options) {
2293
3824
  const now = /* @__PURE__ */ new Date();
@@ -2303,6 +3834,66 @@ var KiroMemorySDK = class {
2303
3834
  }
2304
3835
  return getReportData(this.db.db, this.project, startEpoch, endEpoch);
2305
3836
  }
3837
+ /**
3838
+ * Lista osservazioni con keyset pagination.
3839
+ * Restituisce un oggetto { data, next_cursor, has_more }.
3840
+ *
3841
+ * Esempio:
3842
+ * const page1 = await sdk.listObservations({ limit: 50 });
3843
+ * const page2 = await sdk.listObservations({ cursor: page1.next_cursor });
3844
+ */
3845
+ async listObservations(options = {}) {
3846
+ const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
3847
+ const project = options.project ?? this.project;
3848
+ let rows;
3849
+ if (options.cursor) {
3850
+ const decoded = decodeCursor(options.cursor);
3851
+ if (!decoded) throw new Error("Cursor non valido");
3852
+ const sql = project ? `SELECT * FROM observations
3853
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3854
+ ORDER BY created_at_epoch DESC, id DESC
3855
+ LIMIT ?` : `SELECT * FROM observations
3856
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3857
+ ORDER BY created_at_epoch DESC, id DESC
3858
+ LIMIT ?`;
3859
+ rows = project ? this.db.db.query(sql).all(project, decoded.epoch, decoded.epoch, decoded.id, limit) : this.db.db.query(sql).all(decoded.epoch, decoded.epoch, decoded.id, limit);
3860
+ } else {
3861
+ const sql = project ? "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?" : "SELECT * FROM observations ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
3862
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
3863
+ }
3864
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
3865
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
3866
+ }
3867
+ /**
3868
+ * Lista sommari con keyset pagination.
3869
+ * Restituisce un oggetto { data, next_cursor, has_more }.
3870
+ *
3871
+ * Esempio:
3872
+ * const page1 = await sdk.listSummaries({ limit: 20 });
3873
+ * const page2 = await sdk.listSummaries({ cursor: page1.next_cursor });
3874
+ */
3875
+ async listSummaries(options = {}) {
3876
+ const limit = Math.min(Math.max(options.limit ?? 20, 1), 200);
3877
+ const project = options.project ?? this.project;
3878
+ let rows;
3879
+ if (options.cursor) {
3880
+ const decoded = decodeCursor(options.cursor);
3881
+ if (!decoded) throw new Error("Cursor non valido");
3882
+ const sql = project ? `SELECT * FROM summaries
3883
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3884
+ ORDER BY created_at_epoch DESC, id DESC
3885
+ LIMIT ?` : `SELECT * FROM summaries
3886
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3887
+ ORDER BY created_at_epoch DESC, id DESC
3888
+ LIMIT ?`;
3889
+ rows = project ? this.db.db.query(sql).all(project, decoded.epoch, decoded.epoch, decoded.id, limit) : this.db.db.query(sql).all(decoded.epoch, decoded.epoch, decoded.id, limit);
3890
+ } else {
3891
+ const sql = project ? "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?" : "SELECT * FROM summaries ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
3892
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
3893
+ }
3894
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
3895
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
3896
+ }
2306
3897
  /**
2307
3898
  * Getter for direct database access (for API routes)
2308
3899
  */
@@ -2549,12 +4140,15 @@ function printBanner(opts) {
2549
4140
  }
2550
4141
 
2551
4142
  // src/cli/contextkit.ts
4143
+ init_cli_utils();
4144
+ init_Observations();
2552
4145
  import { execSync } from "child_process";
2553
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
2554
- import { join as join3, dirname as dirname2 } from "path";
2555
- import { homedir as homedir3, platform, release } from "os";
4146
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
4147
+ import { join as join5, dirname as dirname2 } from "path";
4148
+ import { homedir as homedir4, platform, release } from "os";
2556
4149
  import { fileURLToPath as fileURLToPath2 } from "url";
2557
4150
  import { createInterface } from "readline";
4151
+ import * as http from "http";
2558
4152
  var args = process.argv.slice(2);
2559
4153
  var command = args[0];
2560
4154
  var __filename = fileURLToPath2(import.meta.url);
@@ -2562,8 +4156,8 @@ var __dirname2 = dirname2(__filename);
2562
4156
  var DIST_DIR = dirname2(__dirname2);
2563
4157
  var PKG_VERSION = "unknown";
2564
4158
  try {
2565
- const pkgPath = join3(DIST_DIR, "..", "..", "package.json");
2566
- PKG_VERSION = JSON.parse(readFileSync2(pkgPath, "utf8")).version;
4159
+ const pkgPath = join5(DIST_DIR, "..", "..", "package.json");
4160
+ PKG_VERSION = JSON.parse(readFileSync4(pkgPath, "utf8")).version;
2567
4161
  } catch {
2568
4162
  }
2569
4163
  var AGENT_TEMPLATE = JSON.stringify({
@@ -2617,8 +4211,8 @@ function isWSL() {
2617
4211
  try {
2618
4212
  const rel = release().toLowerCase();
2619
4213
  if (rel.includes("microsoft") || rel.includes("wsl")) return true;
2620
- if (existsSync4("/proc/version")) {
2621
- const proc = readFileSync2("/proc/version", "utf8").toLowerCase();
4214
+ if (existsSync6("/proc/version")) {
4215
+ const proc = readFileSync4("/proc/version", "utf8").toLowerCase();
2622
4216
  return proc.includes("microsoft") || proc.includes("wsl");
2623
4217
  }
2624
4218
  return false;
@@ -2757,9 +4351,9 @@ function askUser(question) {
2757
4351
  }
2758
4352
  function detectShellRc() {
2759
4353
  const shell = process.env.SHELL || "/bin/bash";
2760
- if (shell.includes("zsh")) return { name: "zsh", rcFile: join3(homedir3(), ".zshrc") };
2761
- if (shell.includes("fish")) return { name: "fish", rcFile: join3(homedir3(), ".config/fish/config.fish") };
2762
- return { name: "bash", rcFile: join3(homedir3(), ".bashrc") };
4354
+ if (shell.includes("zsh")) return { name: "zsh", rcFile: join5(homedir4(), ".zshrc") };
4355
+ if (shell.includes("fish")) return { name: "fish", rcFile: join5(homedir4(), ".config/fish/config.fish") };
4356
+ return { name: "bash", rcFile: join5(homedir4(), ".bashrc") };
2763
4357
  }
2764
4358
  var AUTOFIXABLE_CHECKS = /* @__PURE__ */ new Set([
2765
4359
  "WSL: npm global prefix",
@@ -2789,14 +4383,14 @@ async function tryAutoFix(failedChecks) {
2789
4383
  if (prefixCheck) {
2790
4384
  console.log(" Fixing npm global prefix...");
2791
4385
  try {
2792
- const npmGlobalDir = join3(homedir3(), ".npm-global");
2793
- mkdirSync3(npmGlobalDir, { recursive: true });
4386
+ const npmGlobalDir = join5(homedir4(), ".npm-global");
4387
+ mkdirSync5(npmGlobalDir, { recursive: true });
2794
4388
  const { spawnSync: spawnNpmConfig } = __require("child_process");
2795
4389
  spawnNpmConfig("npm", ["config", "set", "prefix", npmGlobalDir], { stdio: "ignore" });
2796
4390
  const exportLine = 'export PATH="$HOME/.npm-global/bin:$PATH"';
2797
4391
  let alreadyInRc = false;
2798
- if (existsSync4(rcFile)) {
2799
- const content = readFileSync2(rcFile, "utf8");
4392
+ if (existsSync6(rcFile)) {
4393
+ const content = readFileSync4(rcFile, "utf8");
2800
4394
  alreadyInRc = content.includes(".npm-global/bin");
2801
4395
  }
2802
4396
  if (!alreadyInRc) {
@@ -2816,9 +4410,9 @@ ${exportLine}
2816
4410
  const npmBinaryCheck = fixable.find((c) => c.name === "WSL: npm binary");
2817
4411
  if (npmBinaryCheck) {
2818
4412
  console.log("\n Fixing npm binary (installing nvm + Node.js 22)...");
2819
- const nvmDir = join3(homedir3(), ".nvm");
4413
+ const nvmDir = join5(homedir4(), ".nvm");
2820
4414
  try {
2821
- if (existsSync4(nvmDir)) {
4415
+ if (existsSync6(nvmDir)) {
2822
4416
  console.log(` nvm already installed at ${nvmDir}`);
2823
4417
  } else {
2824
4418
  console.log(" Downloading nvm...");
@@ -2866,8 +4460,8 @@ ${exportLine}
2866
4460
  const { spawnSync: spawnRebuild } = __require("child_process");
2867
4461
  const globalDirResult = spawnRebuild("npm", ["prefix", "-g"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
2868
4462
  const globalDir = (globalDirResult.stdout || "").trim();
2869
- const sqlitePkg = join3(globalDir, "lib", "node_modules", "kiro-memory");
2870
- if (existsSync4(sqlitePkg)) {
4463
+ const sqlitePkg = join5(globalDir, "lib", "node_modules", "kiro-memory");
4464
+ if (existsSync6(sqlitePkg)) {
2871
4465
  spawnRebuild("npm", ["rebuild", "better-sqlite3"], {
2872
4466
  cwd: sqlitePkg,
2873
4467
  stdio: "inherit",
@@ -2915,44 +4509,44 @@ async function installKiro() {
2915
4509
  }
2916
4510
  }
2917
4511
  const distDir = DIST_DIR;
2918
- const kiroDir = process.env.KIRO_CONFIG_DIR || join3(homedir3(), ".kiro");
2919
- const agentsDir = join3(kiroDir, "agents");
2920
- const settingsDir = join3(kiroDir, "settings");
2921
- const steeringDir = join3(kiroDir, "steering");
2922
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
4512
+ const kiroDir = process.env.KIRO_CONFIG_DIR || join5(homedir4(), ".kiro");
4513
+ const agentsDir = join5(kiroDir, "agents");
4514
+ const settingsDir = join5(kiroDir, "settings");
4515
+ const steeringDir = join5(kiroDir, "steering");
4516
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".contextkit");
2923
4517
  console.log("[2/4] Installing Kiro configuration...\n");
2924
4518
  for (const dir of [agentsDir, settingsDir, steeringDir, dataDir]) {
2925
- mkdirSync3(dir, { recursive: true });
4519
+ mkdirSync5(dir, { recursive: true });
2926
4520
  }
2927
4521
  const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
2928
- const agentDestPath = join3(agentsDir, "kiro-memory.json");
2929
- writeFileSync(agentDestPath, agentConfig, "utf8");
4522
+ const agentDestPath = join5(agentsDir, "kiro-memory.json");
4523
+ writeFileSync3(agentDestPath, agentConfig, "utf8");
2930
4524
  console.log(` \u2192 Agent config: ${agentDestPath}`);
2931
- const mcpFilePath = join3(settingsDir, "mcp.json");
4525
+ const mcpFilePath = join5(settingsDir, "mcp.json");
2932
4526
  let mcpConfig = { mcpServers: {} };
2933
- if (existsSync4(mcpFilePath)) {
4527
+ if (existsSync6(mcpFilePath)) {
2934
4528
  try {
2935
- mcpConfig = JSON.parse(readFileSync2(mcpFilePath, "utf8"));
4529
+ mcpConfig = JSON.parse(readFileSync4(mcpFilePath, "utf8"));
2936
4530
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
2937
4531
  } catch {
2938
4532
  }
2939
4533
  }
2940
4534
  mcpConfig.mcpServers["kiro-memory"] = {
2941
4535
  command: "node",
2942
- args: [join3(distDir, "servers", "mcp-server.js")]
4536
+ args: [join5(distDir, "servers", "mcp-server.js")]
2943
4537
  };
2944
- writeFileSync(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
4538
+ writeFileSync3(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
2945
4539
  console.log(` \u2192 MCP config: ${mcpFilePath}`);
2946
- const steeringDestPath = join3(steeringDir, "kiro-memory.md");
2947
- writeFileSync(steeringDestPath, STEERING_CONTENT, "utf8");
4540
+ const steeringDestPath = join5(steeringDir, "kiro-memory.md");
4541
+ writeFileSync3(steeringDestPath, STEERING_CONTENT, "utf8");
2948
4542
  console.log(` \u2192 Steering: ${steeringDestPath}`);
2949
4543
  console.log(` \u2192 Data dir: ${dataDir}`);
2950
4544
  console.log("\n[3/4] Shell alias setup\n");
2951
4545
  const { rcFile } = detectShellRc();
2952
4546
  const aliasLine = 'alias kiro="kiro-cli --agent kiro-memory"';
2953
4547
  let aliasAlreadySet = false;
2954
- if (existsSync4(rcFile)) {
2955
- const rcContent = readFileSync2(rcFile, "utf8");
4548
+ if (existsSync6(rcFile)) {
4549
+ const rcContent = readFileSync4(rcFile, "utf8");
2956
4550
  aliasAlreadySet = rcContent.includes("alias kiro=") && rcContent.includes("kiro-memory");
2957
4551
  }
2958
4552
  if (aliasAlreadySet) {
@@ -3059,16 +4653,16 @@ async function installClaudeCode() {
3059
4653
  }
3060
4654
  }
3061
4655
  const distDir = DIST_DIR;
3062
- const claudeDir = join3(homedir3(), ".claude");
3063
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
4656
+ const claudeDir = join5(homedir4(), ".claude");
4657
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".kiro-memory");
3064
4658
  console.log("[2/3] Installing Claude Code configuration...\n");
3065
- mkdirSync3(claudeDir, { recursive: true });
3066
- mkdirSync3(dataDir, { recursive: true });
3067
- const settingsPath = join3(claudeDir, "settings.json");
4659
+ mkdirSync5(claudeDir, { recursive: true });
4660
+ mkdirSync5(dataDir, { recursive: true });
4661
+ const settingsPath = join5(claudeDir, "settings.json");
3068
4662
  let settings = {};
3069
- if (existsSync4(settingsPath)) {
4663
+ if (existsSync6(settingsPath)) {
3070
4664
  try {
3071
- settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
4665
+ settings = JSON.parse(readFileSync4(settingsPath, "utf8"));
3072
4666
  } catch {
3073
4667
  }
3074
4668
  }
@@ -3083,7 +4677,7 @@ async function installClaudeCode() {
3083
4677
  matcher: "",
3084
4678
  hooks: [{
3085
4679
  type: "command",
3086
- command: `node ${join3(distDir, config.script)}`,
4680
+ command: `node ${join5(distDir, config.script)}`,
3087
4681
  timeout: config.timeout
3088
4682
  }]
3089
4683
  };
@@ -3098,31 +4692,31 @@ async function installClaudeCode() {
3098
4692
  settings[event].push(hookEntry);
3099
4693
  }
3100
4694
  }
3101
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
4695
+ writeFileSync3(settingsPath, JSON.stringify(settings, null, 2), "utf8");
3102
4696
  console.log(` \u2192 Hooks config: ${settingsPath}`);
3103
- const mcpPath = join3(homedir3(), ".mcp.json");
4697
+ const mcpPath = join5(homedir4(), ".mcp.json");
3104
4698
  let mcpConfig = {};
3105
- if (existsSync4(mcpPath)) {
4699
+ if (existsSync6(mcpPath)) {
3106
4700
  try {
3107
- mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
4701
+ mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf8"));
3108
4702
  } catch {
3109
4703
  }
3110
4704
  }
3111
4705
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3112
4706
  mcpConfig.mcpServers["kiro-memory"] = {
3113
4707
  command: "node",
3114
- args: [join3(distDir, "servers", "mcp-server.js")]
4708
+ args: [join5(distDir, "servers", "mcp-server.js")]
3115
4709
  };
3116
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
4710
+ writeFileSync3(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3117
4711
  console.log(` \u2192 MCP config: ${mcpPath}`);
3118
- const steeringPath = join3(claudeDir, "CLAUDE.md");
4712
+ const steeringPath = join5(claudeDir, "CLAUDE.md");
3119
4713
  let existingSteering = "";
3120
- if (existsSync4(steeringPath)) {
3121
- existingSteering = readFileSync2(steeringPath, "utf8");
4714
+ if (existsSync6(steeringPath)) {
4715
+ existingSteering = readFileSync4(steeringPath, "utf8");
3122
4716
  }
3123
4717
  if (!existingSteering.includes("Kiro Memory")) {
3124
4718
  const separator = existingSteering.length > 0 ? "\n\n---\n\n" : "";
3125
- writeFileSync(steeringPath, existingSteering + separator + CLAUDE_CODE_STEERING, "utf8");
4719
+ writeFileSync3(steeringPath, existingSteering + separator + CLAUDE_CODE_STEERING, "utf8");
3126
4720
  console.log(` \u2192 Steering: ${steeringPath}`);
3127
4721
  } else {
3128
4722
  console.log(` \u2192 Steering: ${steeringPath} (already configured)`);
@@ -3166,16 +4760,16 @@ async function installCursor() {
3166
4760
  }
3167
4761
  }
3168
4762
  const distDir = DIST_DIR;
3169
- const cursorDir = join3(homedir3(), ".cursor");
3170
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
4763
+ const cursorDir = join5(homedir4(), ".cursor");
4764
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".kiro-memory");
3171
4765
  console.log("[2/3] Installing Cursor configuration...\n");
3172
- mkdirSync3(cursorDir, { recursive: true });
3173
- mkdirSync3(dataDir, { recursive: true });
3174
- const hooksPath = join3(cursorDir, "hooks.json");
4766
+ mkdirSync5(cursorDir, { recursive: true });
4767
+ mkdirSync5(dataDir, { recursive: true });
4768
+ const hooksPath = join5(cursorDir, "hooks.json");
3175
4769
  let hooksConfig = { version: 1, hooks: {} };
3176
- if (existsSync4(hooksPath)) {
4770
+ if (existsSync6(hooksPath)) {
3177
4771
  try {
3178
- hooksConfig = JSON.parse(readFileSync2(hooksPath, "utf8"));
4772
+ hooksConfig = JSON.parse(readFileSync4(hooksPath, "utf8"));
3179
4773
  if (!hooksConfig.hooks) hooksConfig.hooks = {};
3180
4774
  if (!hooksConfig.version) hooksConfig.version = 1;
3181
4775
  } catch {
@@ -3191,7 +4785,7 @@ async function installCursor() {
3191
4785
  };
3192
4786
  for (const [event, script] of Object.entries(cursorHookMap)) {
3193
4787
  const hookEntry = {
3194
- command: `node ${join3(distDir, script)}`
4788
+ command: `node ${join5(distDir, script)}`
3195
4789
  };
3196
4790
  if (!hooksConfig.hooks[event]) {
3197
4791
  hooksConfig.hooks[event] = [hookEntry];
@@ -3202,22 +4796,22 @@ async function installCursor() {
3202
4796
  hooksConfig.hooks[event].push(hookEntry);
3203
4797
  }
3204
4798
  }
3205
- writeFileSync(hooksPath, JSON.stringify(hooksConfig, null, 2), "utf8");
4799
+ writeFileSync3(hooksPath, JSON.stringify(hooksConfig, null, 2), "utf8");
3206
4800
  console.log(` \u2192 Hooks config: ${hooksPath}`);
3207
- const mcpPath = join3(cursorDir, "mcp.json");
4801
+ const mcpPath = join5(cursorDir, "mcp.json");
3208
4802
  let mcpConfig = {};
3209
- if (existsSync4(mcpPath)) {
4803
+ if (existsSync6(mcpPath)) {
3210
4804
  try {
3211
- mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
4805
+ mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf8"));
3212
4806
  } catch {
3213
4807
  }
3214
4808
  }
3215
4809
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3216
4810
  mcpConfig.mcpServers["kiro-memory"] = {
3217
4811
  command: "node",
3218
- args: [join3(distDir, "servers", "mcp-server.js")]
4812
+ args: [join5(distDir, "servers", "mcp-server.js")]
3219
4813
  };
3220
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
4814
+ writeFileSync3(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3221
4815
  console.log(` \u2192 MCP config: ${mcpPath}`);
3222
4816
  console.log(` \u2192 Data dir: ${dataDir}`);
3223
4817
  console.log("\n[3/3] Done!\n");
@@ -3257,25 +4851,25 @@ async function installWindsurf() {
3257
4851
  }
3258
4852
  }
3259
4853
  const distDir = DIST_DIR;
3260
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
4854
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".kiro-memory");
3261
4855
  console.log("[2/3] Installing Windsurf configuration...\n");
3262
- mkdirSync3(dataDir, { recursive: true });
3263
- const windsurfDir = join3(homedir3(), ".codeium", "windsurf");
3264
- mkdirSync3(windsurfDir, { recursive: true });
3265
- const mcpPath = join3(windsurfDir, "mcp_config.json");
4856
+ mkdirSync5(dataDir, { recursive: true });
4857
+ const windsurfDir = join5(homedir4(), ".codeium", "windsurf");
4858
+ mkdirSync5(windsurfDir, { recursive: true });
4859
+ const mcpPath = join5(windsurfDir, "mcp_config.json");
3266
4860
  let mcpConfig = {};
3267
- if (existsSync4(mcpPath)) {
4861
+ if (existsSync6(mcpPath)) {
3268
4862
  try {
3269
- mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
4863
+ mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf8"));
3270
4864
  } catch {
3271
4865
  }
3272
4866
  }
3273
4867
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3274
4868
  mcpConfig.mcpServers["kiro-memory"] = {
3275
4869
  command: "node",
3276
- args: [join3(distDir, "servers", "mcp-server.js")]
4870
+ args: [join5(distDir, "servers", "mcp-server.js")]
3277
4871
  };
3278
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
4872
+ writeFileSync3(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3279
4873
  console.log(` \u2192 MCP config: ${mcpPath}`);
3280
4874
  console.log(` \u2192 Data dir: ${dataDir}`);
3281
4875
  console.log("\n[3/3] Done!\n");
@@ -3316,31 +4910,31 @@ async function installCline() {
3316
4910
  }
3317
4911
  }
3318
4912
  const distDir = DIST_DIR;
3319
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
4913
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".kiro-memory");
3320
4914
  console.log("[2/3] Installing Cline configuration...\n");
3321
- mkdirSync3(dataDir, { recursive: true });
4915
+ mkdirSync5(dataDir, { recursive: true });
3322
4916
  const platform2 = process.platform;
3323
4917
  let clineSettingsDir;
3324
4918
  if (platform2 === "darwin") {
3325
- clineSettingsDir = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
4919
+ clineSettingsDir = join5(homedir4(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3326
4920
  } else {
3327
- clineSettingsDir = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
4921
+ clineSettingsDir = join5(homedir4(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3328
4922
  }
3329
- mkdirSync3(clineSettingsDir, { recursive: true });
3330
- const mcpPath = join3(clineSettingsDir, "cline_mcp_settings.json");
4923
+ mkdirSync5(clineSettingsDir, { recursive: true });
4924
+ const mcpPath = join5(clineSettingsDir, "cline_mcp_settings.json");
3331
4925
  let mcpConfig = {};
3332
- if (existsSync4(mcpPath)) {
4926
+ if (existsSync6(mcpPath)) {
3333
4927
  try {
3334
- mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
4928
+ mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf8"));
3335
4929
  } catch {
3336
4930
  }
3337
4931
  }
3338
4932
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3339
4933
  mcpConfig.mcpServers["kiro-memory"] = {
3340
4934
  command: "node",
3341
- args: [join3(distDir, "servers", "mcp-server.js")]
4935
+ args: [join5(distDir, "servers", "mcp-server.js")]
3342
4936
  };
3343
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
4937
+ writeFileSync3(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3344
4938
  console.log(` \u2192 MCP config: ${mcpPath}`);
3345
4939
  console.log(` \u2192 Data dir: ${dataDir}`);
3346
4940
  console.log("\n[3/3] Done!\n");
@@ -3359,20 +4953,20 @@ async function installCline() {
3359
4953
  async function runDoctor() {
3360
4954
  console.log("\n=== Kiro Memory - Diagnostics ===");
3361
4955
  const checks = runEnvironmentChecks();
3362
- const kiroDir = process.env.KIRO_CONFIG_DIR || join3(homedir3(), ".kiro");
3363
- const agentPath = join3(kiroDir, "agents", "kiro-memory.json");
3364
- const mcpPath = join3(kiroDir, "settings", "mcp.json");
3365
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
4956
+ const kiroDir = process.env.KIRO_CONFIG_DIR || join5(homedir4(), ".kiro");
4957
+ const agentPath = join5(kiroDir, "agents", "kiro-memory.json");
4958
+ const mcpPath = join5(kiroDir, "settings", "mcp.json");
4959
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join5(homedir4(), ".contextkit");
3366
4960
  checks.push({
3367
4961
  name: "Kiro agent config",
3368
- ok: existsSync4(agentPath),
3369
- message: existsSync4(agentPath) ? agentPath : "Not found",
3370
- fix: !existsSync4(agentPath) ? "Run: kiro-memory install" : void 0
4962
+ ok: existsSync6(agentPath),
4963
+ message: existsSync6(agentPath) ? agentPath : "Not found",
4964
+ fix: !existsSync6(agentPath) ? "Run: kiro-memory install" : void 0
3371
4965
  });
3372
4966
  let mcpOk = false;
3373
- if (existsSync4(mcpPath)) {
4967
+ if (existsSync6(mcpPath)) {
3374
4968
  try {
3375
- const mcp = JSON.parse(readFileSync2(mcpPath, "utf8"));
4969
+ const mcp = JSON.parse(readFileSync4(mcpPath, "utf8"));
3376
4970
  mcpOk = !!mcp.mcpServers?.["kiro-memory"] || !!mcp.mcpServers?.contextkit;
3377
4971
  } catch {
3378
4972
  }
@@ -3385,15 +4979,15 @@ async function runDoctor() {
3385
4979
  });
3386
4980
  checks.push({
3387
4981
  name: "Data directory",
3388
- ok: existsSync4(dataDir),
3389
- message: existsSync4(dataDir) ? dataDir : "Not created (will be created on first use)"
4982
+ ok: existsSync6(dataDir),
4983
+ message: existsSync6(dataDir) ? dataDir : "Not created (will be created on first use)"
3390
4984
  });
3391
- const claudeDir = join3(homedir3(), ".claude");
3392
- const claudeSettingsPath = join3(claudeDir, "settings.json");
4985
+ const claudeDir = join5(homedir4(), ".claude");
4986
+ const claudeSettingsPath = join5(claudeDir, "settings.json");
3393
4987
  let claudeHooksOk = false;
3394
- if (existsSync4(claudeSettingsPath)) {
4988
+ if (existsSync6(claudeSettingsPath)) {
3395
4989
  try {
3396
- const claudeSettings = JSON.parse(readFileSync2(claudeSettingsPath, "utf8"));
4990
+ const claudeSettings = JSON.parse(readFileSync4(claudeSettingsPath, "utf8"));
3397
4991
  claudeHooksOk = !!(claudeSettings?.SessionStart || claudeSettings?.PostToolUse);
3398
4992
  if (claudeHooksOk) {
3399
4993
  const allSettings = JSON.stringify(claudeSettings);
@@ -3402,11 +4996,11 @@ async function runDoctor() {
3402
4996
  } catch {
3403
4997
  }
3404
4998
  }
3405
- const claudeMcpPath = join3(homedir3(), ".mcp.json");
4999
+ const claudeMcpPath = join5(homedir4(), ".mcp.json");
3406
5000
  let claudeMcpOk = false;
3407
- if (existsSync4(claudeMcpPath)) {
5001
+ if (existsSync6(claudeMcpPath)) {
3408
5002
  try {
3409
- const claudeMcp = JSON.parse(readFileSync2(claudeMcpPath, "utf8"));
5003
+ const claudeMcp = JSON.parse(readFileSync4(claudeMcpPath, "utf8"));
3410
5004
  claudeMcpOk = !!claudeMcp.mcpServers?.["kiro-memory"];
3411
5005
  } catch {
3412
5006
  }
@@ -3414,21 +5008,21 @@ async function runDoctor() {
3414
5008
  checks.push({
3415
5009
  name: "Claude Code hooks",
3416
5010
  ok: true,
3417
- // Non-blocking: installazione opzionale
5011
+ // Non-blocking: optional installation
3418
5012
  message: claudeHooksOk ? "Configured in ~/.claude/settings.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3419
5013
  });
3420
5014
  checks.push({
3421
5015
  name: "Claude Code MCP",
3422
5016
  ok: true,
3423
- // Non-blocking: installazione opzionale
5017
+ // Non-blocking: optional installation
3424
5018
  message: claudeMcpOk ? "kiro-memory registered in ~/.mcp.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3425
5019
  });
3426
- const cursorDir = join3(homedir3(), ".cursor");
3427
- const cursorHooksPath = join3(cursorDir, "hooks.json");
5020
+ const cursorDir = join5(homedir4(), ".cursor");
5021
+ const cursorHooksPath = join5(cursorDir, "hooks.json");
3428
5022
  let cursorHooksOk = false;
3429
- if (existsSync4(cursorHooksPath)) {
5023
+ if (existsSync6(cursorHooksPath)) {
3430
5024
  try {
3431
- const cursorHooks = JSON.parse(readFileSync2(cursorHooksPath, "utf8"));
5025
+ const cursorHooks = JSON.parse(readFileSync4(cursorHooksPath, "utf8"));
3432
5026
  cursorHooksOk = !!(cursorHooks.hooks?.sessionStart || cursorHooks.hooks?.afterFileEdit);
3433
5027
  if (cursorHooksOk) {
3434
5028
  const allHooks = JSON.stringify(cursorHooks.hooks);
@@ -3437,11 +5031,11 @@ async function runDoctor() {
3437
5031
  } catch {
3438
5032
  }
3439
5033
  }
3440
- const cursorMcpPath = join3(cursorDir, "mcp.json");
5034
+ const cursorMcpPath = join5(cursorDir, "mcp.json");
3441
5035
  let cursorMcpOk = false;
3442
- if (existsSync4(cursorMcpPath)) {
5036
+ if (existsSync6(cursorMcpPath)) {
3443
5037
  try {
3444
- const cursorMcp = JSON.parse(readFileSync2(cursorMcpPath, "utf8"));
5038
+ const cursorMcp = JSON.parse(readFileSync4(cursorMcpPath, "utf8"));
3445
5039
  cursorMcpOk = !!cursorMcp.mcpServers?.["kiro-memory"];
3446
5040
  } catch {
3447
5041
  }
@@ -3449,20 +5043,20 @@ async function runDoctor() {
3449
5043
  checks.push({
3450
5044
  name: "Cursor hooks",
3451
5045
  ok: true,
3452
- // Non-blocking: installazione opzionale
5046
+ // Non-blocking: optional installation
3453
5047
  message: cursorHooksOk ? "Configured in ~/.cursor/hooks.json" : "Not configured (optional: run kiro-memory install --cursor)"
3454
5048
  });
3455
5049
  checks.push({
3456
5050
  name: "Cursor MCP",
3457
5051
  ok: true,
3458
- // Non-blocking: installazione opzionale
5052
+ // Non-blocking: optional installation
3459
5053
  message: cursorMcpOk ? "kiro-memory registered in ~/.cursor/mcp.json" : "Not configured (optional: run kiro-memory install --cursor)"
3460
5054
  });
3461
- const windsurfMcpPath = join3(homedir3(), ".codeium", "windsurf", "mcp_config.json");
5055
+ const windsurfMcpPath = join5(homedir4(), ".codeium", "windsurf", "mcp_config.json");
3462
5056
  let windsurfMcpOk = false;
3463
- if (existsSync4(windsurfMcpPath)) {
5057
+ if (existsSync6(windsurfMcpPath)) {
3464
5058
  try {
3465
- const windsurfMcp = JSON.parse(readFileSync2(windsurfMcpPath, "utf8"));
5059
+ const windsurfMcp = JSON.parse(readFileSync4(windsurfMcpPath, "utf8"));
3466
5060
  windsurfMcpOk = !!windsurfMcp.mcpServers?.["kiro-memory"];
3467
5061
  } catch {
3468
5062
  }
@@ -3470,21 +5064,21 @@ async function runDoctor() {
3470
5064
  checks.push({
3471
5065
  name: "Windsurf MCP",
3472
5066
  ok: true,
3473
- // Non-blocking: installazione opzionale
5067
+ // Non-blocking: optional installation
3474
5068
  message: windsurfMcpOk ? "kiro-memory registered in ~/.codeium/windsurf/mcp_config.json" : "Not configured (optional: run kiro-memory install --windsurf)"
3475
5069
  });
3476
5070
  const clinePlatform = process.platform;
3477
5071
  let clineSettingsBase;
3478
5072
  if (clinePlatform === "darwin") {
3479
- clineSettingsBase = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
5073
+ clineSettingsBase = join5(homedir4(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3480
5074
  } else {
3481
- clineSettingsBase = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
5075
+ clineSettingsBase = join5(homedir4(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3482
5076
  }
3483
- const clineMcpPath = join3(clineSettingsBase, "cline_mcp_settings.json");
5077
+ const clineMcpPath = join5(clineSettingsBase, "cline_mcp_settings.json");
3484
5078
  let clineMcpOk = false;
3485
- if (existsSync4(clineMcpPath)) {
5079
+ if (existsSync6(clineMcpPath)) {
3486
5080
  try {
3487
- const clineMcp = JSON.parse(readFileSync2(clineMcpPath, "utf8"));
5081
+ const clineMcp = JSON.parse(readFileSync4(clineMcpPath, "utf8"));
3488
5082
  clineMcpOk = !!clineMcp.mcpServers?.["kiro-memory"];
3489
5083
  } catch {
3490
5084
  }
@@ -3492,7 +5086,7 @@ async function runDoctor() {
3492
5086
  checks.push({
3493
5087
  name: "Cline MCP",
3494
5088
  ok: true,
3495
- // Non-blocking: installazione opzionale
5089
+ // Non-blocking: optional installation
3496
5090
  message: clineMcpOk ? `kiro-memory registered in cline_mcp_settings.json` : "Not configured (optional: run kiro-memory install --cline)"
3497
5091
  });
3498
5092
  let workerOk = false;
@@ -3535,9 +5129,42 @@ async function main() {
3535
5129
  return;
3536
5130
  }
3537
5131
  if (command === "doctor") {
5132
+ if (args.includes("--fix")) {
5133
+ await runDoctorFix();
5134
+ return;
5135
+ }
3538
5136
  await runDoctor();
3539
5137
  return;
3540
5138
  }
5139
+ if (command === "export") {
5140
+ const sdk2 = createKiroMemory();
5141
+ try {
5142
+ await exportObservations(sdk2, args.slice(1));
5143
+ } finally {
5144
+ sdk2.close();
5145
+ }
5146
+ return;
5147
+ }
5148
+ if (command === "import") {
5149
+ await importObservations(args.slice(1));
5150
+ return;
5151
+ }
5152
+ if (command === "stats") {
5153
+ await showStats();
5154
+ return;
5155
+ }
5156
+ if (command === "config") {
5157
+ await handleConfig(args.slice(1));
5158
+ return;
5159
+ }
5160
+ if (command === "backup") {
5161
+ await handleBackup(args.slice(1));
5162
+ return;
5163
+ }
5164
+ if (command === "plugins") {
5165
+ await handlePlugins(args.slice(1));
5166
+ return;
5167
+ }
3541
5168
  const sdk = createKiroMemory();
3542
5169
  try {
3543
5170
  switch (command) {
@@ -3546,7 +5173,11 @@ async function main() {
3546
5173
  await showContext(sdk);
3547
5174
  break;
3548
5175
  case "search":
3549
- await searchContext(sdk, args[1]);
5176
+ if (args.includes("--interactive") || args.includes("-i")) {
5177
+ await searchInteractive(sdk, args.slice(1));
5178
+ } else {
5179
+ await searchContext(sdk, args[1]);
5180
+ }
3550
5181
  break;
3551
5182
  case "observations":
3552
5183
  case "obs":
@@ -3899,7 +5530,7 @@ async function generateReportCli(sdk, cliArgs) {
3899
5530
  output = formatReportText(data);
3900
5531
  }
3901
5532
  if (outputArg) {
3902
- writeFileSync(outputArg, output, "utf8");
5533
+ writeFileSync3(outputArg, output, "utf8");
3903
5534
  console.log(`
3904
5535
  Report saved to: ${outputArg}
3905
5536
  `);
@@ -3943,6 +5574,612 @@ async function resumeSession(sdk, sessionId) {
3943
5574
  }
3944
5575
  console.log("");
3945
5576
  }
5577
+ async function searchInteractive(sdk, cliArgs) {
5578
+ const projectArg = cliArgs.find((a, i) => cliArgs[i - 1] === "--project") || cliArgs.find((a) => a.startsWith("--project="))?.split("=").slice(1).join("=");
5579
+ const isInteractive = cliArgs.includes("--interactive") || cliArgs.includes("-i");
5580
+ if (!isInteractive || !process.stdin.isTTY) {
5581
+ const queryArg = cliArgs.find((a) => !a.startsWith("-") && a !== "search");
5582
+ if (!queryArg) {
5583
+ console.error("Errore: fornisci un termine di ricerca o usa --interactive con un TTY");
5584
+ process.exit(1);
5585
+ }
5586
+ const results = projectArg ? await sdk.searchAdvanced(queryArg, { project: projectArg }) : await sdk.search(queryArg);
5587
+ const obs = results.observations.slice(0, 20);
5588
+ if (obs.length === 0) {
5589
+ console.log("\nNessun risultato trovato.\n");
5590
+ return;
5591
+ }
5592
+ console.log(`
5593
+ Risultati per: "${queryArg}"
5594
+ `);
5595
+ obs.forEach((o, i) => {
5596
+ const date = new Date(o.created_at).toLocaleDateString("it-IT");
5597
+ console.log(` ${i + 1}. [${o.type}] ${o.title} \u2014 ${o.project} (${date})`);
5598
+ });
5599
+ console.log("");
5600
+ return;
5601
+ }
5602
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
5603
+ const prompt = (question) => new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
5604
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
5605
+ const cyan = (s) => useColor ? `\x1B[36m${s}\x1B[0m` : s;
5606
+ const bold = (s) => useColor ? `\x1B[1m${s}\x1B[0m` : s;
5607
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[0m` : s;
5608
+ console.log(`
5609
+ ${cyan("=== Kiro Memory \u2014 Ricerca Interattiva ===")}`);
5610
+ if (projectArg) console.log(dim(` Filtro progetto: ${projectArg}`));
5611
+ console.log(dim(' Premi Ctrl+C o digita "exit" per uscire.\n'));
5612
+ while (true) {
5613
+ let query;
5614
+ try {
5615
+ query = await prompt(cyan("> "));
5616
+ } catch {
5617
+ break;
5618
+ }
5619
+ if (!query || query.toLowerCase() === "exit" || query.toLowerCase() === "quit") break;
5620
+ const results = projectArg ? await sdk.searchAdvanced(query, { project: projectArg }) : await sdk.search(query);
5621
+ const obs = results.observations.slice(0, 20);
5622
+ if (obs.length === 0) {
5623
+ console.log(dim("\n Nessun risultato trovato.\n"));
5624
+ continue;
5625
+ }
5626
+ console.log(`
5627
+ ${bold(`${obs.length} risultato/i:`)}
5628
+ `);
5629
+ obs.forEach((o, i) => {
5630
+ const date = new Date(o.created_at).toLocaleDateString("it-IT");
5631
+ console.log(` ${bold(`${i + 1}.`)} [${o.type}] ${o.title}`);
5632
+ console.log(dim(` ${o.project} \u2014 ${date}`));
5633
+ });
5634
+ console.log("");
5635
+ const selRaw = await prompt(` Numero per dettagli (Invio per saltare): `);
5636
+ const selIdx = parseInt(selRaw) - 1;
5637
+ if (!isNaN(selIdx) && selIdx >= 0 && selIdx < obs.length) {
5638
+ const o = obs[selIdx];
5639
+ console.log("");
5640
+ console.log(` ${bold("Titolo:")} ${o.title}`);
5641
+ console.log(` ${bold("Tipo:")} ${o.type}`);
5642
+ console.log(` ${bold("Progetto:")} ${o.project}`);
5643
+ console.log(` ${bold("Data:")} ${new Date(o.created_at).toLocaleString("it-IT")}`);
5644
+ if (o.text) {
5645
+ console.log(` ${bold("Contenuto:")}`);
5646
+ console.log(` ${o.text.substring(0, 500)}${o.text.length > 500 ? "..." : ""}`);
5647
+ }
5648
+ if (o.narrative) {
5649
+ console.log(` ${bold("Narrativa:")}`);
5650
+ console.log(` ${o.narrative.substring(0, 300)}${o.narrative.length > 300 ? "..." : ""}`);
5651
+ }
5652
+ console.log("");
5653
+ }
5654
+ }
5655
+ rl.close();
5656
+ console.log("\n Uscita dalla modalit\xE0 interattiva.\n");
5657
+ }
5658
+ async function exportObservations(sdk, cliArgs) {
5659
+ const formatArg = cliArgs.find((a) => a.startsWith("--format="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => cliArgs[i - 1] === "--format");
5660
+ const projectArg = cliArgs.find((a) => a.startsWith("--project="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => cliArgs[i - 1] === "--project");
5661
+ const outputArg = cliArgs.find((a) => a.startsWith("-o="))?.split("=").slice(1).join("=") || cliArgs.find((a) => a.startsWith("--output="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => (cliArgs[i - 1] === "--output" || cliArgs[i - 1] === "-o") && !a.startsWith("-"));
5662
+ const fromArg = cliArgs.find((a) => a.startsWith("--from="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => cliArgs[i - 1] === "--from" && !a.startsWith("-"));
5663
+ const toArg = cliArgs.find((a) => a.startsWith("--to="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => cliArgs[i - 1] === "--to" && !a.startsWith("-"));
5664
+ const typeArg = cliArgs.find((a) => a.startsWith("--type="))?.split("=").slice(1).join("=") || cliArgs.find((a, i) => cliArgs[i - 1] === "--type" && !a.startsWith("-"));
5665
+ const validFormats = ["jsonl", "json", "md"];
5666
+ const format = validFormats.includes(formatArg) ? formatArg : "jsonl";
5667
+ if (format === "json" || format === "md") {
5668
+ if (!projectArg) {
5669
+ console.error("Errore: --project <nome> \xE8 obbligatorio per il formato json/md");
5670
+ process.exit(1);
5671
+ }
5672
+ const kmDb2 = new KiroMemoryDatabase();
5673
+ let observations;
5674
+ try {
5675
+ observations = getObservationsByProject(kmDb2.db, projectArg, 1e4);
5676
+ } finally {
5677
+ kmDb2.close();
5678
+ }
5679
+ if (observations.length === 0) {
5680
+ console.error(`Nessuna observation trovata per il progetto "${projectArg}"`);
5681
+ process.exit(1);
5682
+ }
5683
+ const output = generateExportOutput(observations, format);
5684
+ if (outputArg) {
5685
+ writeFileSync3(outputArg, output, "utf8");
5686
+ console.error(`
5687
+ Esportate ${observations.length} observations in: ${outputArg}
5688
+ `);
5689
+ } else {
5690
+ process.stdout.write(output + "\n");
5691
+ }
5692
+ return;
5693
+ }
5694
+ const { generateMetaRecord: generateMetaRecord2, exportObservationsStreaming: exportObservationsStreaming2, exportSummariesStreaming: exportSummariesStreaming2, exportPromptsStreaming: exportPromptsStreaming2 } = await Promise.resolve().then(() => (init_ImportExport(), ImportExport_exports));
5695
+ const filters = {};
5696
+ if (projectArg) filters.project = projectArg;
5697
+ if (typeArg) filters.type = typeArg;
5698
+ if (fromArg) filters.from = fromArg;
5699
+ if (toArg) filters.to = toArg;
5700
+ const kmDb = new KiroMemoryDatabase();
5701
+ try {
5702
+ if (outputArg) {
5703
+ const { createWriteStream } = await import("fs");
5704
+ const stream = createWriteStream(outputArg, { encoding: "utf8" });
5705
+ let obsCount = 0;
5706
+ let sumCount = 0;
5707
+ let promptCount = 0;
5708
+ stream.write(generateMetaRecord2(kmDb.db, filters) + "\n");
5709
+ obsCount = exportObservationsStreaming2(kmDb.db, filters, (line) => {
5710
+ stream.write(line + "\n");
5711
+ });
5712
+ sumCount = exportSummariesStreaming2(kmDb.db, filters, (line) => {
5713
+ stream.write(line + "\n");
5714
+ });
5715
+ promptCount = exportPromptsStreaming2(kmDb.db, filters, (line) => {
5716
+ stream.write(line + "\n");
5717
+ });
5718
+ await new Promise((resolve, reject) => {
5719
+ stream.end((err) => err ? reject(err) : resolve());
5720
+ });
5721
+ console.error(`
5722
+ Export JSONL completato:`);
5723
+ console.error(` Observations: ${obsCount}`);
5724
+ console.error(` Summaries: ${sumCount}`);
5725
+ console.error(` Prompts: ${promptCount}`);
5726
+ console.error(` File: ${outputArg}
5727
+ `);
5728
+ } else {
5729
+ process.stdout.write(generateMetaRecord2(kmDb.db, filters) + "\n");
5730
+ exportObservationsStreaming2(kmDb.db, filters, (line) => process.stdout.write(line + "\n"));
5731
+ exportSummariesStreaming2(kmDb.db, filters, (line) => process.stdout.write(line + "\n"));
5732
+ exportPromptsStreaming2(kmDb.db, filters, (line) => process.stdout.write(line + "\n"));
5733
+ }
5734
+ } finally {
5735
+ kmDb.close();
5736
+ }
5737
+ }
5738
+ async function importObservations(cliArgs) {
5739
+ const filePath = cliArgs.find((a) => !a.startsWith("-"));
5740
+ const dryRun = cliArgs.includes("--dry-run");
5741
+ if (!filePath) {
5742
+ console.error("Errore: specifica il percorso del file JSONL\n kiro-memory import <file.jsonl> [--dry-run]");
5743
+ process.exit(1);
5744
+ }
5745
+ if (!existsSync6(filePath)) {
5746
+ console.error(`Errore: file non trovato: ${filePath}`);
5747
+ process.exit(1);
5748
+ }
5749
+ let content;
5750
+ try {
5751
+ content = readFileSync4(filePath, "utf8");
5752
+ } catch (err) {
5753
+ console.error(`Errore lettura file: ${err.message}`);
5754
+ process.exit(1);
5755
+ }
5756
+ if (dryRun) {
5757
+ console.log(`
5758
+ [DRY RUN] Analisi di "${filePath}"...
5759
+ `);
5760
+ } else {
5761
+ console.log(`
5762
+ Importazione di "${filePath}"...
5763
+ `);
5764
+ }
5765
+ const { importJsonl: importJsonl2 } = await Promise.resolve().then(() => (init_ImportExport(), ImportExport_exports));
5766
+ const { formatImportResult: formatImportResult2 } = await Promise.resolve().then(() => (init_cli_utils(), cli_utils_exports));
5767
+ const kmDb = new KiroMemoryDatabase();
5768
+ let result;
5769
+ try {
5770
+ result = importJsonl2(kmDb.db, content, dryRun);
5771
+ } finally {
5772
+ kmDb.close();
5773
+ }
5774
+ const output = formatImportResult2({
5775
+ imported: result.imported,
5776
+ skipped: result.skipped,
5777
+ errors: result.errors,
5778
+ total: result.total,
5779
+ dryRun,
5780
+ errorDetails: result.errorDetails
5781
+ });
5782
+ console.log(output);
5783
+ if (result.imported === 0 && result.errors > 0 && result.skipped === 0) {
5784
+ process.exit(1);
5785
+ }
5786
+ }
5787
+ async function runDoctorFix() {
5788
+ console.log("\n=== Kiro Memory \u2014 Riparazione Database ===\n");
5789
+ const kmDb = new KiroMemoryDatabase();
5790
+ const db = kmDb.db;
5791
+ const messages = [];
5792
+ try {
5793
+ process.stdout.write(" [1/3] Ricostruzione indice FTS5... ");
5794
+ const ftsOk = rebuildFtsIndex(db);
5795
+ if (ftsOk) {
5796
+ console.log("\x1B[32m\u2713\x1B[0m");
5797
+ messages.push("Indice FTS5 ricostruito");
5798
+ } else {
5799
+ console.log("\x1B[33m~\x1B[0m (FTS non disponibile o gia' integro)");
5800
+ }
5801
+ process.stdout.write(" [2/3] Rimozione embeddings orfani... ");
5802
+ const removed = removeOrphanedEmbeddings(db);
5803
+ console.log(`\x1B[32m\u2713\x1B[0m (${removed} rimossi)`);
5804
+ if (removed > 0) messages.push(`${removed} embedding/s orfani rimossi`);
5805
+ process.stdout.write(" [3/3] VACUUM database... ");
5806
+ const vacuumOk = vacuumDatabase(db);
5807
+ if (vacuumOk) {
5808
+ console.log("\x1B[32m\u2713\x1B[0m");
5809
+ messages.push("VACUUM completato");
5810
+ } else {
5811
+ console.log("\x1B[31m\u2717\x1B[0m");
5812
+ }
5813
+ } finally {
5814
+ kmDb.close();
5815
+ }
5816
+ if (messages.length > 0) {
5817
+ console.log("\n Operazioni completate:");
5818
+ for (const msg of messages) {
5819
+ console.log(` \x1B[32m\u2713\x1B[0m ${msg}`);
5820
+ }
5821
+ }
5822
+ console.log("");
5823
+ }
5824
+ async function showStats() {
5825
+ const kmDb = new KiroMemoryDatabase();
5826
+ const db = kmDb.db;
5827
+ try {
5828
+ const obsRow = db.query(
5829
+ "SELECT COUNT(*) as total FROM observations"
5830
+ ).get();
5831
+ const sessRow = db.query(
5832
+ "SELECT COUNT(*) as total FROM sessions"
5833
+ ).get();
5834
+ const projRow = db.query(
5835
+ "SELECT COUNT(DISTINCT project) as cnt FROM observations"
5836
+ ).get();
5837
+ const topProject = db.query(
5838
+ `SELECT project, COUNT(*) as cnt
5839
+ FROM observations
5840
+ GROUP BY project
5841
+ ORDER BY cnt DESC
5842
+ LIMIT 1`
5843
+ ).get();
5844
+ let embCoverage = 0;
5845
+ try {
5846
+ const embStats = db.query(
5847
+ `SELECT
5848
+ (SELECT COUNT(*) FROM observations) as total,
5849
+ COUNT(DISTINCT observation_id) as embedded
5850
+ FROM observation_embeddings`
5851
+ ).get();
5852
+ if (embStats && embStats.total > 0) {
5853
+ embCoverage = Math.round(embStats.embedded / embStats.total * 100);
5854
+ }
5855
+ } catch {
5856
+ }
5857
+ const dbSize = getDbFileSize(DB_PATH);
5858
+ const stats = {
5859
+ totalObservations: obsRow?.total || 0,
5860
+ totalSessions: sessRow?.total || 0,
5861
+ totalProjects: projRow?.cnt || 0,
5862
+ dbSizeBytes: dbSize,
5863
+ mostActiveProject: topProject?.project || null,
5864
+ embeddingCoverage: embCoverage
5865
+ };
5866
+ console.log(formatStatsOutput(stats));
5867
+ } finally {
5868
+ kmDb.close();
5869
+ }
5870
+ }
5871
+ async function handleConfig(subArgs) {
5872
+ const subcommand = subArgs[0];
5873
+ const configPath = getConfigPath();
5874
+ switch (subcommand) {
5875
+ case "list": {
5876
+ const config = listConfig(configPath);
5877
+ console.log("\n=== Configurazione Kiro Memory ===\n");
5878
+ console.log(` File: ${configPath}
5879
+ `);
5880
+ for (const [key, value] of Object.entries(config)) {
5881
+ const displayValue = value === null ? "(non impostato)" : String(value);
5882
+ console.log(` ${key.padEnd(35)} ${displayValue}`);
5883
+ }
5884
+ console.log("");
5885
+ break;
5886
+ }
5887
+ case "get": {
5888
+ const key = subArgs[1];
5889
+ if (!key) {
5890
+ console.error("Errore: specifica una chiave\n kiro-memory config get <chiave>");
5891
+ process.exit(1);
5892
+ }
5893
+ const val = getConfigValue(key, configPath);
5894
+ if (val === null) {
5895
+ console.log(`
5896
+ "${key}" non impostato (nessun valore di default)
5897
+ `);
5898
+ } else {
5899
+ console.log(`
5900
+ ${key} = ${val}
5901
+ `);
5902
+ }
5903
+ break;
5904
+ }
5905
+ case "set": {
5906
+ const key = subArgs[1];
5907
+ const rawValue = subArgs[2];
5908
+ if (!key) {
5909
+ console.error("Errore: specifica chiave e valore\n kiro-memory config set <chiave> <valore>");
5910
+ process.exit(1);
5911
+ }
5912
+ if (rawValue === void 0) {
5913
+ console.error(`Errore: valore mancante per "${key}"
5914
+ kiro-memory config set ${key} <valore>`);
5915
+ process.exit(1);
5916
+ }
5917
+ const saved = setConfigValue(key, rawValue, configPath);
5918
+ console.log(`
5919
+ Impostato: ${key} = ${saved}
5920
+ `);
5921
+ break;
5922
+ }
5923
+ default:
5924
+ console.log("\nUtilizzo: kiro-memory config <subcommand>\n");
5925
+ console.log("Subcommands:");
5926
+ console.log(" list Mostra tutte le impostazioni");
5927
+ console.log(" get <chiave> Legge un valore");
5928
+ console.log(" set <chiave> <valore> Imposta un valore\n");
5929
+ console.log("Esempio:");
5930
+ console.log(" kiro-memory config list");
5931
+ console.log(" kiro-memory config get worker.port");
5932
+ console.log(" kiro-memory config set log.level DEBUG\n");
5933
+ }
5934
+ }
5935
+ async function handleBackup(subArgs) {
5936
+ const subCommand = subArgs[0];
5937
+ if (!subCommand || subCommand === "help") {
5938
+ console.log(`
5939
+ Uso: kiro-memory backup <sottocomando>
5940
+
5941
+ Sottocomandi:
5942
+ create Crea un backup manuale del database
5943
+ list Elenca i backup disponibili con metadata
5944
+ restore <file> Ripristina il database da un file backup
5945
+ `);
5946
+ return;
5947
+ }
5948
+ if (subCommand === "create") {
5949
+ const maxKeep = Number(getConfigValue("backup.maxKeep")) || 7;
5950
+ const db = new KiroMemoryDatabase(DB_PATH, true);
5951
+ try {
5952
+ const entry = createBackup(DB_PATH, BACKUPS_DIR, db.db);
5953
+ const deleted = rotateBackups(BACKUPS_DIR, maxKeep);
5954
+ console.log(`
5955
+ === Kiro Memory \u2014 Backup Creato ===
5956
+ `);
5957
+ console.log(` File: ${entry.metadata.filename}`);
5958
+ console.log(` Timestamp: ${entry.metadata.timestamp}`);
5959
+ console.log(` Schema v.: ${entry.metadata.schemaVersion}`);
5960
+ console.log(` Obs.: ${entry.metadata.stats.observations}`);
5961
+ console.log(` Sessioni: ${entry.metadata.stats.sessions}`);
5962
+ console.log(` Dimensione: ${(entry.metadata.stats.dbSizeBytes / 1024).toFixed(1)} KB`);
5963
+ if (deleted > 0) {
5964
+ console.log(` Rotazione: ${deleted} backup rimossi (max ${maxKeep} mantenuti)`);
5965
+ }
5966
+ console.log(`
5967
+ Directory: ${BACKUPS_DIR}
5968
+ `);
5969
+ } finally {
5970
+ db.close();
5971
+ }
5972
+ return;
5973
+ }
5974
+ if (subCommand === "list") {
5975
+ const entries = listBackups(BACKUPS_DIR);
5976
+ if (entries.length === 0) {
5977
+ console.log("\n Nessun backup trovato in: " + BACKUPS_DIR + "\n");
5978
+ return;
5979
+ }
5980
+ console.log(`
5981
+ === Kiro Memory \u2014 Backup Disponibili ===
5982
+ `);
5983
+ console.log(` Directory: ${BACKUPS_DIR}
5984
+ `);
5985
+ for (let i = 0; i < entries.length; i++) {
5986
+ const e = entries[i];
5987
+ const size = (e.metadata.stats.dbSizeBytes / 1024).toFixed(1);
5988
+ const date = new Date(e.metadata.timestampEpoch).toLocaleString("it-IT");
5989
+ console.log(` ${i + 1}. ${e.metadata.filename}`);
5990
+ console.log(` Data: ${date}`);
5991
+ console.log(` Schema: v${e.metadata.schemaVersion}`);
5992
+ console.log(` Obs.: ${e.metadata.stats.observations} | Sessioni: ${e.metadata.stats.sessions}`);
5993
+ console.log(` Dimensione: ${size} KB`);
5994
+ console.log("");
5995
+ }
5996
+ return;
5997
+ }
5998
+ if (subCommand === "restore") {
5999
+ const file = subArgs[1];
6000
+ if (!file) {
6001
+ console.error("\n Errore: specifica il nome del file backup da ripristinare.");
6002
+ console.error(" Esempio: kiro-memory backup restore backup-2026-02-27-150000.db\n");
6003
+ process.exit(1);
6004
+ }
6005
+ const backupPattern = /^backup-\d{4}-\d{2}-\d{2}-\d{6}(-\d{3})?\.db$/;
6006
+ if (file.includes("/") || file.includes("..") || !backupPattern.test(file)) {
6007
+ console.error(`
6008
+ Errore: nome file non valido: ${file}`);
6009
+ console.error(' Il file deve essere nel formato "backup-YYYY-MM-DD-HHmmss[-mmm].db"\n');
6010
+ process.exit(1);
6011
+ }
6012
+ const entries = listBackups(BACKUPS_DIR);
6013
+ const found = entries.find((e) => e.metadata.filename === file);
6014
+ if (!found) {
6015
+ console.error(`
6016
+ Errore: backup non trovato: ${file}`);
6017
+ console.error(` Usa "kiro-memory backup list" per vedere i backup disponibili.
6018
+ `);
6019
+ process.exit(1);
6020
+ }
6021
+ const date = new Date(found.metadata.timestampEpoch).toLocaleString("it-IT");
6022
+ console.log(`
6023
+ ATTENZIONE: questa operazione sovrascrive il database corrente!`);
6024
+ console.log(` Backup da ripristinare: ${file}`);
6025
+ console.log(` Data backup: ${date}`);
6026
+ console.log(` Obs. nel backup: ${found.metadata.stats.observations}`);
6027
+ console.log("");
6028
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
6029
+ const confirmed = await new Promise((resolve) => {
6030
+ rl.question(' Confermi il ripristino? (digita "si" per confermare): ', (answer) => {
6031
+ rl.close();
6032
+ resolve(answer.trim().toLowerCase() === "si");
6033
+ });
6034
+ });
6035
+ if (!confirmed) {
6036
+ console.log("\n Ripristino annullato.\n");
6037
+ return;
6038
+ }
6039
+ restoreBackup(found.filePath, DB_PATH);
6040
+ console.log(`
6041
+ Database ripristinato da: ${file}`);
6042
+ console.log(" Riavvia il worker per applicare le modifiche.\n");
6043
+ return;
6044
+ }
6045
+ console.error(`
6046
+ Sottocomando backup non riconosciuto: ${subCommand}`);
6047
+ console.error(" Usa: create | list | restore\n");
6048
+ process.exit(1);
6049
+ }
6050
+ async function handlePlugins(subArgs) {
6051
+ const subCommand = subArgs[0];
6052
+ const port = process.env.KIRO_MEMORY_WORKER_PORT || process.env.CONTEXTKIT_WORKER_PORT || "3001";
6053
+ const baseUrl = `http://127.0.0.1:${port}`;
6054
+ async function apiGet(path) {
6055
+ return new Promise((resolve, reject) => {
6056
+ const req = http.get(`${baseUrl}${path}`, (res) => {
6057
+ let body = "";
6058
+ res.on("data", (chunk) => {
6059
+ body += chunk;
6060
+ });
6061
+ res.on("end", () => {
6062
+ try {
6063
+ resolve({ status: res.statusCode, data: JSON.parse(body) });
6064
+ } catch {
6065
+ reject(new Error(`Risposta non JSON: ${body}`));
6066
+ }
6067
+ });
6068
+ });
6069
+ req.on("error", reject);
6070
+ req.setTimeout(5e3, () => {
6071
+ req.destroy(new Error("Timeout"));
6072
+ });
6073
+ });
6074
+ }
6075
+ async function apiPost(path) {
6076
+ return new Promise((resolve, reject) => {
6077
+ const options = {
6078
+ hostname: "127.0.0.1",
6079
+ port: parseInt(port, 10),
6080
+ path,
6081
+ method: "POST",
6082
+ headers: { "Content-Type": "application/json", "Content-Length": 0 }
6083
+ };
6084
+ const req = http.request(options, (res) => {
6085
+ let body = "";
6086
+ res.on("data", (chunk) => {
6087
+ body += chunk;
6088
+ });
6089
+ res.on("end", () => {
6090
+ try {
6091
+ resolve({ status: res.statusCode, data: JSON.parse(body) });
6092
+ } catch {
6093
+ reject(new Error(`Risposta non JSON: ${body}`));
6094
+ }
6095
+ });
6096
+ });
6097
+ req.on("error", reject);
6098
+ req.setTimeout(1e4, () => {
6099
+ req.destroy(new Error("Timeout"));
6100
+ });
6101
+ req.end();
6102
+ });
6103
+ }
6104
+ if (!subCommand || subCommand === "list") {
6105
+ try {
6106
+ const result = await apiGet("/api/plugins");
6107
+ const { plugins } = result.data;
6108
+ console.log("\n=== Kiro Memory \u2014 Plugin ===\n");
6109
+ if (!plugins || plugins.length === 0) {
6110
+ console.log(" Nessun plugin registrato.\n");
6111
+ return;
6112
+ }
6113
+ for (const p of plugins) {
6114
+ const stateColor = p.state === "active" ? "\x1B[32m" : p.state === "error" ? "\x1B[31m" : "\x1B[33m";
6115
+ console.log(` ${p.name}@${p.version}`);
6116
+ console.log(` Stato: ${stateColor}${p.state}\x1B[0m`);
6117
+ if (p.description) console.log(` Desc.: ${p.description}`);
6118
+ if (p.error) console.log(` Errore: \x1B[31m${p.error}\x1B[0m`);
6119
+ console.log("");
6120
+ }
6121
+ } catch {
6122
+ console.error("\n Errore: impossibile contattare il worker. Avvialo con: kiro-memory worker start\n");
6123
+ process.exit(1);
6124
+ }
6125
+ return;
6126
+ }
6127
+ if (subCommand === "enable") {
6128
+ const name = subArgs[1];
6129
+ if (!name) {
6130
+ console.error("\n Errore: specifica il nome del plugin.\n Esempio: kiro-memory plugins enable mio-plugin\n");
6131
+ process.exit(1);
6132
+ }
6133
+ try {
6134
+ const result = await apiPost(`/api/plugins/${encodeURIComponent(name)}/enable`);
6135
+ if (result.status === 200) {
6136
+ console.log(`
6137
+ Plugin "${name}" abilitato con successo.`);
6138
+ if (result.data.plugin?.state === "error") {
6139
+ console.log(` Attenzione: stato corrente = error: ${result.data.plugin.error}`);
6140
+ }
6141
+ console.log("");
6142
+ } else {
6143
+ console.error(`
6144
+ Errore: ${result.data.error}
6145
+ `);
6146
+ process.exit(1);
6147
+ }
6148
+ } catch {
6149
+ console.error("\n Errore: impossibile contattare il worker.\n");
6150
+ process.exit(1);
6151
+ }
6152
+ return;
6153
+ }
6154
+ if (subCommand === "disable") {
6155
+ const name = subArgs[1];
6156
+ if (!name) {
6157
+ console.error("\n Errore: specifica il nome del plugin.\n Esempio: kiro-memory plugins disable mio-plugin\n");
6158
+ process.exit(1);
6159
+ }
6160
+ try {
6161
+ const result = await apiPost(`/api/plugins/${encodeURIComponent(name)}/disable`);
6162
+ if (result.status === 200) {
6163
+ console.log(`
6164
+ Plugin "${name}" disabilitato.
6165
+ `);
6166
+ } else {
6167
+ console.error(`
6168
+ Errore: ${result.data.error}
6169
+ `);
6170
+ process.exit(1);
6171
+ }
6172
+ } catch {
6173
+ console.error("\n Errore: impossibile contattare il worker.\n");
6174
+ process.exit(1);
6175
+ }
6176
+ return;
6177
+ }
6178
+ console.error(`
6179
+ Sottocomando plugins non riconosciuto: ${subCommand}`);
6180
+ console.error(" Usa: list | enable <nome> | disable <nome>\n");
6181
+ process.exit(1);
6182
+ }
3946
6183
  function showHelp() {
3947
6184
  console.log(`Usage: kiro-memory <command> [options]
3948
6185
 
@@ -3953,6 +6190,7 @@ Setup:
3953
6190
  install --windsurf Install MCP server for Windsurf IDE
3954
6191
  install --cline Install MCP server for Cline (VS Code)
3955
6192
  doctor Run environment diagnostics (checks Node, build tools, WSL, etc.)
6193
+ doctor --fix Auto-repair: rebuild FTS5, remove orphaned embeddings, VACUUM
3956
6194
 
3957
6195
  Commands:
3958
6196
  context, ctx Show current project context
@@ -3961,8 +6199,18 @@ Commands:
3961
6199
  --period=weekly|monthly Time period (default: weekly)
3962
6200
  --format=text|md|json Output format (default: text)
3963
6201
  --output=<file> Write to file instead of stdout
6202
+ stats Quick database overview (totals, size, active project, embeddings)
3964
6203
  search <query> Search across all context (keyword FTS5)
6204
+ search --interactive Interactive REPL search with result selection
6205
+ --project <name> Filter results by project
3965
6206
  semantic-search <query> Hybrid search: vector + keyword (semantic)
6207
+ export --project <name> Export observations to JSONL/JSON/Markdown
6208
+ --format=jsonl|json|md Output format (default: jsonl)
6209
+ --output=<file> Write to file instead of stdout
6210
+ import <file> Import observations from JSONL file (deduplication by content_hash)
6211
+ config list Show all configuration settings
6212
+ config get <key> Show a single configuration value
6213
+ config set <key> <value> Set a configuration value
3966
6214
  observations [limit] Show recent observations (default: 10)
3967
6215
  summaries [limit] Show recent summaries (default: 5)
3968
6216
  add-observation <title> <content> Add a new observation
@@ -3977,18 +6225,36 @@ Commands:
3977
6225
  decay stats Show decay statistics (stale, never accessed, etc.)
3978
6226
  decay detect-stale Detect and mark stale observations
3979
6227
  decay consolidate [--dry-run] Consolidate duplicate observations
6228
+ backup create Crea un backup manuale del database
6229
+ backup list Elenca tutti i backup disponibili con metadata
6230
+ backup restore <file> Ripristina il database da un backup (con conferma)
6231
+ plugins list Elenca tutti i plugin registrati con stato
6232
+ plugins enable <nome> Abilita un plugin registrato
6233
+ plugins disable <nome> Disabilita un plugin attivo
3980
6234
  help Show this help message
3981
6235
 
3982
6236
  Examples:
3983
6237
  kiro-memory install
3984
6238
  kiro-memory doctor
6239
+ kiro-memory doctor --fix
6240
+ kiro-memory stats
3985
6241
  kiro-memory context
3986
6242
  kiro-memory resume
3987
6243
  kiro-memory resume 42
3988
6244
  kiro-memory report
3989
6245
  kiro-memory report --period=monthly --format=md --output=report.md
3990
6246
  kiro-memory search "authentication"
6247
+ kiro-memory search --interactive --project myapp
3991
6248
  kiro-memory semantic-search "how did I fix the auth bug"
6249
+ kiro-memory export --project myapp --format jsonl --output backup.jsonl
6250
+ kiro-memory export --project myapp --format md > notes.md
6251
+ kiro-memory import backup.jsonl
6252
+ kiro-memory backup create
6253
+ kiro-memory backup list
6254
+ kiro-memory backup restore backup-2026-02-27-150000.db
6255
+ kiro-memory config list
6256
+ kiro-memory config get worker.port
6257
+ kiro-memory config set log.level DEBUG
3992
6258
  kiro-memory add-knowledge constraint "No any in TypeScript" "Never use any type" --severity=hard
3993
6259
  kiro-memory add-knowledge decision "PostgreSQL over MongoDB" "Chosen for ACID" --alternatives=MongoDB,DynamoDB
3994
6260
  kiro-memory embeddings stats