gnosys 5.0.1 → 5.1.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/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,183 @@ 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
552
  }
553
553
  console.log(`\nStart adding memories with: gnosys add "your knowledge here"`);
554
554
  });
555
+ // ─── gnosys migrate ─────────────────────────────────────────────────────
556
+ program
557
+ .command("migrate")
558
+ .description("Interactively migrate a .gnosys/ store to a new directory. Moves files, updates project name/paths, syncs to central DB, and cleans up.")
559
+ .option("--from <dir>", "Source directory containing .gnosys/ (skips prompt)")
560
+ .option("--to <dir>", "Target directory to move .gnosys/ into (skips prompt)")
561
+ .option("--name <name>", "New project name (skips prompt, default: basename of target)")
562
+ .option("--yes", "Skip all confirmation prompts (non-interactive mode)")
563
+ .action(async (opts) => {
564
+ const { createInterface } = await import("readline/promises");
565
+ const rl = opts.yes ? null : createInterface({ input: process.stdin, output: process.stdout });
566
+ const ask = async (question, defaultValue) => {
567
+ if (!rl)
568
+ return defaultValue || "";
569
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
570
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
571
+ return answer || defaultValue || "";
572
+ };
573
+ try {
574
+ console.log("\n── Gnosys Project Migration ──\n");
575
+ // 1. Resolve source
576
+ let sourceDir;
577
+ if (opts.from) {
578
+ sourceDir = path.resolve(opts.from);
579
+ }
580
+ else {
581
+ // Try auto-detect first
582
+ const found = await findProjectIdentity(process.cwd());
583
+ const defaultSource = found ? found.projectRoot : "";
584
+ const sourceInput = await ask("Source directory (contains .gnosys/)", defaultSource);
585
+ if (!sourceInput) {
586
+ console.error("No source directory provided.");
587
+ rl?.close();
588
+ process.exit(1);
589
+ }
590
+ sourceDir = path.resolve(sourceInput);
591
+ }
592
+ // Verify source has .gnosys/
593
+ const storePath = path.join(sourceDir, ".gnosys");
594
+ try {
595
+ await fs.stat(storePath);
596
+ }
597
+ catch {
598
+ console.error(`No .gnosys/ directory found at ${sourceDir}`);
599
+ rl?.close();
600
+ process.exit(1);
601
+ }
602
+ // Read identity (may not exist for pre-v3 stores)
603
+ const identity = await readProjectIdentity(sourceDir);
604
+ // Count memory files
605
+ const { glob } = await import("glob");
606
+ const memFiles = await glob("**/*.md", {
607
+ cwd: storePath,
608
+ ignore: ["**/CHANGELOG.md", "**/MANIFEST.md", "**/.git/**", "**/.obsidian/**"],
609
+ });
610
+ console.log("\nSource project:");
611
+ if (identity) {
612
+ console.log(` Name: ${identity.projectName}`);
613
+ console.log(` ID: ${identity.projectId}`);
614
+ }
615
+ else {
616
+ console.log(` Name: (unregistered — pre-v3 store)`);
617
+ }
618
+ console.log(` Directory: ${sourceDir}`);
619
+ console.log(` Memories: ${memFiles.length} markdown files`);
620
+ // 2. Resolve target
621
+ let targetDir;
622
+ if (opts.to) {
623
+ targetDir = path.resolve(opts.to);
624
+ }
625
+ else {
626
+ const targetInput = await ask("\nTarget directory (where .gnosys/ should live)");
627
+ if (!targetInput) {
628
+ console.error("No target directory provided.");
629
+ rl?.close();
630
+ process.exit(1);
631
+ }
632
+ targetDir = path.resolve(targetInput);
633
+ }
634
+ // 3. Resolve name
635
+ const defaultName = opts.name || path.basename(targetDir);
636
+ const newName = opts.yes
637
+ ? defaultName
638
+ : await ask("Project name", defaultName);
639
+ // 4. Ask about sync and cleanup
640
+ let doSync = true;
641
+ let doDelete = true;
642
+ if (!opts.yes) {
643
+ const syncAnswer = await ask("\nSync memories to central DB?", "Y");
644
+ doSync = syncAnswer.toLowerCase() !== "n" && syncAnswer.toLowerCase() !== "no";
645
+ const deleteAnswer = await ask("Delete old .gnosys/ after migration?", "Y");
646
+ doDelete = deleteAnswer.toLowerCase() !== "n" && deleteAnswer.toLowerCase() !== "no";
647
+ }
648
+ // 5. Show summary and confirm
649
+ console.log("\n── Migration Summary ──");
650
+ console.log(` From: ${sourceDir}/.gnosys/`);
651
+ console.log(` To: ${targetDir}/.gnosys/`);
652
+ console.log(` Name: ${identity?.projectName || "(new)"} → ${newName}`);
653
+ console.log(` Memories: ${memFiles.length} files`);
654
+ console.log(` Sync to DB: ${doSync ? "yes" : "no"}`);
655
+ console.log(` Delete old: ${doDelete ? "yes" : "no"}`);
656
+ if (!opts.yes) {
657
+ const confirm = await ask("\nProceed?", "Y");
658
+ if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
659
+ console.log("Aborted.");
660
+ rl?.close();
661
+ return;
662
+ }
663
+ }
664
+ rl?.close();
665
+ // 6. Open central DB
666
+ let centralDb = null;
667
+ try {
668
+ centralDb = GnosysDB.openCentral();
669
+ if (!centralDb.isAvailable())
670
+ centralDb = null;
671
+ }
672
+ catch {
673
+ centralDb = null;
674
+ }
675
+ // 7. Run migration
676
+ console.log("\nMigrating...");
677
+ const result = await migrateProject({
678
+ sourcePath: sourceDir,
679
+ targetPath: targetDir,
680
+ newName,
681
+ deleteSource: doDelete,
682
+ centralDb: centralDb || undefined,
683
+ });
684
+ console.log(` Copied ${result.memoryFileCount} memory files`);
685
+ console.log(` Project: ${result.newIdentity.projectName} (${result.newIdentity.projectId})`);
686
+ console.log(` Path: ${result.newIdentity.workingDirectory}`);
687
+ console.log(` Central DB: ${centralDb ? "updated ✓" : "not available"}`);
688
+ // 8. Sync memories to central DB
689
+ if (doSync && centralDb) {
690
+ console.log("\nSyncing memories to central DB...");
691
+ const matter = (await import("gray-matter")).default;
692
+ const { syncMemoryToDb } = await import("./lib/dbWrite.js");
693
+ const newStorePath = path.join(targetDir, ".gnosys");
694
+ const mdFiles = await glob("**/*.md", {
695
+ cwd: newStorePath,
696
+ ignore: ["**/CHANGELOG.md", "**/MANIFEST.md", "**/.git/**", "**/.obsidian/**"],
697
+ });
698
+ let synced = 0;
699
+ for (const file of mdFiles) {
700
+ try {
701
+ const filePath = path.join(newStorePath, file);
702
+ const raw = await fs.readFile(filePath, "utf-8");
703
+ const parsed = matter(raw);
704
+ if (parsed.data?.id) {
705
+ syncMemoryToDb(centralDb, parsed.data, parsed.content, filePath, result.newIdentity.projectId, "project");
706
+ synced++;
707
+ }
708
+ }
709
+ catch {
710
+ // Skip files that fail to parse
711
+ }
712
+ }
713
+ console.log(` Synced ${synced} memories to central DB`);
714
+ }
715
+ if (centralDb)
716
+ centralDb.close();
717
+ if (doDelete) {
718
+ console.log(`\nOld .gnosys/ at ${sourceDir} removed.`);
719
+ }
720
+ console.log(`\nMigration complete! Run 'gnosys projects' to verify.`);
721
+ }
722
+ catch (err) {
723
+ rl?.close();
724
+ const msg = err instanceof Error ? err.message : String(err);
725
+ console.error(`\nMigration failed: ${msg}`);
726
+ process.exit(1);
727
+ }
728
+ });
555
729
  // ─── gnosys stale ───────────────────────────────────────────────────────
556
730
  program
557
731
  .command("stale")
@@ -649,30 +823,34 @@ program
649
823
  const fullContent = opts.content
650
824
  ? `# ${opts.title || memory.frontmatter.title}\n\n${opts.content}`
651
825
  : undefined;
652
- const updated = await sourceStore.store.updateMemory(memory.relativePath, updates, fullContent);
653
- if (!updated) {
654
- console.error(`Failed to update: ${memPath}`);
826
+ const memoryId = memory.frontmatter.id;
827
+ if (!memoryId) {
828
+ console.error(`Memory has no ID: ${memPath}`);
655
829
  process.exit(1);
656
830
  }
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" });
831
+ let centralDb = null;
832
+ try {
833
+ centralDb = GnosysDB.openCentral();
834
+ const { syncUpdateToDb } = await import("./lib/dbWrite.js");
835
+ syncUpdateToDb(centralDb, memoryId, updates, fullContent);
836
+ // Supersession cross-linking
837
+ if (opts.supersedes && memoryId) {
838
+ const allMemories = await resolver.getAllMemories();
839
+ const supersededMemory = allMemories.find((m) => m.frontmatter.id === opts.supersedes);
840
+ if (supersededMemory) {
841
+ syncUpdateToDb(centralDb, supersededMemory.frontmatter.id, { superseded_by: memoryId, status: "superseded" });
667
842
  console.log(`Cross-linked: ${supersededMemory.frontmatter.title} marked as superseded.`);
668
843
  }
669
844
  }
670
845
  }
846
+ finally {
847
+ centralDb?.close();
848
+ }
671
849
  const changedFields = Object.keys(updates);
672
850
  if (opts.content)
673
851
  changedFields.push("content");
674
- console.log(`Memory updated: ${updated.frontmatter.title}`);
675
- console.log(`Path: ${memory.sourceLabel}:${memory.relativePath}`);
852
+ console.log(`Memory updated: ${opts.title || memory.frontmatter.title}`);
853
+ console.log(`ID: ${memoryId}`);
676
854
  console.log(`Changed: ${changedFields.join(", ")}`);
677
855
  });
678
856
  // ─── gnosys reinforce <memoryId> ────────────────────────────────────────
@@ -701,17 +879,16 @@ program
701
879
  await fs.appendFile(logPath, entry + "\n", "utf-8");
702
880
  // If 'useful', update the memory's modified date (reset decay)
703
881
  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
- }
882
+ let centralDb = null;
883
+ try {
884
+ centralDb = GnosysDB.openCentral();
885
+ const { syncUpdateToDb } = await import("./lib/dbWrite.js");
886
+ syncUpdateToDb(centralDb, memoryId, {
887
+ modified: new Date().toISOString().split("T")[0],
888
+ });
889
+ }
890
+ finally {
891
+ centralDb?.close();
715
892
  }
716
893
  }
717
894
  const messages = {
@@ -787,13 +964,7 @@ program
787
964
  centralDb?.close();
788
965
  }
789
966
  }
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
- }
967
+ // ─── DB-only write ────────────────────────────────────────────
797
968
  let tags;
798
969
  try {
799
970
  tags = JSON.parse(opts.tags);
@@ -802,32 +973,47 @@ program
802
973
  console.error("Invalid --tags JSON. Example: '{\"domain\":[\"auth\"],\"type\":[\"decision\"]}'");
803
974
  process.exit(1);
804
975
  }
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}`);
976
+ let centralDb = null;
977
+ try {
978
+ centralDb = GnosysDB.openCentral();
979
+ const projectId = await resolveProjectId();
980
+ const id = centralDb.getNextId(opts.category, projectId || undefined);
981
+ const now = new Date().toISOString();
982
+ const content = `# ${opts.title}\n\n${opts.content}`;
983
+ const tagsJson = Array.isArray(tags)
984
+ ? JSON.stringify(tags)
985
+ : JSON.stringify(Object.values(tags).flat());
986
+ centralDb.insertMemory({
987
+ id,
988
+ title: opts.title,
989
+ category: opts.category,
990
+ content,
991
+ summary: null,
992
+ tags: tagsJson,
993
+ relevance: opts.relevance || opts.content.slice(0, 200),
994
+ author: opts.author,
995
+ authority: opts.authority,
996
+ confidence: parseFloat(opts.confidence),
997
+ reinforcement_count: 0,
998
+ content_hash: "",
999
+ status: "active",
1000
+ tier: "active",
1001
+ supersedes: null,
1002
+ superseded_by: null,
1003
+ last_reinforced: null,
1004
+ created: now,
1005
+ modified: now,
1006
+ embedding: null,
1007
+ source_path: null,
1008
+ project_id: projectId,
1009
+ scope: "project",
1010
+ });
1011
+ console.log(`Memory added: ${opts.title}`);
1012
+ console.log(`ID: ${id}`);
1013
+ }
1014
+ finally {
1015
+ centralDb?.close();
1016
+ }
831
1017
  });
832
1018
  // ─── gnosys ingest <file> ─────────────────────────────────────────────────
833
1019
  program
@@ -1029,49 +1215,70 @@ Output ONLY the JSON array, no markdown fences.`,
1029
1215
  // Step 2: Check novelty and commit
1030
1216
  let added = 0;
1031
1217
  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}`);
1218
+ let centralDb = null;
1219
+ try {
1220
+ centralDb = GnosysDB.openCentral();
1221
+ const projectId = await resolveProjectId();
1222
+ for (const candidate of candidates) {
1223
+ const searchTerms = candidate.search_terms.join(" ");
1224
+ const existing = search.discover(searchTerms, 3);
1225
+ if (existing.length > 0) {
1226
+ console.log(` ⏭ SKIP: "${candidate.summary}"`);
1227
+ console.log(` Overlaps with: ${existing[0].title}`);
1228
+ skipped++;
1229
+ }
1230
+ else if (opts.dryRun) {
1231
+ console.log(` ➕ WOULD ADD: "${candidate.summary}" [${candidate.type}]`);
1068
1232
  added++;
1069
1233
  }
1070
- catch (err) {
1071
- console.error(` ❌ FAILED: "${candidate.summary}": ${err instanceof Error ? err.message : String(err)}`);
1234
+ else {
1235
+ try {
1236
+ const result = await ingestion.ingest(candidate.summary);
1237
+ const id = centralDb.getNextId(result.category, projectId || undefined);
1238
+ const now = new Date().toISOString();
1239
+ const content = `# ${result.title}\n\n${result.content}`;
1240
+ const resultTags = result.tags;
1241
+ const tagsJson = Array.isArray(resultTags)
1242
+ ? JSON.stringify(resultTags)
1243
+ : JSON.stringify(Object.values(resultTags).flat());
1244
+ centralDb.insertMemory({
1245
+ id,
1246
+ title: result.title,
1247
+ category: result.category,
1248
+ content,
1249
+ summary: null,
1250
+ tags: tagsJson,
1251
+ relevance: result.relevance,
1252
+ author: "ai",
1253
+ authority: "observed",
1254
+ confidence: result.confidence,
1255
+ reinforcement_count: 0,
1256
+ content_hash: "",
1257
+ status: "active",
1258
+ tier: "active",
1259
+ supersedes: null,
1260
+ superseded_by: null,
1261
+ last_reinforced: null,
1262
+ created: now,
1263
+ modified: now,
1264
+ embedding: null,
1265
+ source_path: null,
1266
+ project_id: projectId,
1267
+ scope: "project",
1268
+ });
1269
+ console.log(` ➕ ADDED: "${result.title}"`);
1270
+ console.log(` ID: ${id}`);
1271
+ added++;
1272
+ }
1273
+ catch (err) {
1274
+ console.error(` ❌ FAILED: "${candidate.summary}": ${err instanceof Error ? err.message : String(err)}`);
1275
+ }
1072
1276
  }
1277
+ console.log();
1073
1278
  }
1074
- console.log();
1279
+ }
1280
+ finally {
1281
+ centralDb?.close();
1075
1282
  }
1076
1283
  search.close();
1077
1284
  const mode = opts.dryRun ? "DRY RUN" : "COMMITTED";
@@ -2833,10 +3040,10 @@ program
2833
3040
  process.exit(1);
2834
3041
  }
2835
3042
  });
2836
- // ─── gnosys migrate --to-central ────────────────────────────────────────
3043
+ // ─── gnosys migrate-db ──────────────────────────────────────────────────
2837
3044
  program
2838
- .command("migrate")
2839
- .description("Migrate data. Use --to-central to move per-project stores into the central DB.")
3045
+ .command("migrate-db")
3046
+ .description("Legacy data migration. Use --to-central to move per-project stores into the central DB.")
2840
3047
  .option("--to-central", "Migrate all discovered per-project stores into ~/.gnosys/gnosys.db")
2841
3048
  .option("-v, --verbose", "Verbose output")
2842
3049
  .action(async (opts) => {