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.
- package/README.md +5 -1
- package/package.json +5 -5
- package/plugin/dist/cli/contextkit.js +2611 -345
- package/plugin/dist/hooks/agentSpawn.js +853 -223
- package/plugin/dist/hooks/kiro-hooks.js +841 -211
- package/plugin/dist/hooks/postToolUse.js +853 -222
- package/plugin/dist/hooks/stop.js +850 -220
- package/plugin/dist/hooks/userPromptSubmit.js +848 -216
- package/plugin/dist/index.js +843 -340
- package/plugin/dist/plugins/github/github-client.js +152 -0
- package/plugin/dist/plugins/github/index.js +412 -0
- package/plugin/dist/plugins/github/issue-parser.js +54 -0
- package/plugin/dist/plugins/slack/formatter.js +90 -0
- package/plugin/dist/plugins/slack/index.js +215 -0
- package/plugin/dist/sdk/index.js +841 -215
- package/plugin/dist/servers/mcp-server.js +4461 -397
- package/plugin/dist/services/search/EmbeddingService.js +146 -37
- package/plugin/dist/services/search/HybridSearch.js +564 -116
- package/plugin/dist/services/search/VectorSearch.js +187 -60
- package/plugin/dist/services/search/index.js +565 -254
- package/plugin/dist/services/sqlite/Backup.js +416 -0
- package/plugin/dist/services/sqlite/Database.js +126 -153
- package/plugin/dist/services/sqlite/ImportExport.js +452 -0
- package/plugin/dist/services/sqlite/Observations.js +314 -19
- package/plugin/dist/services/sqlite/Prompts.js +1 -1
- package/plugin/dist/services/sqlite/Search.js +41 -29
- package/plugin/dist/services/sqlite/Summaries.js +4 -4
- package/plugin/dist/services/sqlite/index.js +1428 -208
- package/plugin/dist/viewer.css +1 -0
- package/plugin/dist/viewer.html +2 -179
- package/plugin/dist/viewer.js +23 -24942
- package/plugin/dist/viewer.js.map +7 -0
- package/plugin/dist/worker-service.js +427 -5569
- 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,
|
|
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
|
-
|
|
328
|
-
|
|
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, `[
|
|
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
|
-
|
|
361
|
-
|
|
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
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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:
|
|
574
|
-
summaries:
|
|
575
|
-
sessions:
|
|
576
|
-
prompts:
|
|
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
|
-
*
|
|
651
|
-
*
|
|
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
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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",
|
|
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",
|
|
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
|
|
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", "
|
|
1511
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
696
1512
|
return false;
|
|
697
1513
|
}
|
|
698
1514
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
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", `
|
|
1529
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
714
1530
|
}
|
|
715
1531
|
return null;
|
|
716
1532
|
}
|
|
717
1533
|
/**
|
|
718
|
-
*
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
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
|
|
1552
|
+
return this._embedBatchSerial(truncated);
|
|
733
1553
|
}
|
|
734
1554
|
/**
|
|
735
|
-
*
|
|
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
|
-
*
|
|
1561
|
+
* Name of the active provider.
|
|
742
1562
|
*/
|
|
743
1563
|
getProvider() {
|
|
744
1564
|
return this.provider;
|
|
745
1565
|
}
|
|
746
1566
|
/**
|
|
747
|
-
*
|
|
1567
|
+
* Embedding vector dimensions for the active model configuration.
|
|
748
1568
|
*/
|
|
749
1569
|
getDimensions() {
|
|
750
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
2013
|
+
* Create a transaction
|
|
804
2014
|
*/
|
|
805
2015
|
transaction(fn) {
|
|
806
2016
|
return this._db.transaction(fn);
|
|
807
2017
|
}
|
|
808
2018
|
/**
|
|
809
|
-
*
|
|
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
|
-
|
|
817
|
-
_sql;
|
|
2027
|
+
_stmt;
|
|
818
2028
|
constructor(db, sql) {
|
|
819
|
-
this.
|
|
820
|
-
this._sql = sql;
|
|
2029
|
+
this._stmt = db.prepare(sql);
|
|
821
2030
|
}
|
|
822
2031
|
/**
|
|
823
|
-
*
|
|
2032
|
+
* Returns all rows
|
|
824
2033
|
*/
|
|
825
2034
|
all(...params) {
|
|
826
|
-
|
|
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
|
-
*
|
|
2038
|
+
* Returns the first row or null
|
|
831
2039
|
*/
|
|
832
2040
|
get(...params) {
|
|
833
|
-
|
|
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
|
-
*
|
|
2044
|
+
* Execute without results
|
|
838
2045
|
*/
|
|
839
2046
|
run(...params) {
|
|
840
|
-
|
|
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
|
-
|
|
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 -
|
|
887
|
-
* @param skipMigrations -
|
|
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.
|
|
894
|
-
this.
|
|
895
|
-
this.
|
|
896
|
-
this.
|
|
897
|
-
this.
|
|
898
|
-
this.
|
|
899
|
-
this.
|
|
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.
|
|
2115
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
902
2116
|
migrationRunner.runAllMigrations();
|
|
903
2117
|
}
|
|
904
2118
|
}
|
|
905
2119
|
/**
|
|
906
|
-
*
|
|
907
|
-
*
|
|
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.
|
|
911
|
-
return transaction(this.
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 <
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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", `
|
|
3009
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1474
3010
|
return [];
|
|
1475
3011
|
}
|
|
1476
3012
|
}
|
|
1477
3013
|
/**
|
|
1478
|
-
*
|
|
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
|
|
3030
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1495
3031
|
} catch (error) {
|
|
1496
|
-
logger.error("VECTOR", `
|
|
3032
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1497
3033
|
}
|
|
1498
3034
|
}
|
|
1499
3035
|
/**
|
|
1500
|
-
*
|
|
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
|
|
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.
|
|
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
|
|
3067
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1532
3068
|
return count;
|
|
1533
3069
|
}
|
|
1534
3070
|
/**
|
|
1535
|
-
*
|
|
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
|
-
*
|
|
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
|
|
3152
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1617
3153
|
} catch (error) {
|
|
1618
|
-
logger.warn("SEARCH", "
|
|
3154
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1619
3155
|
this.embeddingInitialized = false;
|
|
1620
3156
|
}
|
|
1621
3157
|
}
|
|
1622
3158
|
/**
|
|
1623
|
-
*
|
|
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
|
-
//
|
|
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}
|
|
3192
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1657
3193
|
}
|
|
1658
3194
|
} catch (error) {
|
|
1659
|
-
logger.warn("SEARCH", "
|
|
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}
|
|
3225
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1690
3226
|
} catch (error) {
|
|
1691
|
-
logger.error("SEARCH", "
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
3324
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1794
3325
|
}
|
|
1795
3326
|
}
|
|
1796
3327
|
/**
|
|
1797
|
-
*
|
|
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}
|
|
1804
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
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
|
-
*
|
|
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
|
|
3360
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1830
3361
|
}
|
|
1831
3362
|
}
|
|
1832
3363
|
/**
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
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
|
|
3370
|
+
return createHash2("sha256").update(payload).digest("hex");
|
|
1840
3371
|
}
|
|
1841
3372
|
/**
|
|
1842
|
-
*
|
|
1843
|
-
*
|
|
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 —
|
|
3380
|
+
// 60s — frequent reads on the same files
|
|
1850
3381
|
case "file-write":
|
|
1851
3382
|
return 1e4;
|
|
1852
|
-
// 10s —
|
|
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
|
|
3389
|
+
// 120s — repeated web search and fetch
|
|
1859
3390
|
case "delegation":
|
|
1860
3391
|
return 6e4;
|
|
1861
|
-
// 60s —
|
|
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", `
|
|
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
|
-
*
|
|
1904
|
-
*
|
|
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
|
|
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.
|
|
3471
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
1941
3472
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
1942
|
-
logger.debug("SDK", `
|
|
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 =
|
|
3490
|
+
// facts = JSON metadata
|
|
1960
3491
|
data.concepts?.join(", ") || null,
|
|
1961
3492
|
data.files?.join(", ") || null,
|
|
1962
3493
|
null,
|
|
1963
|
-
// filesModified: knowledge
|
|
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
|
-
*
|
|
2075
|
-
*
|
|
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
|
-
*
|
|
2086
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
3674
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2144
3675
|
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
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
|
-
*
|
|
2221
|
-
*
|
|
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
|
-
*
|
|
2233
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2259
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2283
|
-
*
|
|
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
|
-
*
|
|
2290
|
-
*
|
|
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
|
|
2554
|
-
import { join as
|
|
2555
|
-
import { homedir as
|
|
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 =
|
|
2566
|
-
PKG_VERSION = JSON.parse(
|
|
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 (
|
|
2621
|
-
const proc =
|
|
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:
|
|
2761
|
-
if (shell.includes("fish")) return { name: "fish", rcFile:
|
|
2762
|
-
return { name: "bash", rcFile:
|
|
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 =
|
|
2793
|
-
|
|
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 (
|
|
2799
|
-
const content =
|
|
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 =
|
|
4413
|
+
const nvmDir = join5(homedir4(), ".nvm");
|
|
2820
4414
|
try {
|
|
2821
|
-
if (
|
|
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 =
|
|
2870
|
-
if (
|
|
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 ||
|
|
2919
|
-
const agentsDir =
|
|
2920
|
-
const settingsDir =
|
|
2921
|
-
const steeringDir =
|
|
2922
|
-
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR ||
|
|
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
|
-
|
|
4519
|
+
mkdirSync5(dir, { recursive: true });
|
|
2926
4520
|
}
|
|
2927
4521
|
const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
|
|
2928
|
-
const agentDestPath =
|
|
2929
|
-
|
|
4522
|
+
const agentDestPath = join5(agentsDir, "kiro-memory.json");
|
|
4523
|
+
writeFileSync3(agentDestPath, agentConfig, "utf8");
|
|
2930
4524
|
console.log(` \u2192 Agent config: ${agentDestPath}`);
|
|
2931
|
-
const mcpFilePath =
|
|
4525
|
+
const mcpFilePath = join5(settingsDir, "mcp.json");
|
|
2932
4526
|
let mcpConfig = { mcpServers: {} };
|
|
2933
|
-
if (
|
|
4527
|
+
if (existsSync6(mcpFilePath)) {
|
|
2934
4528
|
try {
|
|
2935
|
-
mcpConfig = JSON.parse(
|
|
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: [
|
|
4536
|
+
args: [join5(distDir, "servers", "mcp-server.js")]
|
|
2943
4537
|
};
|
|
2944
|
-
|
|
4538
|
+
writeFileSync3(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
2945
4539
|
console.log(` \u2192 MCP config: ${mcpFilePath}`);
|
|
2946
|
-
const steeringDestPath =
|
|
2947
|
-
|
|
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 (
|
|
2955
|
-
const rcContent =
|
|
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 =
|
|
3063
|
-
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR ||
|
|
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
|
-
|
|
3066
|
-
|
|
3067
|
-
const settingsPath =
|
|
4659
|
+
mkdirSync5(claudeDir, { recursive: true });
|
|
4660
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
4661
|
+
const settingsPath = join5(claudeDir, "settings.json");
|
|
3068
4662
|
let settings = {};
|
|
3069
|
-
if (
|
|
4663
|
+
if (existsSync6(settingsPath)) {
|
|
3070
4664
|
try {
|
|
3071
|
-
settings = JSON.parse(
|
|
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 ${
|
|
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
|
-
|
|
4695
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
3102
4696
|
console.log(` \u2192 Hooks config: ${settingsPath}`);
|
|
3103
|
-
const mcpPath =
|
|
4697
|
+
const mcpPath = join5(homedir4(), ".mcp.json");
|
|
3104
4698
|
let mcpConfig = {};
|
|
3105
|
-
if (
|
|
4699
|
+
if (existsSync6(mcpPath)) {
|
|
3106
4700
|
try {
|
|
3107
|
-
mcpConfig = JSON.parse(
|
|
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: [
|
|
4708
|
+
args: [join5(distDir, "servers", "mcp-server.js")]
|
|
3115
4709
|
};
|
|
3116
|
-
|
|
4710
|
+
writeFileSync3(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
3117
4711
|
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3118
|
-
const steeringPath =
|
|
4712
|
+
const steeringPath = join5(claudeDir, "CLAUDE.md");
|
|
3119
4713
|
let existingSteering = "";
|
|
3120
|
-
if (
|
|
3121
|
-
existingSteering =
|
|
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
|
-
|
|
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 =
|
|
3170
|
-
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR ||
|
|
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
|
-
|
|
3173
|
-
|
|
3174
|
-
const hooksPath =
|
|
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 (
|
|
4770
|
+
if (existsSync6(hooksPath)) {
|
|
3177
4771
|
try {
|
|
3178
|
-
hooksConfig = JSON.parse(
|
|
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 ${
|
|
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
|
-
|
|
4799
|
+
writeFileSync3(hooksPath, JSON.stringify(hooksConfig, null, 2), "utf8");
|
|
3206
4800
|
console.log(` \u2192 Hooks config: ${hooksPath}`);
|
|
3207
|
-
const mcpPath =
|
|
4801
|
+
const mcpPath = join5(cursorDir, "mcp.json");
|
|
3208
4802
|
let mcpConfig = {};
|
|
3209
|
-
if (
|
|
4803
|
+
if (existsSync6(mcpPath)) {
|
|
3210
4804
|
try {
|
|
3211
|
-
mcpConfig = JSON.parse(
|
|
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: [
|
|
4812
|
+
args: [join5(distDir, "servers", "mcp-server.js")]
|
|
3219
4813
|
};
|
|
3220
|
-
|
|
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 ||
|
|
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
|
-
|
|
3263
|
-
const windsurfDir =
|
|
3264
|
-
|
|
3265
|
-
const mcpPath =
|
|
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 (
|
|
4861
|
+
if (existsSync6(mcpPath)) {
|
|
3268
4862
|
try {
|
|
3269
|
-
mcpConfig = JSON.parse(
|
|
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: [
|
|
4870
|
+
args: [join5(distDir, "servers", "mcp-server.js")]
|
|
3277
4871
|
};
|
|
3278
|
-
|
|
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 ||
|
|
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
|
-
|
|
4915
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
3322
4916
|
const platform2 = process.platform;
|
|
3323
4917
|
let clineSettingsDir;
|
|
3324
4918
|
if (platform2 === "darwin") {
|
|
3325
|
-
clineSettingsDir =
|
|
4919
|
+
clineSettingsDir = join5(homedir4(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3326
4920
|
} else {
|
|
3327
|
-
clineSettingsDir =
|
|
4921
|
+
clineSettingsDir = join5(homedir4(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3328
4922
|
}
|
|
3329
|
-
|
|
3330
|
-
const mcpPath =
|
|
4923
|
+
mkdirSync5(clineSettingsDir, { recursive: true });
|
|
4924
|
+
const mcpPath = join5(clineSettingsDir, "cline_mcp_settings.json");
|
|
3331
4925
|
let mcpConfig = {};
|
|
3332
|
-
if (
|
|
4926
|
+
if (existsSync6(mcpPath)) {
|
|
3333
4927
|
try {
|
|
3334
|
-
mcpConfig = JSON.parse(
|
|
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: [
|
|
4935
|
+
args: [join5(distDir, "servers", "mcp-server.js")]
|
|
3342
4936
|
};
|
|
3343
|
-
|
|
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 ||
|
|
3363
|
-
const agentPath =
|
|
3364
|
-
const mcpPath =
|
|
3365
|
-
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR ||
|
|
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:
|
|
3369
|
-
message:
|
|
3370
|
-
fix: !
|
|
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 (
|
|
4967
|
+
if (existsSync6(mcpPath)) {
|
|
3374
4968
|
try {
|
|
3375
|
-
const mcp = JSON.parse(
|
|
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:
|
|
3389
|
-
message:
|
|
4982
|
+
ok: existsSync6(dataDir),
|
|
4983
|
+
message: existsSync6(dataDir) ? dataDir : "Not created (will be created on first use)"
|
|
3390
4984
|
});
|
|
3391
|
-
const claudeDir =
|
|
3392
|
-
const claudeSettingsPath =
|
|
4985
|
+
const claudeDir = join5(homedir4(), ".claude");
|
|
4986
|
+
const claudeSettingsPath = join5(claudeDir, "settings.json");
|
|
3393
4987
|
let claudeHooksOk = false;
|
|
3394
|
-
if (
|
|
4988
|
+
if (existsSync6(claudeSettingsPath)) {
|
|
3395
4989
|
try {
|
|
3396
|
-
const claudeSettings = JSON.parse(
|
|
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 =
|
|
4999
|
+
const claudeMcpPath = join5(homedir4(), ".mcp.json");
|
|
3406
5000
|
let claudeMcpOk = false;
|
|
3407
|
-
if (
|
|
5001
|
+
if (existsSync6(claudeMcpPath)) {
|
|
3408
5002
|
try {
|
|
3409
|
-
const claudeMcp = JSON.parse(
|
|
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:
|
|
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:
|
|
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 =
|
|
3427
|
-
const cursorHooksPath =
|
|
5020
|
+
const cursorDir = join5(homedir4(), ".cursor");
|
|
5021
|
+
const cursorHooksPath = join5(cursorDir, "hooks.json");
|
|
3428
5022
|
let cursorHooksOk = false;
|
|
3429
|
-
if (
|
|
5023
|
+
if (existsSync6(cursorHooksPath)) {
|
|
3430
5024
|
try {
|
|
3431
|
-
const cursorHooks = JSON.parse(
|
|
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 =
|
|
5034
|
+
const cursorMcpPath = join5(cursorDir, "mcp.json");
|
|
3441
5035
|
let cursorMcpOk = false;
|
|
3442
|
-
if (
|
|
5036
|
+
if (existsSync6(cursorMcpPath)) {
|
|
3443
5037
|
try {
|
|
3444
|
-
const cursorMcp = JSON.parse(
|
|
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:
|
|
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:
|
|
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 =
|
|
5055
|
+
const windsurfMcpPath = join5(homedir4(), ".codeium", "windsurf", "mcp_config.json");
|
|
3462
5056
|
let windsurfMcpOk = false;
|
|
3463
|
-
if (
|
|
5057
|
+
if (existsSync6(windsurfMcpPath)) {
|
|
3464
5058
|
try {
|
|
3465
|
-
const windsurfMcp = JSON.parse(
|
|
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:
|
|
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 =
|
|
5073
|
+
clineSettingsBase = join5(homedir4(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3480
5074
|
} else {
|
|
3481
|
-
clineSettingsBase =
|
|
5075
|
+
clineSettingsBase = join5(homedir4(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3482
5076
|
}
|
|
3483
|
-
const clineMcpPath =
|
|
5077
|
+
const clineMcpPath = join5(clineSettingsBase, "cline_mcp_settings.json");
|
|
3484
5078
|
let clineMcpOk = false;
|
|
3485
|
-
if (
|
|
5079
|
+
if (existsSync6(clineMcpPath)) {
|
|
3486
5080
|
try {
|
|
3487
|
-
const clineMcp = JSON.parse(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|