99 * Run: bun scripts/backfill-embeddings.ts [--directory <cwd>] [--project <project_path>]
1010 * --directory Project directory used to resolve config and identity.
1111 * --project Only backfill memories for this project_path (must match --directory identity unless --force-project-path).
12+ * --shadow Re-embed the HISTORICAL corpus under the current Synapse shadow
13+ * identity instead of backfilling primary memories. Use this after
14+ * a fingerprint rotation orphaned the shadow measurement cohort;
15+ * it drives the same bounded enqueue path the plugin runs, but to
16+ * completion with progress output.
1217 */
1318import { Database } from "../src/shared/sqlite" ;
14- import { loadPluginConfig } from "../src/config" ;
19+ import { loadPluginConfig , loadPluginConfigDetailed } from "../src/config" ;
1520import {
1621 embedBatchForProject ,
22+ flushShadowEmbeddingBacklog ,
1723 getProjectEmbeddingSnapshot ,
24+ getShadowBackfillRemaining ,
1825 registerProjectEmbedding ,
26+ registerProjectShadowEmbedding ,
1927} from "../src/features/magic-context/memory/embedding" ;
2028import { resolveProjectIdentity } from "../src/features/magic-context/memory/project-identity" ;
2129import { saveEmbedding } from "../src/features/magic-context/memory/storage-memory-embeddings" ;
30+ import { isConfigLoadUntrusted } from "../src/plugin/embedding-bootstrap-helpers" ;
31+ import { resolveEmbeddingRouting } from "../src/plugin/embedding-routing" ;
2232
2333const DB_PATH = `${ process . env . HOME } /.local/share/opencode/storage/plugin/magic-context/context.db` ;
2434function getArg ( name : string ) : string | null {
2535 const index = process . argv . indexOf ( name ) ;
2636 return index >= 0 ? ( process . argv [ index + 1 ] ?? null ) : null ;
2737}
2838
39+ /**
40+ * Drive the shadow historical backfill to completion with progress output.
41+ * Registering the (possibly rotated) shadow identity arms the backfill inside
42+ * the registry; this just resolves the live routing, registers both lanes the
43+ * same way the boot path does, then flushes the bounded queue until the missing
44+ * set is empty.
45+ */
46+ async function runShadowBackfill (
47+ db : Database ,
48+ directory : string ,
49+ projectIdentity : string ,
50+ ) : Promise < void > {
51+ const detailed = loadPluginConfigDetailed ( directory ) ;
52+ if ( isConfigLoadUntrusted ( detailed ) ) {
53+ console . error (
54+ "Config load is untrusted for this project; refusing to run the shadow backfill off a config we don't trust." ,
55+ ) ;
56+ process . exitCode = 1 ;
57+ return ;
58+ }
59+ const routing = await resolveEmbeddingRouting ( {
60+ config : detailed . config ,
61+ projectRoot : directory ,
62+ session : `shadow-backfill:${ projectIdentity } ` ,
63+ } ) ;
64+ for ( const warning of routing . warnings ) console . warn ( `[shadow] ${ warning } ` ) ;
65+ if ( ! routing . shadow ) {
66+ console . error (
67+ "No shadow lane is armed for this project (shadow_embedding disabled or unavailable). Nothing to do." ,
68+ ) ;
69+ return ;
70+ }
71+ registerProjectEmbedding (
72+ db ,
73+ projectIdentity ,
74+ routing . primary ,
75+ {
76+ memoryEnabled : detailed . config . memory . enabled ,
77+ gitCommitEnabled : detailed . config . memory . git_commit_indexing . enabled ,
78+ } ,
79+ directory ,
80+ ) ;
81+ registerProjectShadowEmbedding ( db , projectIdentity , routing . shadow , directory ) ;
82+
83+ const before = getShadowBackfillRemaining ( db , projectIdentity ) ;
84+ console . log (
85+ `Shadow backfill for ${ projectIdentity } : ${ before . memory } memories, ${ before . commit } commits, ${ before . chunk } chunks missing under the current shadow identity.` ,
86+ ) ;
87+ if ( before . memory === 0 && before . commit === 0 && before . chunk === 0 ) {
88+ console . log ( "Nothing to do." ) ;
89+ return ;
90+ }
91+ await flushShadowEmbeddingBacklog ( projectIdentity , ( ) => {
92+ const remaining = getShadowBackfillRemaining ( db , projectIdentity ) ;
93+ console . log (
94+ ` remaining: ${ remaining . memory } memories, ${ remaining . commit } commits, ${ remaining . chunk } chunks` ,
95+ ) ;
96+ } ) ;
97+ const after = getShadowBackfillRemaining ( db , projectIdentity ) ;
98+ console . log (
99+ `Done. Embedded ${ before . memory - after . memory } memories, ${ before . commit - after . commit } commits, ${ before . chunk - after . chunk } chunks. ` +
100+ `Remaining: ${ after . memory } memories, ${ after . commit } commits, ${ after . chunk } chunks.` ,
101+ ) ;
102+ }
103+
29104async function main ( ) {
30105 const directory = getArg ( "--directory" ) ?? process . cwd ( ) ;
31106 const projectFilter = getArg ( "--project" ) ;
@@ -42,6 +117,13 @@ async function main() {
42117
43118 const db = new Database ( DB_PATH ) ;
44119 db . exec ( "PRAGMA journal_mode=WAL" ) ;
120+
121+ if ( process . argv . includes ( "--shadow" ) ) {
122+ await runShadowBackfill ( db , directory , projectIdentity ) ;
123+ db . close ( ) ;
124+ return ;
125+ }
126+
45127 const config = loadPluginConfig ( directory ) ;
46128 registerProjectEmbedding (
47129 db ,
0 commit comments