gnosys 5.0.1 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import { GnosysAsk } from "./lib/ask.js";
27
27
  import { getLLMProvider, isProviderAvailable } from "./lib/llm.js";
28
28
  import { GnosysDB } from "./lib/db.js";
29
29
  import { migrate, formatMigrationReport } from "./lib/migrate.js";
30
- import { createProjectIdentity, readProjectIdentity, findProjectIdentity } from "./lib/projectIdentity.js";
30
+ import { createProjectIdentity, readProjectIdentity, findProjectIdentity, migrateProject } from "./lib/projectIdentity.js";
31
31
  import { setPreference, getPreference, getAllPreferences, deletePreference } from "./lib/preferences.js";
32
32
  import { syncToTarget } from "./lib/rulesGen.js";
33
33
  // Load API keys from ~/.config/gnosys/.env (same as MCP server)
@@ -389,29 +389,51 @@ program
389
389
  }
390
390
  console.log("Structuring memory via LLM...");
391
391
  const result = await ingestion.ingest(input);
392
- const id = await writeTarget.store.generateId(result.category);
393
- const today = new Date().toISOString().split("T")[0];
394
- const frontmatter = {
395
- id,
396
- title: result.title,
397
- category: result.category,
398
- tags: result.tags,
399
- relevance: result.relevance,
400
- author: opts.author,
401
- authority: opts.authority,
402
- confidence: result.confidence,
403
- created: today,
404
- modified: today,
405
- last_reviewed: today,
406
- status: "active",
407
- supersedes: null,
408
- };
409
- const content = `# ${result.title}\n\n${result.content}`;
410
- const relPath = await writeTarget.store.writeMemory(result.category, `${result.filename}.md`, frontmatter, content);
411
- console.log(`\nMemory added to [${writeTarget.label}]: ${result.title}`);
412
- console.log(`Path: ${writeTarget.label}:${relPath}`);
413
- console.log(`Category: ${result.category}`);
414
- console.log(`Confidence: ${result.confidence}`);
392
+ let centralDb = null;
393
+ try {
394
+ centralDb = GnosysDB.openCentral();
395
+ const projectId = await resolveProjectId();
396
+ const id = centralDb.getNextId(result.category, projectId || undefined);
397
+ const today = new Date().toISOString().split("T")[0];
398
+ const now = new Date().toISOString();
399
+ const content = `# ${result.title}\n\n${result.content}`;
400
+ const tags = result.tags;
401
+ const tagsJson = Array.isArray(tags)
402
+ ? JSON.stringify(tags)
403
+ : JSON.stringify(Object.values(tags).flat());
404
+ centralDb.insertMemory({
405
+ id,
406
+ title: result.title,
407
+ category: result.category,
408
+ content,
409
+ summary: null,
410
+ tags: tagsJson,
411
+ relevance: result.relevance,
412
+ author: opts.author,
413
+ authority: opts.authority,
414
+ confidence: result.confidence,
415
+ reinforcement_count: 0,
416
+ content_hash: "",
417
+ status: "active",
418
+ tier: "active",
419
+ supersedes: null,
420
+ superseded_by: null,
421
+ last_reinforced: null,
422
+ created: now,
423
+ modified: now,
424
+ embedding: null,
425
+ source_path: null,
426
+ project_id: projectId,
427
+ scope: "project",
428
+ });
429
+ console.log(`\nMemory added to [${writeTarget.label}]: ${result.title}`);
430
+ console.log(`ID: ${id}`);
431
+ console.log(`Category: ${result.category}`);
432
+ console.log(`Confidence: ${result.confidence}`);
433
+ }
434
+ finally {
435
+ centralDb?.close();
436
+ }
415
437
  if (result.proposedNewTags && result.proposedNewTags.length > 0) {
416
438
  console.log("\nProposed new tags (not yet in registry):");
417
439
  for (const t of result.proposedNewTags) {
@@ -452,6 +474,7 @@ program
452
474
  // Good — fresh init
453
475
  }
454
476
  if (!isResync) {
477
+ // Create directory structure (DB is sole source of truth — no category folders or changelog)
455
478
  await fs.mkdir(storePath, { recursive: true });
456
479
  await fs.mkdir(path.join(storePath, ".config"), { recursive: true });
457
480
  const defaultRegistry = {
@@ -475,27 +498,6 @@ program
475
498
  // Create .gitignore inside .gnosys to exclude large binary attachments
476
499
  const storeGitignore = "# Large binary attachments (tracked via manifest, not git)\nattachments/\n";
477
500
  await fs.writeFile(path.join(storePath, ".gitignore"), storeGitignore, "utf-8");
478
- const changelog = `# Gnosys Changelog\n\n## ${new Date().toISOString().split("T")[0]}\n\n- Store initialized\n`;
479
- await fs.writeFile(path.join(storePath, "CHANGELOG.md"), changelog, "utf-8");
480
- try {
481
- const { execSync } = await import("child_process");
482
- execSync("git init", { cwd: storePath, stdio: "pipe" });
483
- try {
484
- execSync("git config user.name", { cwd: storePath, stdio: "pipe" });
485
- }
486
- catch {
487
- execSync('git config user.name "Gnosys"', { cwd: storePath, stdio: "pipe" });
488
- execSync('git config user.email "gnosys@local"', { cwd: storePath, stdio: "pipe" });
489
- }
490
- execSync("git add -A && git add -f .config/", { cwd: storePath, stdio: "pipe" });
491
- execSync('git commit -m "Initialize Gnosys store"', {
492
- cwd: storePath,
493
- stdio: "pipe",
494
- });
495
- }
496
- catch {
497
- // Git not available
498
- }
499
501
  }
500
502
  // v3.0: Create/update project identity and register in central DB
501
503
  let centralDb = null;
@@ -547,11 +549,194 @@ program
547
549
  console.log(` gnosys.json (project identity)`);
548
550
  console.log(` .config/ (internal config)`);
549
551
  console.log(` tags.json (tag registry)`);
550
- console.log(` CHANGELOG.md`);
551
- console.log(` git repo`);
552
+ }
553
+ // Configure IDE hooks for automatic memory recall
554
+ const { configureIdeHooks } = await import("./lib/projectIdentity.js");
555
+ const hookResult = await configureIdeHooks(targetDir);
556
+ if (hookResult.configured) {
557
+ console.log(`\nIDE hooks (${hookResult.ide}):`);
558
+ console.log(` ${hookResult.details}`);
559
+ console.log(` File: ${hookResult.filePath}`);
560
+ }
561
+ else {
562
+ console.log(`\nIDE hooks: ${hookResult.details}`);
552
563
  }
553
564
  console.log(`\nStart adding memories with: gnosys add "your knowledge here"`);
554
565
  });
566
+ // ─── gnosys migrate ─────────────────────────────────────────────────────
567
+ program
568
+ .command("migrate")
569
+ .description("Interactively migrate a .gnosys/ store to a new directory. Moves files, updates project name/paths, syncs to central DB, and cleans up.")
570
+ .option("--from <dir>", "Source directory containing .gnosys/ (skips prompt)")
571
+ .option("--to <dir>", "Target directory to move .gnosys/ into (skips prompt)")
572
+ .option("--name <name>", "New project name (skips prompt, default: basename of target)")
573
+ .option("--yes", "Skip all confirmation prompts (non-interactive mode)")
574
+ .action(async (opts) => {
575
+ const { createInterface } = await import("readline/promises");
576
+ const rl = opts.yes ? null : createInterface({ input: process.stdin, output: process.stdout });
577
+ const ask = async (question, defaultValue) => {
578
+ if (!rl)
579
+ return defaultValue || "";
580
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
581
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
582
+ return answer || defaultValue || "";
583
+ };
584
+ try {
585
+ console.log("\n── Gnosys Project Migration ──\n");
586
+ // 1. Resolve source
587
+ let sourceDir;
588
+ if (opts.from) {
589
+ sourceDir = path.resolve(opts.from);
590
+ }
591
+ else {
592
+ // Try auto-detect first
593
+ const found = await findProjectIdentity(process.cwd());
594
+ const defaultSource = found ? found.projectRoot : "";
595
+ const sourceInput = await ask("Source directory (contains .gnosys/)", defaultSource);
596
+ if (!sourceInput) {
597
+ console.error("No source directory provided.");
598
+ rl?.close();
599
+ process.exit(1);
600
+ }
601
+ sourceDir = path.resolve(sourceInput);
602
+ }
603
+ // Verify source has .gnosys/
604
+ const storePath = path.join(sourceDir, ".gnosys");
605
+ try {
606
+ await fs.stat(storePath);
607
+ }
608
+ catch {
609
+ console.error(`No .gnosys/ directory found at ${sourceDir}`);
610
+ rl?.close();
611
+ process.exit(1);
612
+ }
613
+ // Read identity (may not exist for pre-v3 stores)
614
+ const identity = await readProjectIdentity(sourceDir);
615
+ // Count memory files
616
+ const { glob } = await import("glob");
617
+ const memFiles = await glob("**/*.md", {
618
+ cwd: storePath,
619
+ ignore: ["**/CHANGELOG.md", "**/MANIFEST.md", "**/.git/**", "**/.obsidian/**"],
620
+ });
621
+ console.log("\nSource project:");
622
+ if (identity) {
623
+ console.log(` Name: ${identity.projectName}`);
624
+ console.log(` ID: ${identity.projectId}`);
625
+ }
626
+ else {
627
+ console.log(` Name: (unregistered — pre-v3 store)`);
628
+ }
629
+ console.log(` Directory: ${sourceDir}`);
630
+ console.log(` Memories: ${memFiles.length} markdown files`);
631
+ // 2. Resolve target
632
+ let targetDir;
633
+ if (opts.to) {
634
+ targetDir = path.resolve(opts.to);
635
+ }
636
+ else {
637
+ const targetInput = await ask("\nTarget directory (where .gnosys/ should live)");
638
+ if (!targetInput) {
639
+ console.error("No target directory provided.");
640
+ rl?.close();
641
+ process.exit(1);
642
+ }
643
+ targetDir = path.resolve(targetInput);
644
+ }
645
+ // 3. Resolve name
646
+ const defaultName = opts.name || path.basename(targetDir);
647
+ const newName = opts.yes
648
+ ? defaultName
649
+ : await ask("Project name", defaultName);
650
+ // 4. Ask about sync and cleanup
651
+ let doSync = true;
652
+ let doDelete = true;
653
+ if (!opts.yes) {
654
+ const syncAnswer = await ask("\nSync memories to central DB?", "Y");
655
+ doSync = syncAnswer.toLowerCase() !== "n" && syncAnswer.toLowerCase() !== "no";
656
+ const deleteAnswer = await ask("Delete old .gnosys/ after migration?", "Y");
657
+ doDelete = deleteAnswer.toLowerCase() !== "n" && deleteAnswer.toLowerCase() !== "no";
658
+ }
659
+ // 5. Show summary and confirm
660
+ console.log("\n── Migration Summary ──");
661
+ console.log(` From: ${sourceDir}/.gnosys/`);
662
+ console.log(` To: ${targetDir}/.gnosys/`);
663
+ console.log(` Name: ${identity?.projectName || "(new)"} → ${newName}`);
664
+ console.log(` Memories: ${memFiles.length} files`);
665
+ console.log(` Sync to DB: ${doSync ? "yes" : "no"}`);
666
+ console.log(` Delete old: ${doDelete ? "yes" : "no"}`);
667
+ if (!opts.yes) {
668
+ const confirm = await ask("\nProceed?", "Y");
669
+ if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
670
+ console.log("Aborted.");
671
+ rl?.close();
672
+ return;
673
+ }
674
+ }
675
+ rl?.close();
676
+ // 6. Open central DB
677
+ let centralDb = null;
678
+ try {
679
+ centralDb = GnosysDB.openCentral();
680
+ if (!centralDb.isAvailable())
681
+ centralDb = null;
682
+ }
683
+ catch {
684
+ centralDb = null;
685
+ }
686
+ // 7. Run migration
687
+ console.log("\nMigrating...");
688
+ const result = await migrateProject({
689
+ sourcePath: sourceDir,
690
+ targetPath: targetDir,
691
+ newName,
692
+ deleteSource: doDelete,
693
+ centralDb: centralDb || undefined,
694
+ });
695
+ console.log(` Copied ${result.memoryFileCount} memory files`);
696
+ console.log(` Project: ${result.newIdentity.projectName} (${result.newIdentity.projectId})`);
697
+ console.log(` Path: ${result.newIdentity.workingDirectory}`);
698
+ console.log(` Central DB: ${centralDb ? "updated ✓" : "not available"}`);
699
+ // 8. Sync memories to central DB
700
+ if (doSync && centralDb) {
701
+ console.log("\nSyncing memories to central DB...");
702
+ const matter = (await import("gray-matter")).default;
703
+ const { syncMemoryToDb } = await import("./lib/dbWrite.js");
704
+ const newStorePath = path.join(targetDir, ".gnosys");
705
+ const mdFiles = await glob("**/*.md", {
706
+ cwd: newStorePath,
707
+ ignore: ["**/CHANGELOG.md", "**/MANIFEST.md", "**/.git/**", "**/.obsidian/**"],
708
+ });
709
+ let synced = 0;
710
+ for (const file of mdFiles) {
711
+ try {
712
+ const filePath = path.join(newStorePath, file);
713
+ const raw = await fs.readFile(filePath, "utf-8");
714
+ const parsed = matter(raw);
715
+ if (parsed.data?.id) {
716
+ syncMemoryToDb(centralDb, parsed.data, parsed.content, filePath, result.newIdentity.projectId, "project");
717
+ synced++;
718
+ }
719
+ }
720
+ catch {
721
+ // Skip files that fail to parse
722
+ }
723
+ }
724
+ console.log(` Synced ${synced} memories to central DB`);
725
+ }
726
+ if (centralDb)
727
+ centralDb.close();
728
+ if (doDelete) {
729
+ console.log(`\nOld .gnosys/ at ${sourceDir} removed.`);
730
+ }
731
+ console.log(`\nMigration complete! Run 'gnosys projects' to verify.`);
732
+ }
733
+ catch (err) {
734
+ rl?.close();
735
+ const msg = err instanceof Error ? err.message : String(err);
736
+ console.error(`\nMigration failed: ${msg}`);
737
+ process.exit(1);
738
+ }
739
+ });
555
740
  // ─── gnosys stale ───────────────────────────────────────────────────────
556
741
  program
557
742
  .command("stale")
@@ -649,30 +834,34 @@ program
649
834
  const fullContent = opts.content
650
835
  ? `# ${opts.title || memory.frontmatter.title}\n\n${opts.content}`
651
836
  : undefined;
652
- const updated = await sourceStore.store.updateMemory(memory.relativePath, updates, fullContent);
653
- if (!updated) {
654
- console.error(`Failed to update: ${memPath}`);
837
+ const memoryId = memory.frontmatter.id;
838
+ if (!memoryId) {
839
+ console.error(`Memory has no ID: ${memPath}`);
655
840
  process.exit(1);
656
841
  }
657
- // Supersession cross-linking
658
- if (opts.supersedes && updated.frontmatter.id) {
659
- const allMemories = await resolver.getAllMemories();
660
- const supersededMemory = allMemories.find((m) => m.frontmatter.id === opts.supersedes);
661
- if (supersededMemory) {
662
- const supersededStore = resolver
663
- .getStores()
664
- .find((s) => s.label === supersededMemory.sourceLabel);
665
- if (supersededStore?.writable) {
666
- await supersededStore.store.updateMemory(supersededMemory.relativePath, { superseded_by: updated.frontmatter.id, status: "superseded" });
842
+ let centralDb = null;
843
+ try {
844
+ centralDb = GnosysDB.openCentral();
845
+ const { syncUpdateToDb } = await import("./lib/dbWrite.js");
846
+ syncUpdateToDb(centralDb, memoryId, updates, fullContent);
847
+ // Supersession cross-linking
848
+ if (opts.supersedes && memoryId) {
849
+ const allMemories = await resolver.getAllMemories();
850
+ const supersededMemory = allMemories.find((m) => m.frontmatter.id === opts.supersedes);
851
+ if (supersededMemory) {
852
+ syncUpdateToDb(centralDb, supersededMemory.frontmatter.id, { superseded_by: memoryId, status: "superseded" });
667
853
  console.log(`Cross-linked: ${supersededMemory.frontmatter.title} marked as superseded.`);
668
854
  }
669
855
  }
670
856
  }
857
+ finally {
858
+ centralDb?.close();
859
+ }
671
860
  const changedFields = Object.keys(updates);
672
861
  if (opts.content)
673
862
  changedFields.push("content");
674
- console.log(`Memory updated: ${updated.frontmatter.title}`);
675
- console.log(`Path: ${memory.sourceLabel}:${memory.relativePath}`);
863
+ console.log(`Memory updated: ${opts.title || memory.frontmatter.title}`);
864
+ console.log(`ID: ${memoryId}`);
676
865
  console.log(`Changed: ${changedFields.join(", ")}`);
677
866
  });
678
867
  // ─── gnosys reinforce <memoryId> ────────────────────────────────────────
@@ -701,17 +890,16 @@ program
701
890
  await fs.appendFile(logPath, entry + "\n", "utf-8");
702
891
  // If 'useful', update the memory's modified date (reset decay)
703
892
  if (opts.signal === "useful") {
704
- const allMemories = await resolver.getAllMemories();
705
- const memory = allMemories.find((m) => m.frontmatter.id === memoryId);
706
- if (memory) {
707
- const sourceStore = resolver
708
- .getStores()
709
- .find((s) => s.label === memory.sourceLabel);
710
- if (sourceStore?.writable) {
711
- await sourceStore.store.updateMemory(memory.relativePath, {
712
- modified: new Date().toISOString().split("T")[0],
713
- });
714
- }
893
+ let centralDb = null;
894
+ try {
895
+ centralDb = GnosysDB.openCentral();
896
+ const { syncUpdateToDb } = await import("./lib/dbWrite.js");
897
+ syncUpdateToDb(centralDb, memoryId, {
898
+ modified: new Date().toISOString().split("T")[0],
899
+ });
900
+ }
901
+ finally {
902
+ centralDb?.close();
715
903
  }
716
904
  }
717
905
  const messages = {
@@ -787,13 +975,7 @@ program
787
975
  centralDb?.close();
788
976
  }
789
977
  }
790
- // ─── Default: file-based store (original behavior) ────────────
791
- const resolver = await getResolver();
792
- const writeTarget = resolver.getWriteTarget(opts.store || undefined);
793
- if (!writeTarget) {
794
- console.error("No writable store found.");
795
- process.exit(1);
796
- }
978
+ // ─── DB-only write ────────────────────────────────────────────
797
979
  let tags;
798
980
  try {
799
981
  tags = JSON.parse(opts.tags);
@@ -802,32 +984,47 @@ program
802
984
  console.error("Invalid --tags JSON. Example: '{\"domain\":[\"auth\"],\"type\":[\"decision\"]}'");
803
985
  process.exit(1);
804
986
  }
805
- const id = await writeTarget.store.generateId(opts.category);
806
- const slug = opts.title
807
- .toLowerCase()
808
- .replace(/[^a-z0-9]+/g, "-")
809
- .replace(/^-|-$/g, "")
810
- .substring(0, 60);
811
- const today = new Date().toISOString().split("T")[0];
812
- const frontmatter = {
813
- id,
814
- title: opts.title,
815
- category: opts.category,
816
- tags,
817
- relevance: opts.relevance,
818
- author: opts.author,
819
- authority: opts.authority,
820
- confidence: parseFloat(opts.confidence),
821
- created: today,
822
- modified: today,
823
- last_reviewed: today,
824
- status: "active",
825
- supersedes: null,
826
- };
827
- const content = `# ${opts.title}\n\n${opts.content}`;
828
- const relPath = await writeTarget.store.writeMemory(opts.category, `${slug}.md`, frontmatter, content);
829
- console.log(`Memory added to [${writeTarget.label}]: ${opts.title}`);
830
- console.log(`Path: ${writeTarget.label}:${relPath}`);
987
+ let centralDb = null;
988
+ try {
989
+ centralDb = GnosysDB.openCentral();
990
+ const projectId = await resolveProjectId();
991
+ const id = centralDb.getNextId(opts.category, projectId || undefined);
992
+ const now = new Date().toISOString();
993
+ const content = `# ${opts.title}\n\n${opts.content}`;
994
+ const tagsJson = Array.isArray(tags)
995
+ ? JSON.stringify(tags)
996
+ : JSON.stringify(Object.values(tags).flat());
997
+ centralDb.insertMemory({
998
+ id,
999
+ title: opts.title,
1000
+ category: opts.category,
1001
+ content,
1002
+ summary: null,
1003
+ tags: tagsJson,
1004
+ relevance: opts.relevance || opts.content.slice(0, 200),
1005
+ author: opts.author,
1006
+ authority: opts.authority,
1007
+ confidence: parseFloat(opts.confidence),
1008
+ reinforcement_count: 0,
1009
+ content_hash: "",
1010
+ status: "active",
1011
+ tier: "active",
1012
+ supersedes: null,
1013
+ superseded_by: null,
1014
+ last_reinforced: null,
1015
+ created: now,
1016
+ modified: now,
1017
+ embedding: null,
1018
+ source_path: null,
1019
+ project_id: projectId,
1020
+ scope: "project",
1021
+ });
1022
+ console.log(`Memory added: ${opts.title}`);
1023
+ console.log(`ID: ${id}`);
1024
+ }
1025
+ finally {
1026
+ centralDb?.close();
1027
+ }
831
1028
  });
832
1029
  // ─── gnosys ingest <file> ─────────────────────────────────────────────────
833
1030
  program
@@ -1029,49 +1226,70 @@ Output ONLY the JSON array, no markdown fences.`,
1029
1226
  // Step 2: Check novelty and commit
1030
1227
  let added = 0;
1031
1228
  let skipped = 0;
1032
- for (const candidate of candidates) {
1033
- const searchTerms = candidate.search_terms.join(" ");
1034
- const existing = search.discover(searchTerms, 3);
1035
- if (existing.length > 0) {
1036
- console.log(` ⏭ SKIP: "${candidate.summary}"`);
1037
- console.log(` Overlaps with: ${existing[0].title}`);
1038
- skipped++;
1039
- }
1040
- else if (opts.dryRun) {
1041
- console.log(` WOULD ADD: "${candidate.summary}" [${candidate.type}]`);
1042
- added++;
1043
- }
1044
- else {
1045
- try {
1046
- const result = await ingestion.ingest(candidate.summary);
1047
- const id = await writeTarget.store.generateId(result.category);
1048
- const today = new Date().toISOString().split("T")[0];
1049
- const frontmatter = {
1050
- id,
1051
- title: result.title,
1052
- category: result.category,
1053
- tags: result.tags,
1054
- relevance: result.relevance,
1055
- author: "ai",
1056
- authority: "observed",
1057
- confidence: result.confidence,
1058
- created: today,
1059
- modified: today,
1060
- last_reviewed: today,
1061
- status: "active",
1062
- supersedes: null,
1063
- };
1064
- const content = `# ${result.title}\n\n${result.content}`;
1065
- const relPath = await writeTarget.store.writeMemory(result.category, `${result.filename}.md`, frontmatter, content);
1066
- console.log(` ➕ ADDED: "${result.title}"`);
1067
- console.log(` Path: ${writeTarget.label}:${relPath}`);
1229
+ let centralDb = null;
1230
+ try {
1231
+ centralDb = GnosysDB.openCentral();
1232
+ const projectId = await resolveProjectId();
1233
+ for (const candidate of candidates) {
1234
+ const searchTerms = candidate.search_terms.join(" ");
1235
+ const existing = search.discover(searchTerms, 3);
1236
+ if (existing.length > 0) {
1237
+ console.log(` ⏭ SKIP: "${candidate.summary}"`);
1238
+ console.log(` Overlaps with: ${existing[0].title}`);
1239
+ skipped++;
1240
+ }
1241
+ else if (opts.dryRun) {
1242
+ console.log(` ➕ WOULD ADD: "${candidate.summary}" [${candidate.type}]`);
1068
1243
  added++;
1069
1244
  }
1070
- catch (err) {
1071
- console.error(` ❌ FAILED: "${candidate.summary}": ${err instanceof Error ? err.message : String(err)}`);
1245
+ else {
1246
+ try {
1247
+ const result = await ingestion.ingest(candidate.summary);
1248
+ const id = centralDb.getNextId(result.category, projectId || undefined);
1249
+ const now = new Date().toISOString();
1250
+ const content = `# ${result.title}\n\n${result.content}`;
1251
+ const resultTags = result.tags;
1252
+ const tagsJson = Array.isArray(resultTags)
1253
+ ? JSON.stringify(resultTags)
1254
+ : JSON.stringify(Object.values(resultTags).flat());
1255
+ centralDb.insertMemory({
1256
+ id,
1257
+ title: result.title,
1258
+ category: result.category,
1259
+ content,
1260
+ summary: null,
1261
+ tags: tagsJson,
1262
+ relevance: result.relevance,
1263
+ author: "ai",
1264
+ authority: "observed",
1265
+ confidence: result.confidence,
1266
+ reinforcement_count: 0,
1267
+ content_hash: "",
1268
+ status: "active",
1269
+ tier: "active",
1270
+ supersedes: null,
1271
+ superseded_by: null,
1272
+ last_reinforced: null,
1273
+ created: now,
1274
+ modified: now,
1275
+ embedding: null,
1276
+ source_path: null,
1277
+ project_id: projectId,
1278
+ scope: "project",
1279
+ });
1280
+ console.log(` ➕ ADDED: "${result.title}"`);
1281
+ console.log(` ID: ${id}`);
1282
+ added++;
1283
+ }
1284
+ catch (err) {
1285
+ console.error(` ❌ FAILED: "${candidate.summary}": ${err instanceof Error ? err.message : String(err)}`);
1286
+ }
1072
1287
  }
1288
+ console.log();
1073
1289
  }
1074
- console.log();
1290
+ }
1291
+ finally {
1292
+ centralDb?.close();
1075
1293
  }
1076
1294
  search.close();
1077
1295
  const mode = opts.dryRun ? "DRY RUN" : "COMMITTED";
@@ -2217,6 +2435,9 @@ program
2217
2435
  }
2218
2436
  centralDb.close();
2219
2437
  }
2438
+ // Configure IDE hooks for automatic memory recall
2439
+ const { configureIdeHooks } = await import("./lib/projectIdentity.js");
2440
+ await configureIdeHooks(projectDir);
2220
2441
  upgraded.push(projectDir);
2221
2442
  }
2222
2443
  catch (err) {
@@ -2833,10 +3054,10 @@ program
2833
3054
  process.exit(1);
2834
3055
  }
2835
3056
  });
2836
- // ─── gnosys migrate --to-central ────────────────────────────────────────
3057
+ // ─── gnosys migrate-db ──────────────────────────────────────────────────
2837
3058
  program
2838
- .command("migrate")
2839
- .description("Migrate data. Use --to-central to move per-project stores into the central DB.")
3059
+ .command("migrate-db")
3060
+ .description("Legacy data migration. Use --to-central to move per-project stores into the central DB.")
2840
3061
  .option("--to-central", "Migrate all discovered per-project stores into ~/.gnosys/gnosys.db")
2841
3062
  .option("-v, --verbose", "Verbose output")
2842
3063
  .action(async (opts) => {