create-claude-cabinet 0.27.0 → 0.27.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/lib/migrate-from-omega.js +143 -13
- package/lib/migrate-memory-cmd.js +11 -6
- package/package.json +1 -1
|
@@ -549,6 +549,111 @@ function buildEdgesJson(edges) {
|
|
|
549
549
|
);
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
// ---------------------------------------------------------------------------
|
|
553
|
+
// Merge mode — additive, never clobbers native memory
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
|
|
556
|
+
const OMEGA_SUBDIR = 'omega-migrated';
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Build the MEMORY.md section that indexes omega topic files living under
|
|
560
|
+
* the omega-migrated/ subdir. Carries the preamble marker so a re-run
|
|
561
|
+
* detects already-migrated. Paths are prefixed with the subdir.
|
|
562
|
+
*/
|
|
563
|
+
function buildMergeSection(topicFileMeta, summary) {
|
|
564
|
+
const lines = [
|
|
565
|
+
PREAMBLE_MARKER,
|
|
566
|
+
'',
|
|
567
|
+
`## Migrated from omega (${new Date().toISOString().slice(0, 10)})`,
|
|
568
|
+
'',
|
|
569
|
+
`_${summary.migrated} memories migrated from omega into \`${OMEGA_SUBDIR}/\`. ` +
|
|
570
|
+
`Native memory files above are unchanged. ${summary.edges} edges in ${OMEGA_SUBDIR}/edges.json._`,
|
|
571
|
+
'',
|
|
572
|
+
];
|
|
573
|
+
for (const { topic, file, count } of topicFileMeta) {
|
|
574
|
+
const desc = describeTopic(topic);
|
|
575
|
+
lines.push(`- [${topic}](${OMEGA_SUBDIR}/${file}) (${count}) — ${desc}`);
|
|
576
|
+
}
|
|
577
|
+
return lines.join('\n') + '\n';
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Additive merge: write omega topic files into an omega-migrated/ subdir
|
|
582
|
+
* (zero filename collision with native files), back up the existing dir
|
|
583
|
+
* first, and append/create a MEMORY.md section indexing them. Never
|
|
584
|
+
* overwrites a native file.
|
|
585
|
+
*
|
|
586
|
+
* @returns {{ backupDir, omegaDir, indexedInto }}
|
|
587
|
+
*/
|
|
588
|
+
function mergeIntoExisting(outputDir, topicFiles, edgesJson, mergeSection, opts = {}) {
|
|
589
|
+
const omegaDir = path.join(outputDir, OMEGA_SUBDIR);
|
|
590
|
+
const memoryMdPath = path.join(outputDir, 'MEMORY.md');
|
|
591
|
+
|
|
592
|
+
// GUARD: never clobber whatever already occupies the subdir path. If it
|
|
593
|
+
// exists, it is either a native item the user named `omega-migrated/`, or
|
|
594
|
+
// residue from an interrupted prior merge. We cannot safely tell them
|
|
595
|
+
// apart, and the cost of guessing wrong is permanent loss of native data.
|
|
596
|
+
// Refuse and instruct — the caller surfaces this as a failure with the
|
|
597
|
+
// native dir fully intact (nothing has been written yet at this point).
|
|
598
|
+
if (fs.existsSync(omegaDir)) {
|
|
599
|
+
throw new Error(
|
|
600
|
+
`Refusing to merge: '${OMEGA_SUBDIR}/' already exists at ${omegaDir}. ` +
|
|
601
|
+
`If it is leftover from an interrupted migration, remove it and re-run. ` +
|
|
602
|
+
`If it is your own data, rename it first, then re-run.`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// 1. Back up the existing memory dir wholesale, to a guaranteed-fresh path,
|
|
607
|
+
// BEFORE any destructive step. Never proceed without a backup taken
|
|
608
|
+
// this invocation (a stale/reused backup wouldn't reflect pristine
|
|
609
|
+
// native state).
|
|
610
|
+
let backupDir = opts.backupDir;
|
|
611
|
+
if (!backupDir) {
|
|
612
|
+
const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
|
|
613
|
+
const base = `${outputDir}.pre-omega-merge-${stamp}`;
|
|
614
|
+
backupDir = base;
|
|
615
|
+
for (let n = 1; fs.existsSync(backupDir); n++) backupDir = `${base}-${n}`;
|
|
616
|
+
} else if (fs.existsSync(backupDir)) {
|
|
617
|
+
throw new Error(`Refusing to merge: backup path ${backupDir} already exists.`);
|
|
618
|
+
}
|
|
619
|
+
fs.cpSync(outputDir, backupDir, { recursive: true });
|
|
620
|
+
|
|
621
|
+
// 2. Write omega topic files + edges into the subdir (atomic via staging
|
|
622
|
+
// rename of the subdir). omegaDir is known absent from the guard above.
|
|
623
|
+
const stagingDir = path.join(outputDir, `.${OMEGA_SUBDIR}-staging-${process.pid}`);
|
|
624
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
625
|
+
fs.mkdirSync(stagingDir, { recursive: true });
|
|
626
|
+
for (const [name, content] of Object.entries(topicFiles)) {
|
|
627
|
+
fs.writeFileSync(path.join(stagingDir, name), content, 'utf8');
|
|
628
|
+
}
|
|
629
|
+
if (edgesJson) fs.writeFileSync(path.join(stagingDir, 'edges.json'), edgesJson, 'utf8');
|
|
630
|
+
fs.renameSync(stagingDir, omegaDir);
|
|
631
|
+
|
|
632
|
+
// 3. Append/create MEMORY.md with the omega section (preamble marker
|
|
633
|
+
// included so re-runs detect already-migrated). Idempotent: if the
|
|
634
|
+
// marker is somehow already present, do not append a second section.
|
|
635
|
+
let indexedInto;
|
|
636
|
+
if (fs.existsSync(memoryMdPath)) {
|
|
637
|
+
const existing = fs.readFileSync(memoryMdPath, 'utf8');
|
|
638
|
+
if (existing.includes(PREAMBLE_MARKER)) {
|
|
639
|
+
indexedInto = 'already-indexed';
|
|
640
|
+
} else {
|
|
641
|
+
const sep = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
642
|
+
const merged = existing + sep + mergeSection;
|
|
643
|
+
const tmp = memoryMdPath + '.tmp-' + process.pid;
|
|
644
|
+
fs.writeFileSync(tmp, merged, 'utf8');
|
|
645
|
+
fs.renameSync(tmp, memoryMdPath);
|
|
646
|
+
indexedInto = 'appended-to-existing';
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
const header = `# Memory Index\n\n_Native memory files in this directory predate the index; Claude reads them on demand._\n\n`;
|
|
650
|
+
fs.writeFileSync(memoryMdPath, header + mergeSection, 'utf8');
|
|
651
|
+
indexedInto = 'created-new';
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return { backupDir, omegaDir, indexedInto };
|
|
655
|
+
}
|
|
656
|
+
|
|
552
657
|
// ---------------------------------------------------------------------------
|
|
553
658
|
// Main orchestrator
|
|
554
659
|
// ---------------------------------------------------------------------------
|
|
@@ -564,19 +669,17 @@ async function migrateFromOmega(opts = {}) {
|
|
|
564
669
|
const currentProject = opts.currentProject || resolveCurrentProject(cwd, homeDir);
|
|
565
670
|
const outputDir = resolveOutputDir({ ...opts, cwd, homeDir });
|
|
566
671
|
|
|
672
|
+
// Determine write mode. Foreign content (native memory present, no
|
|
673
|
+
// migration preamble) triggers MERGE — additive, never clobbers — so
|
|
674
|
+
// omega memories land alongside native memory instead of being skipped.
|
|
675
|
+
let mode = 'fresh';
|
|
567
676
|
if (!opts.force) {
|
|
568
677
|
const existing = checkExistingMigration(outputDir);
|
|
569
678
|
if (existing.state === 'migrated') {
|
|
570
679
|
return { migrated: 0, reason: 'already-migrated', outputDir };
|
|
571
680
|
}
|
|
572
681
|
if (existing.state === 'foreign-content' || existing.state === 'partial-or-foreign') {
|
|
573
|
-
|
|
574
|
-
migrated: 0,
|
|
575
|
-
reason: existing.state,
|
|
576
|
-
outputDir,
|
|
577
|
-
details: existing.files || ['MEMORY.md without migration preamble'],
|
|
578
|
-
hint: 'Inspect outputDir. If safe to overwrite, re-run with { force: true }.',
|
|
579
|
-
};
|
|
682
|
+
mode = 'merge';
|
|
580
683
|
}
|
|
581
684
|
}
|
|
582
685
|
|
|
@@ -587,6 +690,10 @@ async function migrateFromOmega(opts = {}) {
|
|
|
587
690
|
const { memories, edges } = readVault(vaultDir);
|
|
588
691
|
|
|
589
692
|
if (memories.length === 0) {
|
|
693
|
+
// Nothing to migrate. In merge mode, leave native memory untouched.
|
|
694
|
+
if (mode === 'merge') {
|
|
695
|
+
return { migrated: 0, reason: 'empty-db', outputDir, mode: 'merge', noop: true };
|
|
696
|
+
}
|
|
590
697
|
const minimalIndex = `${PREAMBLE_MARKER}\n# Memory Index\n\n_Source: migrated from omega on ${new Date()
|
|
591
698
|
.toISOString()
|
|
592
699
|
.slice(0, 10)}. No prior memories migrated — omega database was empty._\n`;
|
|
@@ -636,11 +743,7 @@ async function migrateFromOmega(opts = {}) {
|
|
|
636
743
|
}
|
|
637
744
|
|
|
638
745
|
const memoryMd = buildMemoryMd(topicFileMeta, summary);
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (edges.length > 0) {
|
|
642
|
-
filesToWrite['edges.json'] = buildEdgesJson(edges);
|
|
643
|
-
}
|
|
746
|
+
const edgesJson = edges.length > 0 ? buildEdgesJson(edges) : null;
|
|
644
747
|
|
|
645
748
|
if (opts.dryRun) {
|
|
646
749
|
return {
|
|
@@ -648,13 +751,39 @@ async function migrateFromOmega(opts = {}) {
|
|
|
648
751
|
edges: edges.length,
|
|
649
752
|
outputDir,
|
|
650
753
|
dryRun: true,
|
|
754
|
+
mode,
|
|
651
755
|
topicFiles: topicFileMeta,
|
|
652
|
-
memoryMdPreview: memoryMd,
|
|
756
|
+
memoryMdPreview: mode === 'merge' ? buildMergeSection(topicFileMeta, summary) : memoryMd,
|
|
653
757
|
omegaBin,
|
|
654
758
|
currentProject,
|
|
655
759
|
};
|
|
656
760
|
}
|
|
657
761
|
|
|
762
|
+
if (mode === 'merge') {
|
|
763
|
+
const mergeSection = buildMergeSection(topicFileMeta, summary);
|
|
764
|
+
const { backupDir, omegaDir, indexedInto } = mergeIntoExisting(
|
|
765
|
+
outputDir,
|
|
766
|
+
filesToWrite,
|
|
767
|
+
edgesJson,
|
|
768
|
+
mergeSection,
|
|
769
|
+
opts
|
|
770
|
+
);
|
|
771
|
+
return {
|
|
772
|
+
migrated: memories.length,
|
|
773
|
+
edges: edges.length,
|
|
774
|
+
outputDir,
|
|
775
|
+
mode: 'merge',
|
|
776
|
+
backupDir,
|
|
777
|
+
omegaDir,
|
|
778
|
+
indexedInto,
|
|
779
|
+
topicFiles: topicFileMeta,
|
|
780
|
+
currentProject,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Fresh write (empty dir, or force clobber).
|
|
785
|
+
filesToWrite['MEMORY.md'] = memoryMd;
|
|
786
|
+
if (edgesJson) filesToWrite['edges.json'] = edgesJson;
|
|
658
787
|
const stagingDir = path.join(path.dirname(outputDir), STAGING_PREFIX + process.pid);
|
|
659
788
|
writeStaging(stagingDir, filesToWrite);
|
|
660
789
|
commitStaging(stagingDir, outputDir, { force: opts.force });
|
|
@@ -663,6 +792,7 @@ async function migrateFromOmega(opts = {}) {
|
|
|
663
792
|
migrated: memories.length,
|
|
664
793
|
edges: edges.length,
|
|
665
794
|
outputDir,
|
|
795
|
+
mode: 'fresh',
|
|
666
796
|
topicFiles: topicFileMeta,
|
|
667
797
|
currentProject,
|
|
668
798
|
};
|
|
@@ -221,15 +221,20 @@ async function stepMigrateMemories(ctx) {
|
|
|
221
221
|
if (result.reason === 'already-migrated') {
|
|
222
222
|
return { action: `memories already migrated; reusing existing output at ${result.outputDir}` };
|
|
223
223
|
}
|
|
224
|
-
if (result.reason === 'foreign-content' || result.reason === 'partial-or-foreign') {
|
|
225
|
-
return {
|
|
226
|
-
action: `migrate-from-omega refused: ${result.reason} at ${result.outputDir}. ` +
|
|
227
|
-
`Hint: ${result.hint || 'inspect manually'}. Continuing — omega cleanup steps will still run.`,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
224
|
if (result.reason === 'empty-db') {
|
|
225
|
+
if (result.mode === 'merge') {
|
|
226
|
+
return { action: `omega DB was empty; native memory at ${result.outputDir} left untouched` };
|
|
227
|
+
}
|
|
231
228
|
return { action: `omega DB was empty; minimal MEMORY.md written at ${result.outputDir}` };
|
|
232
229
|
}
|
|
230
|
+
if (result.mode === 'merge') {
|
|
231
|
+
return {
|
|
232
|
+
action:
|
|
233
|
+
`merged ${result.migrated} omega memories (${result.edges || 0} edges) into existing native ` +
|
|
234
|
+
`memory at ${result.outputDir} (omega content under omega-migrated/; backup at ${result.backupDir}). ` +
|
|
235
|
+
`Native memories preserved.`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
233
238
|
return {
|
|
234
239
|
action: `migrated ${result.migrated} memories (${result.edges || 0} edges) → ${result.outputDir}`,
|
|
235
240
|
};
|
package/package.json
CHANGED