@goondocks/myco 0.21.0 → 0.21.2
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/bin/myco-run +68 -7
- package/dist/{agent-eval-RJSQI5S2.js → agent-eval-2MQKTXX3.js} +7 -6
- package/dist/{agent-eval-RJSQI5S2.js.map → agent-eval-2MQKTXX3.js.map} +1 -1
- package/dist/{agent-run-2JSYFOKU.js → agent-run-XJBTSVJR.js} +5 -5
- package/dist/{agent-tasks-APFJIM2T.js → agent-tasks-7MWBZOC7.js} +5 -5
- package/dist/{chunk-75Z7UKDY.js → chunk-4D22KTXY.js} +2 -2
- package/dist/{chunk-P66DLD6G.js → chunk-6FBLL7MD.js} +8 -2
- package/dist/chunk-6FBLL7MD.js.map +1 -0
- package/dist/{chunk-JZS6GZ6T.js → chunk-AUIXX33A.js} +10 -3
- package/dist/chunk-AUIXX33A.js.map +1 -0
- package/dist/{chunk-F3OEQYLS.js → chunk-DBBO6FHE.js} +33 -30
- package/dist/{chunk-F3OEQYLS.js.map → chunk-DBBO6FHE.js.map} +1 -1
- package/dist/{chunk-CESKJD44.js → chunk-DMPCC7V6.js} +19 -11
- package/dist/chunk-DMPCC7V6.js.map +1 -0
- package/dist/{chunk-RL5R4CQU.js → chunk-DTWUHHFI.js} +39 -2
- package/dist/{chunk-RL5R4CQU.js.map → chunk-DTWUHHFI.js.map} +1 -1
- package/dist/{chunk-XL75KZGI.js → chunk-EKZG2MCD.js} +7 -3
- package/dist/chunk-EKZG2MCD.js.map +1 -0
- package/dist/{chunk-NGH7U6A3.js → chunk-HCT7RMM2.js} +487 -98
- package/dist/chunk-HCT7RMM2.js.map +1 -0
- package/dist/{chunk-G6QIBNZM.js → chunk-IMW5TJ3O.js} +7 -6
- package/dist/chunk-IMW5TJ3O.js.map +1 -0
- package/dist/chunk-LQIPXVDH.js +17 -0
- package/dist/chunk-LQIPXVDH.js.map +1 -0
- package/dist/{chunk-5ZG4RMUH.js → chunk-N2DGFACQ.js} +2 -2
- package/dist/{chunk-VHNRMM4O.js → chunk-OTQH5KZW.js} +87 -37
- package/dist/chunk-OTQH5KZW.js.map +1 -0
- package/dist/{chunk-6LB7XELY.js → chunk-QATYARI5.js} +15 -13
- package/dist/chunk-QATYARI5.js.map +1 -0
- package/dist/{chunk-LVIY7P35.js → chunk-QLLBJEM7.js} +5 -1
- package/dist/chunk-QLLBJEM7.js.map +1 -0
- package/dist/{chunk-DJ3IHNYO.js → chunk-TFRUDNLI.js} +2 -2
- package/dist/{chunk-R2JIJBCL.js → chunk-TMAXWERS.js} +87 -4
- package/dist/chunk-TMAXWERS.js.map +1 -0
- package/dist/chunk-TSM6VESW.js +25 -0
- package/dist/chunk-TSM6VESW.js.map +1 -0
- package/dist/{chunk-ILJPRYES.js → chunk-USVFEWYL.js} +2 -2
- package/dist/{chunk-JR54LTPP.js → chunk-W5L5IHP5.js} +3 -3
- package/dist/{chunk-BUTL6IFS.js → chunk-Z55WGA2J.js} +2 -2
- package/dist/{chunk-NGROSFOH.js → chunk-Z66IT5KL.js} +14 -9
- package/dist/chunk-Z66IT5KL.js.map +1 -0
- package/dist/{cli-LNYSTDQM.js → cli-DDHTHU2J.js} +37 -37
- package/dist/{client-NWE4TCNO.js → client-PQU53UQU.js} +5 -3
- package/dist/{detect-PXNM6TA7.js → detect-7NUD5B5R.js} +2 -2
- package/dist/{doctor-TI7EZ3RW.js → doctor-QK6KFY6H.js} +6 -6
- package/dist/{executor-F2YU7HXJ.js → executor-FJCMNSXM.js} +11 -10
- package/dist/{init-KG3TYVGE.js → init-GQPD6HHX.js} +9 -9
- package/dist/{installer-UMH7OJ5A.js → installer-N4UTEACX.js} +2 -2
- package/dist/{loader-NAVVZK63.js → loader-UDNUMEDA.js} +3 -2
- package/dist/{main-5PRQNEEE.js → main-4HKTZFIM.js} +469 -187
- package/dist/main-4HKTZFIM.js.map +1 -0
- package/dist/{open-5A27BCSB.js → open-3P3DDAOA.js} +5 -5
- package/dist/{post-compact-USAODKPQ.js → post-compact-QA5LME2J.js} +7 -7
- package/dist/{post-tool-use-GMMSYBII.js → post-tool-use-QRZMPNYL.js} +6 -6
- package/dist/{post-tool-use-failure-NZVSL2PO.js → post-tool-use-failure-XNHIKBZG.js} +7 -7
- package/dist/{pre-compact-LZ57DLUS.js → pre-compact-HDV6X5QM.js} +7 -7
- package/dist/{registry-M2Z5QBWH.js → registry-F3THYC5M.js} +4 -3
- package/dist/{remove-T3KE6C5N.js → remove-USQDLGTJ.js} +7 -7
- package/dist/{restart-YWDEVZUJ.js → restart-FQLZE2TW.js} +6 -6
- package/dist/{search-GKFDGELR.js → search-5COKV6TD.js} +6 -6
- package/dist/{server-AHUR6CWF.js → server-KRMBRW4T.js} +23 -7
- package/dist/{server-AHUR6CWF.js.map → server-KRMBRW4T.js.map} +1 -1
- package/dist/{session-2ZEPLWW6.js → session-NJCUW3OX.js} +5 -5
- package/dist/{session-end-LWJYQAXX.js → session-end-XD27GRYF.js} +6 -6
- package/dist/{session-start-WTA6GCOQ.js → session-start-RDTXUSYL.js} +11 -11
- package/dist/{setup-llm-E7UU5IO7.js → setup-llm-FYPPJI6W.js} +5 -5
- package/dist/src/agent/definitions/tasks/cortex-instructions.yaml +63 -41
- package/dist/src/agent/definitions/tasks/skill-evolve.yaml +178 -22
- package/dist/src/agent/definitions/tasks/skill-generate.yaml +20 -6
- package/dist/src/agent/definitions/tasks/vault-evolve.yaml +65 -55
- package/dist/src/cli.js +1 -1
- package/dist/src/daemon/main.js +1 -1
- package/dist/src/hooks/post-tool-use.js +1 -1
- package/dist/src/hooks/session-end.js +1 -1
- package/dist/src/hooks/session-start.js +1 -1
- package/dist/src/hooks/stop.js +1 -1
- package/dist/src/hooks/user-prompt-submit.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/symbionts/manifests/opencode.yaml +7 -0
- package/dist/src/symbionts/templates/agents-starter.md +1 -1
- package/dist/src/symbionts/templates/opencode/plugin.ts +41 -1
- package/dist/src/symbionts/templates/pi/plugin.ts +12 -1
- package/dist/{stats-DFG6S23S.js → stats-JCLZLA5G.js} +6 -6
- package/dist/{stop-WRBTXEVT.js → stop-B7XCXEM5.js} +6 -6
- package/dist/{stop-failure-32MGIG2Q.js → stop-failure-R6QZCUOZ.js} +7 -7
- package/dist/{subagent-start-VFGHQFVL.js → subagent-start-N7A622F3.js} +7 -7
- package/dist/{subagent-stop-663FXG3P.js → subagent-stop-SVOG5MZJ.js} +7 -7
- package/dist/{task-completed-ZCQYEFMZ.js → task-completed-3DL5LJXF.js} +7 -7
- package/dist/{team-JTI5CDUO.js → team-VJ3M263F.js} +3 -3
- package/dist/ui/assets/{index-DGf1h-Ha.js → index-O1kNWlWM.js} +119 -119
- package/dist/ui/assets/index-z2Jm8i4A.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/{update-3NBQTG32.js → update-TVXAUJMZ.js} +45 -11
- package/dist/update-TVXAUJMZ.js.map +1 -0
- package/dist/{user-prompt-submit-ME2TBKOS.js → user-prompt-submit-KYO2VGLB.js} +10 -9
- package/dist/user-prompt-submit-KYO2VGLB.js.map +1 -0
- package/dist/{version-GQAFBBPX.js → version-LDFEALUJ.js} +2 -2
- package/package.json +1 -1
- package/skills/myco-rules/SKILL.md +94 -0
- package/skills/{rules → myco-rules}/references/rules-bad-example.md +1 -1
- package/skills/{rules → myco-rules}/references/rules-good-example.md +1 -1
- package/dist/chunk-6LB7XELY.js.map +0 -1
- package/dist/chunk-CESKJD44.js.map +0 -1
- package/dist/chunk-CUDIZJY7.js +0 -36
- package/dist/chunk-CUDIZJY7.js.map +0 -1
- package/dist/chunk-G6QIBNZM.js.map +0 -1
- package/dist/chunk-JZS6GZ6T.js.map +0 -1
- package/dist/chunk-LVIY7P35.js.map +0 -1
- package/dist/chunk-NGH7U6A3.js.map +0 -1
- package/dist/chunk-NGROSFOH.js.map +0 -1
- package/dist/chunk-P66DLD6G.js.map +0 -1
- package/dist/chunk-R2JIJBCL.js.map +0 -1
- package/dist/chunk-VHNRMM4O.js.map +0 -1
- package/dist/chunk-XL75KZGI.js.map +0 -1
- package/dist/main-5PRQNEEE.js.map +0 -1
- package/dist/ui/assets/index-_OP4ifzH.css +0 -1
- package/dist/update-3NBQTG32.js.map +0 -1
- package/dist/user-prompt-submit-ME2TBKOS.js.map +0 -1
- package/skills/myco-curate/SKILL.md +0 -86
- package/skills/rules/SKILL.md +0 -214
- /package/dist/{agent-run-2JSYFOKU.js.map → agent-run-XJBTSVJR.js.map} +0 -0
- /package/dist/{agent-tasks-APFJIM2T.js.map → agent-tasks-7MWBZOC7.js.map} +0 -0
- /package/dist/{chunk-75Z7UKDY.js.map → chunk-4D22KTXY.js.map} +0 -0
- /package/dist/{chunk-5ZG4RMUH.js.map → chunk-N2DGFACQ.js.map} +0 -0
- /package/dist/{chunk-DJ3IHNYO.js.map → chunk-TFRUDNLI.js.map} +0 -0
- /package/dist/{chunk-ILJPRYES.js.map → chunk-USVFEWYL.js.map} +0 -0
- /package/dist/{chunk-JR54LTPP.js.map → chunk-W5L5IHP5.js.map} +0 -0
- /package/dist/{chunk-BUTL6IFS.js.map → chunk-Z55WGA2J.js.map} +0 -0
- /package/dist/{cli-LNYSTDQM.js.map → cli-DDHTHU2J.js.map} +0 -0
- /package/dist/{client-NWE4TCNO.js.map → client-PQU53UQU.js.map} +0 -0
- /package/dist/{detect-PXNM6TA7.js.map → detect-7NUD5B5R.js.map} +0 -0
- /package/dist/{doctor-TI7EZ3RW.js.map → doctor-QK6KFY6H.js.map} +0 -0
- /package/dist/{executor-F2YU7HXJ.js.map → executor-FJCMNSXM.js.map} +0 -0
- /package/dist/{init-KG3TYVGE.js.map → init-GQPD6HHX.js.map} +0 -0
- /package/dist/{installer-UMH7OJ5A.js.map → installer-N4UTEACX.js.map} +0 -0
- /package/dist/{loader-NAVVZK63.js.map → loader-UDNUMEDA.js.map} +0 -0
- /package/dist/{open-5A27BCSB.js.map → open-3P3DDAOA.js.map} +0 -0
- /package/dist/{post-compact-USAODKPQ.js.map → post-compact-QA5LME2J.js.map} +0 -0
- /package/dist/{post-tool-use-GMMSYBII.js.map → post-tool-use-QRZMPNYL.js.map} +0 -0
- /package/dist/{post-tool-use-failure-NZVSL2PO.js.map → post-tool-use-failure-XNHIKBZG.js.map} +0 -0
- /package/dist/{pre-compact-LZ57DLUS.js.map → pre-compact-HDV6X5QM.js.map} +0 -0
- /package/dist/{registry-M2Z5QBWH.js.map → registry-F3THYC5M.js.map} +0 -0
- /package/dist/{remove-T3KE6C5N.js.map → remove-USQDLGTJ.js.map} +0 -0
- /package/dist/{restart-YWDEVZUJ.js.map → restart-FQLZE2TW.js.map} +0 -0
- /package/dist/{search-GKFDGELR.js.map → search-5COKV6TD.js.map} +0 -0
- /package/dist/{session-2ZEPLWW6.js.map → session-NJCUW3OX.js.map} +0 -0
- /package/dist/{session-end-LWJYQAXX.js.map → session-end-XD27GRYF.js.map} +0 -0
- /package/dist/{session-start-WTA6GCOQ.js.map → session-start-RDTXUSYL.js.map} +0 -0
- /package/dist/{setup-llm-E7UU5IO7.js.map → setup-llm-FYPPJI6W.js.map} +0 -0
- /package/dist/{stats-DFG6S23S.js.map → stats-JCLZLA5G.js.map} +0 -0
- /package/dist/{stop-WRBTXEVT.js.map → stop-B7XCXEM5.js.map} +0 -0
- /package/dist/{stop-failure-32MGIG2Q.js.map → stop-failure-R6QZCUOZ.js.map} +0 -0
- /package/dist/{subagent-start-VFGHQFVL.js.map → subagent-start-N7A622F3.js.map} +0 -0
- /package/dist/{subagent-stop-663FXG3P.js.map → subagent-stop-SVOG5MZJ.js.map} +0 -0
- /package/dist/{task-completed-ZCQYEFMZ.js.map → task-completed-3DL5LJXF.js.map} +0 -0
- /package/dist/{team-JTI5CDUO.js.map → team-VJ3M263F.js.map} +0 -0
- /package/dist/{version-GQAFBBPX.js.map → version-LDFEALUJ.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/db/schema-ddl.ts","../src/constants/skill-candidate-status.ts","../src/plans/identity.ts","../src/db/migrations.ts","../src/db/schema.ts"],"sourcesContent":["/**\n * DDL constants for all Myco vault tables, FTS5 virtual tables,\n * sync triggers, and secondary indexes.\n *\n * Extracted from schema.ts for readability -- these are pure string\n * constants with no runtime behaviour.\n */\n\n// ---------------------------------------------------------------------------\n// Table DDL statements\n// ---------------------------------------------------------------------------\n\nconst SCHEMA_VERSION_TABLE = `\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )`;\n\n// -- Capture Layer ----------------------------------------------------------\n\nconst SESSIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent TEXT NOT NULL,\n \"user\" TEXT,\n project_root TEXT,\n branch TEXT,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n status TEXT DEFAULT 'active',\n prompt_count INTEGER DEFAULT 0,\n tool_count INTEGER DEFAULT 0,\n title TEXT,\n summary TEXT,\n transcript_path TEXT,\n parent_session_id TEXT,\n parent_session_reason TEXT,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst PROMPT_BATCHES_TABLE = `\n CREATE TABLE IF NOT EXISTS prompt_batches (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n prompt_number INTEGER,\n user_prompt TEXT,\n response_summary TEXT,\n classification TEXT,\n started_at INTEGER,\n ended_at INTEGER,\n status TEXT DEFAULT 'active',\n activity_count INTEGER DEFAULT 0,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ACTIVITIES_TABLE = `\n CREATE TABLE IF NOT EXISTS activities (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n tool_name TEXT NOT NULL,\n tool_input TEXT,\n tool_output_summary TEXT,\n file_path TEXT,\n files_affected TEXT,\n duration_ms INTEGER,\n success INTEGER DEFAULT 1,\n error_message TEXT,\n timestamp INTEGER NOT NULL,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL\n )`;\n\nconst PLANS_TABLE = `\n CREATE TABLE IF NOT EXISTS plans (\n id TEXT PRIMARY KEY,\n logical_key TEXT NOT NULL,\n status TEXT DEFAULT 'active',\n author TEXT,\n title TEXT,\n content TEXT,\n source_path TEXT,\n tags TEXT,\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n content_hash TEXT,\n processed INTEGER DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ARTIFACTS_TABLE = `\n CREATE TABLE IF NOT EXISTS artifacts (\n id TEXT PRIMARY KEY,\n artifact_type TEXT,\n source_path TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT,\n last_captured_by TEXT,\n tags TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst TEAM_MEMBERS_TABLE = `\n CREATE TABLE IF NOT EXISTS team_members (\n id TEXT PRIMARY KEY,\n \"user\" TEXT NOT NULL,\n role TEXT,\n joined TEXT,\n tags TEXT,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ATTACHMENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS attachments (\n id TEXT PRIMARY KEY,\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n file_path TEXT NOT NULL,\n media_type TEXT,\n description TEXT,\n data BLOB,\n content_hash TEXT,\n created_at INTEGER NOT NULL\n )`;\n\n// -- Intelligence Layer -----------------------------------------------------\n\nconst AGENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n provider TEXT,\n model TEXT,\n system_prompt_hash TEXT,\n config TEXT,\n source TEXT NOT NULL DEFAULT 'built-in',\n system_prompt TEXT,\n max_turns INTEGER,\n timeout_seconds INTEGER,\n tool_access TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL,\n updated_at INTEGER\n )`;\n\nconst SPORES_TABLE = `\n CREATE TABLE IF NOT EXISTS spores (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n observation_type TEXT NOT NULL,\n status TEXT DEFAULT 'active',\n content TEXT NOT NULL,\n context TEXT,\n importance INTEGER DEFAULT 5,\n file_path TEXT,\n tags TEXT,\n content_hash TEXT UNIQUE,\n properties TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ENTITIES_TABLE = `\n CREATE TABLE IF NOT EXISTS entities (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n properties TEXT,\n first_seen INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n status TEXT DEFAULT 'active',\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (agent_id, type, name)\n )`;\n\nconst GRAPH_EDGES_TABLE = `\n CREATE TABLE IF NOT EXISTS graph_edges (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source_id TEXT NOT NULL,\n source_type TEXT NOT NULL,\n target_id TEXT NOT NULL,\n target_type TEXT NOT NULL,\n type TEXT NOT NULL,\n session_id TEXT,\n confidence REAL DEFAULT 1.0,\n properties TEXT,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ENTITY_MENTIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS entity_mentions (\n entity_id TEXT NOT NULL REFERENCES entities(id),\n note_id TEXT NOT NULL,\n note_type TEXT NOT NULL,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (entity_id, note_id, note_type, agent_id)\n )`;\n\nconst RESOLUTION_EVENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS resolution_events (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n spore_id TEXT NOT NULL REFERENCES spores(id),\n action TEXT NOT NULL,\n new_spore_id TEXT,\n reason TEXT,\n session_id TEXT,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst DIGEST_EXTRACTS_TABLE = `\n CREATE TABLE IF NOT EXISTS digest_extracts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n tier INTEGER NOT NULL,\n content TEXT NOT NULL,\n substrate_hash TEXT,\n generated_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (agent_id, tier)\n )`;\n\nexport const CORTEX_INSTRUCTIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS cortex_instructions (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n content TEXT NOT NULL,\n input_hash TEXT NOT NULL,\n source_run_id TEXT,\n generated_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\n// -- Agent State Layer ------------------------------------------------------\n\nconst AGENT_RUNS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_runs (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n task TEXT,\n instruction TEXT,\n status TEXT DEFAULT 'pending',\n runtime TEXT,\n provider TEXT,\n model TEXT,\n session_ref TEXT,\n resumable INTEGER DEFAULT 0,\n resume_status TEXT,\n resume_mode TEXT,\n resumed_at INTEGER,\n checkpoints TEXT,\n usage_data TEXT,\n started_at INTEGER,\n completed_at INTEGER,\n tokens_used INTEGER,\n cost_usd REAL,\n actual_cost_usd REAL,\n estimated_cost_usd REAL,\n cost_source TEXT,\n cost_data TEXT,\n actions_taken TEXT,\n error TEXT,\n dry_run INTEGER NOT NULL DEFAULT 0,\n evaluation_id TEXT,\n reasoning_level TEXT,\n execution_overrides TEXT\n )`;\n\nconst AGENT_REPORTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_reports (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id),\n agent_id TEXT NOT NULL REFERENCES agents(id),\n action TEXT NOT NULL,\n summary TEXT NOT NULL,\n details TEXT,\n created_at INTEGER NOT NULL\n )`;\n\nconst AGENT_TURNS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_turns (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id),\n agent_id TEXT NOT NULL REFERENCES agents(id),\n turn_number INTEGER NOT NULL,\n tool_name TEXT NOT NULL,\n tool_input TEXT,\n tool_output_summary TEXT,\n started_at INTEGER,\n completed_at INTEGER\n )`;\n\nconst AGENT_TASKS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_tasks (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source TEXT NOT NULL DEFAULT 'built-in',\n display_name TEXT,\n description TEXT,\n prompt TEXT NOT NULL,\n is_default INTEGER DEFAULT 0,\n tool_overrides TEXT,\n model TEXT,\n config TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER\n )`;\n\nconst AGENT_STATE_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_state (\n agent_id TEXT NOT NULL REFERENCES agents(id),\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (agent_id, key)\n )`;\n\n// -- Sync Layer -------------------------------------------------------------\n\nexport const TEAM_OUTBOX_TABLE = `\n CREATE TABLE IF NOT EXISTS team_outbox (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n table_name TEXT NOT NULL,\n row_id TEXT NOT NULL,\n operation TEXT NOT NULL DEFAULT 'upsert',\n payload TEXT NOT NULL,\n machine_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n sent_at INTEGER,\n retry_count INTEGER NOT NULL DEFAULT 0,\n last_attempt_at INTEGER\n )`;\n\n// -- Logging Layer ----------------------------------------------------------\n\nexport const LOG_ENTRIES_TABLE = `\n CREATE TABLE IF NOT EXISTS log_entries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp TEXT NOT NULL,\n level TEXT NOT NULL,\n component TEXT NOT NULL,\n kind TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT,\n session_id TEXT\n )`;\n\n// -- Skills Layer -----------------------------------------------------------\n\nexport const SKILL_CANDIDATES_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_candidates (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n topic TEXT NOT NULL,\n rationale TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.0,\n status TEXT NOT NULL DEFAULT 'identified',\n source_ids TEXT NOT NULL DEFAULT '[]',\n skill_id TEXT,\n supersedes TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n approved_at INTEGER,\n synced_at INTEGER\n )`;\n\nexport const SKILL_RECORDS_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_records (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n embedded INTEGER DEFAULT 0,\n generation INTEGER NOT NULL DEFAULT 1,\n candidate_id TEXT REFERENCES skill_candidates(id),\n source_ids TEXT NOT NULL DEFAULT '[]',\n path TEXT NOT NULL,\n usage_count INTEGER NOT NULL DEFAULT 0,\n last_used_at INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n properties TEXT NOT NULL DEFAULT '{}',\n synced_at INTEGER\n )`;\n\nexport const SKILL_LINEAGE_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_lineage (\n id TEXT PRIMARY KEY,\n skill_id TEXT NOT NULL REFERENCES skill_records(id),\n generation INTEGER NOT NULL,\n action TEXT NOT NULL,\n rationale TEXT NOT NULL,\n source_ids_added TEXT NOT NULL DEFAULT '[]',\n content_snapshot TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )`;\n\nexport const SKILL_USAGE_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_usage (\n id TEXT PRIMARY KEY,\n skill_id TEXT NOT NULL REFERENCES skill_records(id),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n detected_at INTEGER NOT NULL\n )`;\n\n// -- Notifications Layer ----------------------------------------------------\n\nexport const NOTIFICATIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS notifications (\n id TEXT PRIMARY KEY,\n domain TEXT NOT NULL,\n type TEXT NOT NULL,\n level TEXT NOT NULL DEFAULT 'info',\n title TEXT NOT NULL,\n message TEXT,\n mode TEXT NOT NULL DEFAULT 'banner',\n status TEXT NOT NULL DEFAULT 'unread',\n link TEXT,\n metadata TEXT,\n created_at INTEGER NOT NULL\n )`;\n\n// -- Eval Harness Layer -----------------------------------------------------\n\n/**\n * Append-only log of every write a dry-run attempted. Each row captures the\n * tool that was called, the JSON-encoded arguments, the synthetic payload we\n * returned to the agent, and any stub id we minted for a synthetic resource.\n *\n * Append-only invariant: enforced at the query layer — no UPDATE or DELETE\n * helper is exposed. The ON DELETE CASCADE on `run_id` is intentional so a\n * future purge of `agent_runs` (e.g. retention job) also removes the\n * corresponding intents in a single atomic step. Rows must never be deleted\n * except as a side effect of a parent `agent_runs` delete.\n */\nexport const AGENT_RUN_WRITE_INTENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_run_write_intents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,\n phase_id TEXT,\n tool_name TEXT NOT NULL,\n tool_input TEXT NOT NULL,\n synthetic_output TEXT NOT NULL,\n stub_id TEXT,\n recorded_at INTEGER NOT NULL\n )`;\n\n/**\n * Append-only history of digest_extracts rows. A new revision is inserted\n * every time a real (non-dry) run overwrites an existing digest. Rollback\n * restores an old revision and records a fresh revision to preserve the\n * append-only invariant.\n */\nexport const DIGEST_EXTRACT_REVISIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS digest_extract_revisions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n tier INTEGER NOT NULL,\n content TEXT NOT NULL,\n metadata TEXT,\n run_id TEXT REFERENCES agent_runs(id) ON DELETE SET NULL,\n parent_revision_id INTEGER REFERENCES digest_extract_revisions(id),\n created_at INTEGER NOT NULL\n )`;\n\n/**\n * Matrix grouping record for evaluation runs. Child runs link back via\n * `agent_runs.evaluation_id` — code-level integrity, no FK because child\n * runs are themselves normal agent_runs rows.\n */\nexport const AGENT_RUN_EVALUATIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_run_evaluations (\n id TEXT PRIMARY KEY,\n task_id TEXT NOT NULL,\n matrix_json TEXT NOT NULL,\n notes TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n completed_at INTEGER\n )`;\n\n// -- FTS5 Virtual Tables ----------------------------------------------------\n\nexport const FTS_TABLES = [\n `CREATE VIRTUAL TABLE IF NOT EXISTS prompt_batches_fts\n USING fts5(user_prompt, response_summary, content='prompt_batches', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS activities_fts\n USING fts5(tool_name, tool_input, file_path, content='activities', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS log_entries_fts\n USING fts5(message, content='log_entries', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS spores_fts\n USING fts5(content, content='spores', content_rowid='rowid')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts\n USING fts5(title, summary, content='sessions', content_rowid='rowid')`,\n\n // FTS5 sync triggers for log_entries (external-content table)\n `CREATE TRIGGER IF NOT EXISTS log_entries_ai AFTER INSERT ON log_entries BEGIN\n INSERT INTO log_entries_fts(rowid, message) VALUES (new.id, new.message);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS log_entries_ad AFTER DELETE ON log_entries BEGIN\n INSERT INTO log_entries_fts(log_entries_fts, rowid, message) VALUES('delete', old.id, old.message);\n END`,\n\n // FTS5 sync triggers for prompt_batches\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ai AFTER INSERT ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_au AFTER UPDATE OF user_prompt, response_summary ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ad AFTER DELETE ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n END`,\n\n // FTS5 sync triggers for spores\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ai AFTER INSERT ON spores BEGIN\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS spores_fts_au AFTER UPDATE OF content ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ad AFTER DELETE ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n END`,\n\n // FTS5 sync triggers for sessions\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ai AFTER INSERT ON sessions BEGIN\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_au AFTER UPDATE OF title, summary ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ad AFTER DELETE ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n END`,\n];\n\n// -- Indexes ----------------------------------------------------------------\n\nexport const SECONDARY_INDEXES = [\n // Sessions\n 'CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions (status)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_processed ON sessions (processed)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions (started_at)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions (agent)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_created_at ON sessions (created_at)',\n\n // Prompt batches\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_session_id ON prompt_batches (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_processed ON prompt_batches (processed)',\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_status ON prompt_batches (status)',\n\n // Activities\n 'CREATE INDEX IF NOT EXISTS idx_activities_session_id ON activities (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_prompt_batch_id ON activities (prompt_batch_id)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_tool_name ON activities (tool_name)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_timestamp ON activities (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_processed ON activities (processed)',\n\n // Spores\n 'CREATE INDEX IF NOT EXISTS idx_spores_agent_id ON spores (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_session_id ON spores (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_status ON spores (status)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_observation_type ON spores (observation_type)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_created_at ON spores (created_at)',\n\n // Entities\n 'CREATE INDEX IF NOT EXISTS idx_entities_agent_id ON entities (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_entities_type ON entities (type)',\n\n // Graph edges\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id, source_type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id, target_type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_type ON graph_edges (type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_agent ON graph_edges (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_source_type ON graph_edges (source_id, type)',\n\n // Entity mentions\n 'CREATE INDEX IF NOT EXISTS idx_entity_mentions_entity_id ON entity_mentions (entity_id)',\n 'CREATE INDEX IF NOT EXISTS idx_entity_mentions_agent_id ON entity_mentions (agent_id)',\n\n // Resolution events\n 'CREATE INDEX IF NOT EXISTS idx_resolution_events_spore_id ON resolution_events (spore_id)',\n 'CREATE INDEX IF NOT EXISTS idx_resolution_events_agent_id ON resolution_events (agent_id)',\n\n // Digest extracts\n 'CREATE INDEX IF NOT EXISTS idx_digest_extracts_agent_id ON digest_extracts (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_cortex_instructions_agent_id ON cortex_instructions (agent_id)',\n\n // Agent runs\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent_id ON agent_runs (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs (status)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent_status ON agent_runs (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_completed ON agent_runs (task, status, completed_at)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_status_started_at ON agent_runs (task, status, started_at)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_resumable_task ON agent_runs (task, resumable, completed_at)',\n\n // Agent reports\n 'CREATE INDEX IF NOT EXISTS idx_agent_reports_run_id ON agent_reports (run_id)',\n\n // Agent turns\n 'CREATE INDEX IF NOT EXISTS idx_agent_turns_run_id ON agent_turns (run_id)',\n\n // Agent tasks\n 'CREATE INDEX IF NOT EXISTS idx_agent_tasks_agent_id ON agent_tasks (agent_id)',\n\n // Plans\n 'CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_logical_key ON plans (logical_key)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_session_id ON plans (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_source_path ON plans (source_path)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_content_hash ON plans (content_hash)',\n // Attachments\n 'CREATE INDEX IF NOT EXISTS idx_attachments_file_path ON attachments (file_path)',\n\n // Team outbox\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_pending ON team_outbox (sent_at, created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_table_name ON team_outbox (table_name)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_row_lookup ON team_outbox (table_name, row_id)',\n\n // Machine ID (synced tables)\n 'CREATE INDEX IF NOT EXISTS idx_sessions_machine_id ON sessions (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_machine_id ON spores (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_machine_id ON graph_edges (machine_id)',\n\n // Skill candidates\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_id ON skill_candidates (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_status ON skill_candidates (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_machine_id ON skill_candidates (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_status ON skill_candidates (agent_id, status)',\n\n // Skill records\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_id ON skill_records (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_status ON skill_records (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_name ON skill_records (name)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_machine_id ON skill_records (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_status ON skill_records (agent_id, status)',\n\n // Skill lineage\n 'CREATE INDEX IF NOT EXISTS idx_skill_lineage_skill_id ON skill_lineage (skill_id)',\n\n // Skill usage\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_id ON skill_usage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_session_id ON skill_usage (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_session ON skill_usage (skill_id, session_id)',\n\n // Log entries\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_timestamp ON log_entries (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_level ON log_entries (level)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_component ON log_entries (component)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_kind ON log_entries (kind)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_session_id ON log_entries (session_id)',\n\n // Notifications\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status ON notifications (status)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_domain ON notifications (domain)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications (created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status_created ON notifications (status, created_at)',\n\n // Eval harness\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id ON agent_run_write_intents (run_id)',\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id_tool ON agent_run_write_intents (run_id, tool_name)',\n 'CREATE INDEX IF NOT EXISTS idx_digest_revisions_agent_tier ON digest_extract_revisions (agent_id, tier, created_at DESC)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_evaluation_id ON agent_runs (evaluation_id)',\n];\n\n// -- Ordered table creation -------------------------------------------------\n\nexport const TABLE_DDLS = [\n SCHEMA_VERSION_TABLE,\n // Capture layer (order matters for FK references)\n SESSIONS_TABLE,\n PROMPT_BATCHES_TABLE,\n ACTIVITIES_TABLE,\n PLANS_TABLE,\n ARTIFACTS_TABLE,\n TEAM_MEMBERS_TABLE,\n ATTACHMENTS_TABLE,\n // Intelligence layer\n AGENTS_TABLE,\n SPORES_TABLE,\n ENTITIES_TABLE,\n GRAPH_EDGES_TABLE,\n ENTITY_MENTIONS_TABLE,\n RESOLUTION_EVENTS_TABLE,\n DIGEST_EXTRACTS_TABLE,\n CORTEX_INSTRUCTIONS_TABLE,\n // Agent state layer\n AGENT_RUNS_TABLE,\n AGENT_REPORTS_TABLE,\n AGENT_TURNS_TABLE,\n AGENT_TASKS_TABLE,\n AGENT_STATE_TABLE,\n // Skills layer\n SKILL_CANDIDATES_TABLE,\n SKILL_RECORDS_TABLE,\n SKILL_LINEAGE_TABLE,\n SKILL_USAGE_TABLE,\n // Sync layer\n TEAM_OUTBOX_TABLE,\n // Logging layer\n LOG_ENTRIES_TABLE,\n // Notifications layer\n NOTIFICATIONS_TABLE,\n // Eval harness layer\n AGENT_RUN_WRITE_INTENTS_TABLE,\n DIGEST_EXTRACT_REVISIONS_TABLE,\n AGENT_RUN_EVALUATIONS_TABLE,\n];\n","/**\n * Canonical string values for skill_candidates.status.\n *\n * The skill lifecycle uses four states:\n * - identified: discovered by skill-survey, awaiting human review\n * - approved: human approved; queued for skill-generate\n * - generated: vault_finalize_skill promoted the staged skill to live\n * - dismissed: retired (human or agent)\n *\n * See docs/superpowers/plans/2026-04-08-skill-lifecycle-audit-and-staging.md\n * for the lifecycle transitions and who is allowed to make each one.\n */\nexport const CANDIDATE_STATUS = {\n IDENTIFIED: 'identified',\n APPROVED: 'approved',\n GENERATED: 'generated',\n DISMISSED: 'dismissed',\n} as const;\n\nexport type SkillCandidateStatus = (typeof CANDIDATE_STATUS)[keyof typeof CANDIDATE_STATUS];\n\n/**\n * Statuses the agent-facing vault_skill_candidates tool is allowed to set\n * on an update. Human-only transitions (approved) and internal-only\n * transitions (generated, via vault_finalize_skill) are excluded.\n */\nexport const AGENT_SETTABLE_STATUSES: readonly SkillCandidateStatus[] = [\n CANDIDATE_STATUS.IDENTIFIED,\n CANDIDATE_STATUS.DISMISSED,\n];\n\n/**\n * Statuses REST callers (UI + MCP myco_skill_candidates) are allowed to\n * set. 'generated' is internal — only vault_finalize_skill sets it, and\n * that path calls updateCandidate directly rather than going through REST.\n */\nexport const REST_SETTABLE_STATUSES: readonly SkillCandidateStatus[] = [\n CANDIDATE_STATUS.IDENTIFIED,\n CANDIDATE_STATUS.APPROVED,\n CANDIDATE_STATUS.DISMISSED,\n];\n\n/**\n * Composite UI filter value that the REST handler translates into a\n * multi-status query (`status IN ('approved', 'generated')`). Kept here\n * so the UI and backend share a single source of truth for the wire\n * encoding.\n */\nexport const PIPELINE_FILTER_VALUE = `${CANDIDATE_STATUS.APPROVED},${CANDIDATE_STATUS.GENERATED}`;\n","import { createHash } from 'node:crypto';\nimport path from 'node:path';\n\nexport const TRANSCRIPT_SOURCE_PREFIX = 'transcript:';\n\nconst PLAN_ID_HASH_LENGTH = 16;\nconst PLAN_PATH_KEY_PREFIX = 'path:';\nconst PLAN_SESSION_KEY_PREFIX = 'session:';\nconst PLAN_TAG_KEY_SEGMENT = ':tag:';\nconst PLAN_PLAN_KEY_SEGMENT = ':key:';\nconst PLAN_LEGACY_KEY_PREFIX = 'legacy:';\nconst PLAN_SESSION_LEGACY_KEY_SEGMENT = ':legacy:';\nconst WINDOWS_SEPARATOR = '\\\\';\nconst POSIX_SEPARATOR = '/';\nconst ABSOLUTE_PATH_PREFIXES = ['..', `..${path.sep}`];\n\nfunction normalizePathSeparators(value: string): string {\n return value.replaceAll(WINDOWS_SEPARATOR, POSIX_SEPARATOR);\n}\n\nfunction isInsideRoot(relativePath: string): boolean {\n if (relativePath === '') return true;\n return !ABSOLUTE_PATH_PREFIXES.some((prefix) => relativePath === prefix || relativePath.startsWith(prefix))\n && !path.isAbsolute(relativePath);\n}\n\nexport function normalizePlanSourcePath(sourcePath: string, projectRoot?: string): string {\n if (sourcePath.startsWith(TRANSCRIPT_SOURCE_PREFIX)) return sourcePath;\n\n const normalizedProjectRoot = projectRoot ? path.resolve(projectRoot) : null;\n const resolvedSourcePath = normalizedProjectRoot\n ? path.resolve(normalizedProjectRoot, sourcePath)\n : path.resolve(sourcePath);\n\n if (normalizedProjectRoot) {\n const relativePath = path.relative(normalizedProjectRoot, resolvedSourcePath);\n if (isInsideRoot(relativePath)) {\n return normalizePathSeparators(path.normalize(relativePath));\n }\n }\n\n if (path.isAbsolute(sourcePath)) {\n return normalizePathSeparators(path.normalize(resolvedSourcePath));\n }\n\n return normalizePathSeparators(path.normalize(sourcePath));\n}\n\nexport function buildPathPlanLogicalKey(sourcePath: string, projectRoot?: string): string {\n return `${PLAN_PATH_KEY_PREFIX}${normalizePlanSourcePath(sourcePath, projectRoot)}`;\n}\n\nexport function buildSessionTagPlanLogicalKey(sessionId: string, tag: string): string {\n return `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_TAG_KEY_SEGMENT}${tag}`;\n}\n\nexport function buildSessionPlanLogicalKey(sessionId: string, planKey: string): string {\n return `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_PLAN_KEY_SEGMENT}${planKey}`;\n}\n\nexport function buildLegacyPlanLogicalKey(\n id: string,\n sessionId?: string | null,\n): string {\n return sessionId\n ? `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_SESSION_LEGACY_KEY_SEGMENT}${id}`\n : `${PLAN_LEGACY_KEY_PREFIX}${id}`;\n}\n\nexport function deriveStoredPlanLogicalKey(row: {\n id: string;\n source_path?: string | null;\n session_id?: string | null;\n}): string {\n const sourcePath = row.source_path ?? null;\n if (sourcePath) {\n if (sourcePath.startsWith(TRANSCRIPT_SOURCE_PREFIX) && row.session_id) {\n return buildSessionTagPlanLogicalKey(\n row.session_id,\n sourcePath.slice(TRANSCRIPT_SOURCE_PREFIX.length),\n );\n }\n return buildPathPlanLogicalKey(sourcePath);\n }\n return buildLegacyPlanLogicalKey(row.id, row.session_id);\n}\n\nexport function buildPlanId(logicalKey: string): string {\n return createHash('md5').update(logicalKey).digest('hex').slice(0, PLAN_ID_HASH_LENGTH);\n}\n\nexport function humanizePlanToken(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n","/**\n * Schema migrations for the Myco vault database.\n *\n * Each migration is a function that upgrades the database from version N-1 to N.\n * The MIGRATIONS registry provides a declarative list that createSchema() can\n * iterate over instead of hand-coding version checks.\n */\n\nimport type { Database } from 'better-sqlite3';\nimport { epochSeconds, DEFAULT_MACHINE_ID } from '@myco/constants.js';\nimport { CANDIDATE_STATUS } from '@myco/constants/skill-candidate-status.js';\nimport {\n LOG_ENTRIES_TABLE,\n TEAM_OUTBOX_TABLE,\n SKILL_CANDIDATES_TABLE,\n SKILL_RECORDS_TABLE,\n SKILL_LINEAGE_TABLE,\n SKILL_USAGE_TABLE,\n NOTIFICATIONS_TABLE,\n AGENT_RUN_WRITE_INTENTS_TABLE,\n DIGEST_EXTRACT_REVISIONS_TABLE,\n AGENT_RUN_EVALUATIONS_TABLE,\n CORTEX_INSTRUCTIONS_TABLE,\n} from './schema-ddl.js';\nimport {\n buildPlanId,\n deriveStoredPlanLogicalKey,\n} from '@myco/plans/identity.js';\n\n// ---------------------------------------------------------------------------\n// Migration interface + registry\n// ---------------------------------------------------------------------------\n\nexport interface Migration {\n version: number;\n migrate: (db: Database, machineId: string) => void;\n}\n\nexport const MIGRATIONS: Migration[] = [\n { version: 2, migrate: (db) => migrateV1ToV2(db) },\n { version: 3, migrate: (db) => migrateV2ToV3(db) },\n { version: 4, migrate: migrateV3ToV4 },\n { version: 5, migrate: (db) => migrateV4ToV5(db) },\n { version: 6, migrate: (db) => migrateV5ToV6(db) },\n { version: 7, migrate: migrateV6ToV7 },\n { version: 8, migrate: (db) => migrateV7ToV8(db) },\n { version: 9, migrate: (db) => migrateV8ToV9(db) },\n { version: 10, migrate: (db) => migrateV9ToV10(db) },\n { version: 11, migrate: (db) => migrateV10ToV11(db) },\n { version: 12, migrate: (db) => migrateV11ToV12(db) },\n { version: 13, migrate: (db) => migrateV12ToV13(db) },\n { version: 14, migrate: (db) => migrateV13ToV14(db) },\n { version: 15, migrate: (db) => migrateV14ToV15(db) },\n { version: 16, migrate: (db) => migrateV15ToV16(db) },\n { version: 17, migrate: (db) => migrateV16ToV17(db) },\n { version: 18, migrate: (db) => migrateV17ToV18(db) },\n { version: 19, migrate: (db) => migrateV18ToV19(db) },\n { version: 20, migrate: (db, machineId) => migrateV19ToV20(db, machineId) },\n { version: 21, migrate: (db) => migrateV20ToV21(db) },\n];\n\n// ---------------------------------------------------------------------------\n// Individual migration functions\n// ---------------------------------------------------------------------------\n\n/**\n * Return the set of column names on a table, via PRAGMA table_info.\n * Used to make ADD COLUMN migrations idempotent without wrapping each\n * statement in a try/catch inside a transaction (which poisons the txn).\n */\nfunction getTableColumnSet(db: Database, tableName: string): Set<string> {\n const rows = db.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\n/**\n * Migrate a version-1 database to version-2.\n *\n * Version 2 adds:\n * - plans.session_id, plans.prompt_batch_id, plans.content_hash\n * - attachments.data, attachments.content_hash\n * - indexes: idx_plans_session_id, idx_plans_source_path, idx_plans_content_hash\n *\n * Each ALTER TABLE is wrapped in try/catch so re-running is safe -- SQLite\n * throws \"duplicate column name\" if the column already exists, which we ignore.\n */\nfunction migrateV1ToV2(db: Database): void {\n db.exec('BEGIN');\n try {\n const alterStatements = [\n 'ALTER TABLE plans ADD COLUMN session_id TEXT REFERENCES sessions(id)',\n 'ALTER TABLE plans ADD COLUMN prompt_batch_id INTEGER REFERENCES prompt_batches(id)',\n 'ALTER TABLE plans ADD COLUMN content_hash TEXT',\n 'ALTER TABLE attachments ADD COLUMN data BLOB',\n 'ALTER TABLE attachments ADD COLUMN content_hash TEXT',\n ];\n\n for (const stmt of alterStatements) {\n try {\n db.exec(stmt);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n }\n\n // Indexes use IF NOT EXISTS so they are idempotent\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_plans_session_id ON plans (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_source_path ON plans (source_path)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_content_hash ON plans (content_hash)',\n 'CREATE INDEX IF NOT EXISTS idx_attachments_file_path ON attachments (file_path)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(2, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-2 database to version-3.\n *\n * Version 3 adds:\n * - log_entries table\n * - log_entries_fts virtual table (FTS5)\n * - indexes: idx_log_entries_timestamp, _level, _component, _kind, _session_id\n *\n * Uses `CREATE ... IF NOT EXISTS` throughout for idempotency.\n */\nfunction migrateV2ToV3(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(LOG_ENTRIES_TABLE);\n\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS log_entries_fts\n USING fts5(message, content='log_entries', content_rowid='id')`\n );\n\n // FTS5 sync triggers for log_entries\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS log_entries_ai AFTER INSERT ON log_entries BEGIN\n INSERT INTO log_entries_fts(rowid, message) VALUES (new.id, new.message);\n END`\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS log_entries_ad AFTER DELETE ON log_entries BEGIN\n INSERT INTO log_entries_fts(log_entries_fts, rowid, message) VALUES('delete', old.id, old.message);\n END`\n );\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_timestamp ON log_entries (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_level ON log_entries (level)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_component ON log_entries (component)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_kind ON log_entries (kind)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_session_id ON log_entries (session_id)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(3, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-3 database to version-4.\n *\n * Version 4 adds multi-machine support:\n * - machine_id TEXT NOT NULL DEFAULT 'local' on all synced tables\n * - synced_at INTEGER on all synced tables\n * - team_outbox table + indexes\n * - machine_id indexes on high-traffic tables\n *\n * Backfills existing rows with the provided machineId.\n */\nfunction migrateV3ToV4(db: Database, machineId: string): void {\n db.exec('BEGIN');\n try {\n // Tables that need machine_id + synced_at columns\n const syncedTables = [\n 'sessions',\n 'prompt_batches',\n 'spores',\n 'entities',\n 'graph_edges',\n 'entity_mentions',\n 'resolution_events',\n 'plans',\n 'artifacts',\n 'digest_extracts',\n 'team_members',\n ];\n\n for (const table of syncedTables) {\n try {\n db.exec(`ALTER TABLE ${table} ADD COLUMN machine_id TEXT NOT NULL DEFAULT 'local'`);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n try {\n db.exec(`ALTER TABLE ${table} ADD COLUMN synced_at INTEGER`);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n }\n\n // Backfill machine_id on existing rows\n for (const table of syncedTables) {\n db.prepare(`UPDATE ${table} SET machine_id = ? WHERE machine_id = 'local'`).run(machineId);\n }\n\n // Create team_outbox table\n db.exec(TEAM_OUTBOX_TABLE);\n\n // Create new indexes (IF NOT EXISTS for idempotency)\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_pending ON team_outbox (sent_at, created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_table_name ON team_outbox (table_name)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_row_lookup ON team_outbox (table_name, row_id)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_machine_id ON sessions (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_machine_id ON spores (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_machine_id ON graph_edges (machine_id)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(4, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nfunction migrateV17ToV18(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(CORTEX_INSTRUCTIONS_TABLE);\n db.exec(\n 'CREATE INDEX IF NOT EXISTS idx_cortex_instructions_agent_id ON cortex_instructions (agent_id)',\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(18, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 19 removes Cortex instructions from the team-sync surface.\n *\n * Cortex instructions are local operating guidance, not shared team\n * knowledge. No current call site enqueues `cortex_instructions` outbox\n * rows, so this DELETE is a safety net — the real invariant is enforced\n * in `enqueueOutbox` via LOCAL_ONLY_OUTBOX_TABLES. Any rows that slipped\n * in from an older build are cleared here so they don't linger as\n * futile retries; new inserts for this table name are rejected at the\n * enqueue layer.\n */\nfunction migrateV18ToV19(db: Database): void {\n db.exec('BEGIN');\n try {\n db.prepare(\n 'DELETE FROM team_outbox WHERE table_name = ?',\n ).run('cortex_instructions');\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(19, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-12 database to version-13.\n *\n * Version 13 adds first-class agent runtime/checkpoint fields so runs can be\n * resumed without overloading actions_taken JSON.\n */\nfunction migrateV12ToV13(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['runtime', 'TEXT'],\n ['provider', 'TEXT'],\n ['model', 'TEXT'],\n ['session_ref', 'TEXT'],\n ['resumable', 'INTEGER DEFAULT 0'],\n ['resume_status', 'TEXT'],\n ['resume_mode', 'TEXT'],\n ['resumed_at', 'INTEGER'],\n ['checkpoints', 'TEXT'],\n ['usage_data', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n const newIndexes = [\n `CREATE INDEX IF NOT EXISTS idx_agent_runs_task_status_started_at ON agent_runs (task, status, started_at)`,\n `CREATE INDEX IF NOT EXISTS idx_agent_runs_resumable_task ON agent_runs (task, resumable, completed_at)`,\n ];\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(13, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-13 database to version-14.\n *\n * Version 14 adds richer local-only cost accounting metadata for agent runs.\n * These columns intentionally stay on the local SQLite vault only and are not\n * part of team sync / outbox payloads.\n */\nfunction migrateV13ToV14(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['actual_cost_usd', 'REAL'],\n ['estimated_cost_usd', 'REAL'],\n ['cost_source', 'TEXT'],\n ['cost_data', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(14, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Test-only re-export of the v20 collision resolver. The migration itself is\n * internal; tests need this entry point to assert the collision guard.\n */\nexport function resolveV20PlanIdentityCollisionsForTest(db: Database): void {\n resolveV20PlanIdentityCollisions(db);\n}\n\n/**\n * Resolve any logical-key collisions in the v20 plan migration staging pass.\n *\n * Used after the first pass populates id_next / logical_key_next on every\n * plan row. Rows whose derived logical key was already taken by an earlier\n * row get moved onto a per-row legacy key so no two rows end up with the\n * same plan id after the swap.\n *\n * Throws a descriptive error if a collision would remain even after the\n * legacy-key fallback -- the caller is expected to fail the migration and\n * surface the conflicting keys to the operator.\n */\nfunction resolveV20PlanIdentityCollisions(db: Database): void {\n const dupes = db.prepare(\n `SELECT logical_key_next, COUNT(*) AS n\n FROM plans\n WHERE logical_key_next <> ''\n GROUP BY logical_key_next\n HAVING n > 1`,\n ).all() as Array<{ logical_key_next: string; n: number }>;\n\n if (dupes.length > 0) {\n const rowsForKey = db.prepare(\n `SELECT id, session_id\n FROM plans\n WHERE logical_key_next = ?\n ORDER BY created_at ASC, id ASC`,\n );\n const updateStaging = db.prepare(\n `UPDATE plans\n SET logical_key_next = ?, id_next = ?\n WHERE id = ?`,\n );\n\n for (const dupe of dupes) {\n const conflicting = rowsForKey.all(dupe.logical_key_next) as Array<{ id: string; session_id: string | null }>;\n for (let i = 1; i < conflicting.length; i += 1) {\n const row = conflicting[i];\n const legacyKey = row.session_id\n ? `session:${row.session_id}:legacy:${row.id}`\n : `legacy:${row.id}`;\n updateStaging.run(legacyKey, buildPlanId(legacyKey), row.id);\n }\n }\n }\n\n // Final guard: ensure the staging pass is collision-free. If a collision\n // still exists after the legacy fallback, surface a descriptive error\n // listing the offending keys rather than letting the swap hit a UNIQUE\n // constraint violation.\n const remaining = db.prepare(\n `SELECT id_next, GROUP_CONCAT(id, ',') AS ids\n FROM plans\n WHERE id_next IS NOT NULL\n GROUP BY id_next\n HAVING COUNT(*) > 1`,\n ).all() as Array<{ id_next: string; ids: string }>;\n\n if (remaining.length > 0) {\n const detail = remaining\n .map((r) => `${r.id_next} <= [${r.ids}]`)\n .join('; ');\n throw new Error(\n `v20 plan migration: plan id collisions after legacy fallback: ${detail}`,\n );\n }\n}\n\n/**\n * Version 20 adds plans.logical_key and backfills plan identity so capture\n * channels can converge on one last-write-wins row per logical plan.\n *\n * This is intentionally a forward migration on top of the shipped v19 schema\n * chain from main. Earlier versions keep their original meaning; logical-key\n * plan identity is introduced only once the vault reaches v20.\n *\n * Identity swap is performed in two passes:\n * 1. Populate `id_next` / `logical_key_next` staging columns with the\n * computed values for every row, and resolve any collisions before\n * touching the primary key.\n * 2. Swap `id` / `logical_key` from the staging columns in a single\n * UPDATE. This guarantees we never attempt an UPDATE that would fail\n * with a UNIQUE violation mid-loop and leave the vault stuck at v19.\n */\nfunction migrateV19ToV20(db: Database, machineId: string): void {\n db.exec('BEGIN');\n try {\n const planColumns = getTableColumnSet(db, 'plans');\n if (!planColumns.has('logical_key')) {\n db.exec(`ALTER TABLE plans ADD COLUMN logical_key TEXT NOT NULL DEFAULT ''`);\n }\n // Staging columns for the two-pass identity swap. Dropped before COMMIT.\n if (!planColumns.has('id_next')) {\n db.exec(`ALTER TABLE plans ADD COLUMN id_next TEXT`);\n }\n if (!planColumns.has('logical_key_next')) {\n db.exec(`ALTER TABLE plans ADD COLUMN logical_key_next TEXT NOT NULL DEFAULT ''`);\n }\n\n const rows = db.prepare(\n `SELECT *\n FROM plans\n ORDER BY created_at ASC, id ASC`,\n ).all() as Array<{\n id: string;\n logical_key?: string;\n status: string | null;\n author: string | null;\n title: string | null;\n content: string | null;\n source_path: string | null;\n tags: string | null;\n session_id: string | null;\n prompt_batch_id: number | null;\n content_hash: string | null;\n processed: number | null;\n created_at: number;\n updated_at: number | null;\n embedded: number | null;\n machine_id: string | null;\n synced_at: number | null;\n }>;\n\n // Pass 1: populate staging columns with derived identities.\n const writeStaging = db.prepare(\n `UPDATE plans\n SET id_next = ?, logical_key_next = ?\n WHERE id = ?`,\n );\n for (const row of rows) {\n const derivedLogicalKey = deriveStoredPlanLogicalKey(row);\n writeStaging.run(buildPlanId(derivedLogicalKey), derivedLogicalKey, row.id);\n }\n\n // Pass 1b: reassign any colliding rows onto per-row legacy keys and\n // verify the staging pass is collision-free before the swap.\n resolveV20PlanIdentityCollisions(db);\n\n // Build a map of each row's previous logical_key (pre-swap) so the\n // outbox re-enqueue step can detect whether identity actually changed.\n const previousLogicalKeyByOldId = new Map<string, string>();\n const previousIdNextByOldId = new Map<string, string>();\n const staged = db.prepare(\n `SELECT id, id_next, logical_key, logical_key_next FROM plans`,\n ).all() as Array<{ id: string; id_next: string | null; logical_key: string | null; logical_key_next: string }>;\n for (const row of staged) {\n previousLogicalKeyByOldId.set(row.id, row.logical_key ?? '');\n if (row.id_next) previousIdNextByOldId.set(row.id, row.id_next);\n }\n\n // Pass 2: swap identity columns in place. Because the staging columns\n // are collision-free, the final id update cannot violate UNIQUE.\n db.prepare(\n `UPDATE plans\n SET embedded = CASE WHEN id = id_next THEN embedded ELSE 0 END,\n synced_at = CASE WHEN id = id_next THEN synced_at ELSE NULL END\n WHERE id_next IS NOT NULL`,\n ).run();\n db.prepare(\n `UPDATE plans\n SET id = id_next,\n logical_key = logical_key_next\n WHERE id_next IS NOT NULL`,\n ).run();\n\n // Finalize: enqueue outbox operations for rows whose identity changed.\n // Skip enqueuing upserts for rows whose id AND logical_key match their\n // prior values -- those rows retain their existing outbox state.\n // (Finding #39)\n const deleteOutboxEntries = db.prepare(\n `DELETE FROM team_outbox\n WHERE table_name = 'plans' AND row_id IN (?, ?)`,\n );\n const enqueueOutbox = db.prepare(\n `INSERT INTO team_outbox (table_name, row_id, operation, payload, machine_id, created_at)\n VALUES ('plans', ?, ?, ?, ?, ?)`,\n );\n const now = epochSeconds();\n\n for (const row of rows) {\n const nextId = previousIdNextByOldId.get(row.id) ?? row.id;\n const fresh = db.prepare(`SELECT * FROM plans WHERE id = ?`).get(nextId) as Record<string, unknown> | undefined;\n\n const identityChanged = nextId !== row.id;\n const previousLogicalKey = previousLogicalKeyByOldId.get(row.id) ?? '';\n const currentLogicalKey = (fresh?.logical_key as string | undefined) ?? '';\n const logicalKeyChanged = previousLogicalKey !== currentLogicalKey;\n const needsResync = identityChanged || logicalKeyChanged;\n\n if (!needsResync) continue;\n\n deleteOutboxEntries.run(row.id, nextId);\n\n if (identityChanged) {\n enqueueOutbox.run(\n row.id,\n 'delete',\n JSON.stringify({ id: row.id }),\n row.machine_id ?? machineId,\n now,\n );\n }\n\n if (fresh) {\n const { id_next: _idNext, logical_key_next: _lkNext, ...publishable } = fresh;\n enqueueOutbox.run(\n nextId,\n 'upsert',\n JSON.stringify(publishable),\n (fresh.machine_id as string | null) ?? machineId,\n now,\n );\n }\n }\n\n db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_logical_key ON plans (logical_key)`);\n\n // Drop staging columns now that the swap is committed. SQLite supports\n // DROP COLUMN since 3.35; our minimum version is newer.\n db.exec(`ALTER TABLE plans DROP COLUMN id_next`);\n db.exec(`ALTER TABLE plans DROP COLUMN logical_key_next`);\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(20, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-4 database to version-5.\n *\n * Version 5 adds the Skills layer:\n * - skill_candidates table\n * - skill_records table\n * - skill_lineage table\n * - skill_usage table\n * - indexes for all new tables\n *\n * Uses `CREATE TABLE IF NOT EXISTS` throughout for idempotency.\n */\nfunction migrateV4ToV5(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(SKILL_CANDIDATES_TABLE);\n db.exec(SKILL_RECORDS_TABLE);\n db.exec(SKILL_LINEAGE_TABLE);\n db.exec(SKILL_USAGE_TABLE);\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_id ON skill_candidates (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_status ON skill_candidates (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_machine_id ON skill_candidates (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_status ON skill_candidates (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_id ON skill_records (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_status ON skill_records (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_name ON skill_records (name)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_machine_id ON skill_records (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_status ON skill_records (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_lineage_skill_id ON skill_lineage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_id ON skill_usage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_session_id ON skill_usage (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_session ON skill_usage (skill_id, session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_completed ON agent_runs (task, status, completed_at)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(5, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-5 database to version-6.\n *\n * Version 6 expands FTS5 coverage:\n * - prompt_batches_fts gains response_summary column (drop + recreate)\n * - spores_fts new virtual table (content column, hidden rowid)\n * - sessions_fts new virtual table (title + summary, hidden rowid)\n * - sync triggers for all three tables (insert / update / delete)\n * - backfills FTS from existing data\n *\n * Uses `IF NOT EXISTS` throughout for idempotency where possible.\n * The prompt_batches_fts table must be dropped first since its column\n * definition changed.\n */\nfunction migrateV5ToV6(db: Database): void {\n db.exec('BEGIN');\n try {\n // Drop old prompt_batches_fts (column definition changed)\n db.exec('DROP TABLE IF EXISTS prompt_batches_fts');\n\n // Recreate with response_summary added\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS prompt_batches_fts\n USING fts5(user_prompt, response_summary, content='prompt_batches', content_rowid='id')`,\n );\n\n // New FTS tables\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS spores_fts\n USING fts5(content, content='spores', content_rowid='rowid')`,\n );\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts\n USING fts5(title, summary, content='sessions', content_rowid='rowid')`,\n );\n\n // Triggers for prompt_batches\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ai AFTER INSERT ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_au AFTER UPDATE OF user_prompt, response_summary ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ad AFTER DELETE ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n END`,\n );\n\n // Triggers for spores\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ai AFTER INSERT ON spores BEGIN\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_au AFTER UPDATE OF content ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ad AFTER DELETE ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n END`,\n );\n\n // Triggers for sessions\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ai AFTER INSERT ON sessions BEGIN\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_au AFTER UPDATE OF title, summary ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ad AFTER DELETE ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n END`,\n );\n\n // Backfill FTS from existing data\n db.exec(\n `INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary)\n SELECT rowid, user_prompt, response_summary FROM prompt_batches`,\n );\n db.exec(\n `INSERT INTO spores_fts(rowid, content)\n SELECT rowid, content FROM spores`,\n );\n db.exec(\n `INSERT INTO sessions_fts(rowid, title, summary)\n SELECT rowid, title, summary FROM sessions`,\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(6, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v6 -> v7: fix stale 'local' machine_id on ALL synced tables.\n *\n * The agent vault tools historically used DEFAULT_MACHINE_ID ('local')\n * instead of the resolved machine identity. This one-time data migration\n * fixes all affected records and re-queues them for team sync.\n */\nfunction migrateV6ToV7(db: Database, machineId: string): void {\n if (machineId === 'local' || machineId === DEFAULT_MACHINE_ID) return; // Nothing to fix\n\n db.exec('BEGIN');\n try {\n // entity_mentions excluded -- no `id` column (composite key: entity_id, note_id, note_type)\n const tables = [\n 'sessions', 'prompt_batches', 'spores', 'entities', 'graph_edges',\n 'resolution_events', 'plans', 'artifacts',\n 'digest_extracts', 'skill_candidates', 'skill_records',\n ];\n\n for (const table of tables) {\n try {\n // Find rows that need fixing BEFORE updating\n const staleRows = db.prepare(\n `SELECT id FROM ${table} WHERE machine_id = 'local'`,\n ).all() as Array<{ id: string }>;\n\n if (staleRows.length === 0) continue;\n\n // Fix machine_id and clear synced_at\n db.prepare(\n `UPDATE ${table} SET machine_id = ?, synced_at = NULL WHERE machine_id = 'local'`,\n ).run(machineId);\n\n // Clear stale outbox entries for affected rows only\n for (const row of staleRows) {\n db.prepare(\n `DELETE FROM team_outbox WHERE table_name = ? AND row_id = ?`,\n ).run(table, String(row.id));\n }\n\n // Re-enqueue only the fixed rows with full payload\n const enqueueStmt = db.prepare(\n `INSERT INTO team_outbox (table_name, row_id, operation, payload, machine_id, created_at)\n VALUES (?, ?, 'upsert', ?, ?, ?)`,\n );\n const now = epochSeconds();\n for (const stale of staleRows) {\n const fresh = db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(stale.id) as Record<string, unknown>;\n if (fresh) {\n enqueueStmt.run(table, String(stale.id), JSON.stringify(fresh), machineId, now);\n }\n }\n } catch (tableErr) {\n // Skip if table doesn't exist; re-throw for other errors (I/O, constraint)\n const msg = tableErr instanceof Error ? tableErr.message : String(tableErr);\n if (!msg.includes('no such table')) throw tableErr;\n }\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(7, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v7 -> v8: add notifications table.\n *\n * Uses `CREATE TABLE IF NOT EXISTS` for idempotency.\n */\nfunction migrateV7ToV8(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(NOTIFICATIONS_TABLE);\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status ON notifications (status)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_domain ON notifications (domain)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications (created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status_created ON notifications (status, created_at)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(8, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 9 adds retry tracking to the team outbox:\n * - retry_count INTEGER NOT NULL DEFAULT 0\n * - last_attempt_at INTEGER\n *\n * Records exceeding the max retry count are dead-lettered (excluded from\n * pending queries) so they don't block the sync flush or deep sleep.\n */\nfunction migrateV8ToV9(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec('ALTER TABLE team_outbox ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0');\n db.exec('ALTER TABLE team_outbox ADD COLUMN last_attempt_at INTEGER');\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(9, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 10 adds an audit trail for skill candidate approvals.\n *\n * - skill_candidates.approved_at INTEGER (nullable) — timestamp of the\n * first transition into status='approved'. Auto-managed by\n * updateCandidate going forward.\n *\n * Backfill: rows currently in status 'approved' or 'generated' get\n * approved_at set to the migration timestamp. This is a one-time,\n * deliberately-imprecise assumption — the true approval time is lost\n * for existing rows, so we record \"as of the migration, these were\n * considered approved\" rather than inventing timestamps.\n *\n * Rows in 'identified' or 'dismissed' state keep approved_at = NULL.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column; the backfill uses `WHERE approved_at IS NULL` so it\n * never overwrites a previously-recorded timestamp.\n */\nfunction migrateV9ToV10(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_candidates ADD COLUMN approved_at INTEGER');\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n\n const now = epochSeconds();\n db.prepare(\n `UPDATE skill_candidates\n SET approved_at = ?\n WHERE approved_at IS NULL\n AND status IN (?, ?)`,\n ).run(now, CANDIDATE_STATUS.APPROVED, CANDIDATE_STATUS.GENERATED);\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(10, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 11 adds supersedes tracking to skill candidates.\n *\n * - skill_candidates.supersedes TEXT (nullable) — JSON array of skill\n * record names that this candidate would replace. Used by the skill\n * survey task to create domain-level candidates that explicitly\n * subsume existing narrow skills.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column.\n */\nfunction migrateV10ToV11(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_candidates ADD COLUMN supersedes TEXT');\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(11, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 12 adds embedding support for skill records.\n *\n * - skill_records.embedded INTEGER DEFAULT 0 — flag for the embedding\n * pipeline to know which rows still need vectors.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column.\n */\nfunction migrateV11ToV12(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_records ADD COLUMN embedded INTEGER DEFAULT 0');\n } catch {\n // Column already exists\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(12, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 15 adds the evaluation / dry-run harness storage:\n * - agent_run_write_intents — append-only log of dry-run attempted writes\n * - digest_extract_revisions — append-only history of digest_extracts rows\n * - agent_run_evaluations — matrix grouping record\n * - agent_runs.dry_run INTEGER NOT NULL DEFAULT 0\n * - agent_runs.evaluation_id TEXT (nullable, no FK)\n *\n * Each ALTER uses the standard idempotency guard. Table creation uses\n * `CREATE TABLE IF NOT EXISTS`.\n *\n * Note on write_intents: the table is append-only from the query layer\n * (no UPDATE/DELETE helper exposed). ON DELETE CASCADE on run_id is\n * intentional so parent-row purges still cleanly cascade.\n */\n/**\n * Version 16 persists the reasoning level and full execution override packet\n * used for each run, so downstream consumers (eval comparison, RunTaskDialog\n * override editor, phase-level override execution) can reconstruct exactly\n * what configuration produced a run independent of the current task definition.\n *\n * - agent_runs.reasoning_level TEXT (nullable) -- 'low' | 'default' | 'high'\n * - agent_runs.execution_overrides TEXT (nullable) -- JSON of RunOptions.executionOverrides\n *\n * Each ALTER uses the standard idempotency guard; NULL is the expected value\n * for runs that used the task default config throughout.\n */\nfunction migrateV15ToV16(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['reasoning_level', 'TEXT'],\n ['execution_overrides', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(16, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 17 adds a composite index on agent_run_write_intents to speed up\n * the per-evaluation batched tool-count query:\n *\n * SELECT wi.run_id, wi.tool_name, COUNT(*)\n * FROM agent_run_write_intents wi\n * JOIN agent_runs r ON r.id = wi.run_id\n * WHERE r.evaluation_id = ?\n * GROUP BY wi.run_id, wi.tool_name\n *\n * The existing `idx_write_intents_run_id` already serves the equality\n * filter, but the composite index lets SQLite resolve the GROUP BY\n * directly from the index without a separate sort pass.\n *\n * Fully idempotent: `IF NOT EXISTS` on the index, no DDL on the base\n * table.\n */\nfunction migrateV16ToV17(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id_tool ON agent_run_write_intents (run_id, tool_name)',\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(17, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nfunction migrateV14ToV15(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['dry_run', 'INTEGER NOT NULL DEFAULT 0'],\n ['evaluation_id', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n db.exec(AGENT_RUN_WRITE_INTENTS_TABLE);\n db.exec(DIGEST_EXTRACT_REVISIONS_TABLE);\n db.exec(AGENT_RUN_EVALUATIONS_TABLE);\n\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id ON agent_run_write_intents (run_id)',\n 'CREATE INDEX IF NOT EXISTS idx_digest_revisions_agent_tier ON digest_extract_revisions (agent_id, tier, created_at DESC)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_evaluation_id ON agent_runs (evaluation_id)',\n ];\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(15, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v20 → v21: retire the semantic knowledge graph.\n *\n * Prunes agent-created entities, entity mentions, and semantic-typed edges.\n * Lineage edges remain (daemon-created). Tables preserved for reversibility.\n */\nfunction migrateV20ToV21(db: Database): void {\n db.prepare('BEGIN').run();\n try {\n // Guard each DELETE — the v13 migration-chain test scaffold omits these tables.\n if (tableExists(db, 'graph_edges')) {\n db.prepare(\n `DELETE FROM graph_edges WHERE type IN ('REFERENCES', 'AFFECTS', 'DEPENDS_ON', 'RELATES_TO')`,\n ).run();\n }\n if (tableExists(db, 'entity_mentions')) {\n db.prepare(`DELETE FROM entity_mentions`).run();\n }\n if (tableExists(db, 'entities')) {\n db.prepare(`DELETE FROM entities`).run();\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(21, epochSeconds());\n\n db.prepare('COMMIT').run();\n } catch (err) {\n db.prepare('ROLLBACK').run();\n throw err;\n }\n}\n\nfunction tableExists(db: Database, name: string): boolean {\n const row = db.prepare(\n `SELECT count(*) AS c FROM sqlite_master WHERE type = 'table' AND name = ?`,\n ).get(name) as { c: number };\n return row.c > 0;\n}\n","/**\n * SQLite database schema -- all capture, intelligence, and agent state tables.\n *\n * Uses `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` throughout\n * for idempotency. Running `createSchema()` multiple times is always safe.\n *\n * Timestamp convention: all timestamps are INTEGER (Unix epoch seconds).\n * Content hashing: all `content_hash` columns are TEXT with UNIQUE constraint.\n * Embedding dimensions: 1024 (bge-m3 default) -- used by external sqlite-vec store.\n *\n * Vector columns live in a separate sqlite-vec virtual table, not inline.\n * Tables that participate in vector search carry an `embedded INTEGER DEFAULT 0`\n * flag so the embedder knows which rows still need vectors.\n */\n\nimport type { Database } from 'better-sqlite3';\nimport { epochSeconds, DEFAULT_MACHINE_ID } from '@myco/constants.js';\nimport { TABLE_DDLS, FTS_TABLES, SECONDARY_INDEXES } from './schema-ddl.js';\nimport { MIGRATIONS } from './migrations.js';\n\n/** Current schema version -- fresh start for the SQLite era. */\nexport const SCHEMA_VERSION = 21;\n\n// Re-export for backwards compat (other modules import from schema.ts)\nexport { DEFAULT_MACHINE_ID };\n\n/** Embedding vector dimensions (bge-m3 default). */\nexport const EMBEDDING_DIMENSIONS = 1024;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getCurrentVersion(db: Database): number {\n const row = db.prepare(\n 'SELECT version FROM schema_version ORDER BY version DESC LIMIT 1'\n ).get() as { version: number } | undefined;\n return row?.version ?? 0;\n}\n\n/**\n * Detect whether the `schema_version` table exists.\n *\n * Used to distinguish a truly fresh database (no schema at all) from one\n * that has a `schema_version` row but is mid-upgrade. We read\n * `sqlite_master` directly instead of catching exceptions from the version\n * query, so actual errors during migration propagate instead of silently\n * falling through to the fresh-install path.\n */\nfunction hasSchemaVersionTable(db: Database): boolean {\n const row = db.prepare(\n `SELECT 1 AS present FROM sqlite_master\n WHERE type = 'table' AND name = 'schema_version'\n LIMIT 1`,\n ).get() as { present: number } | undefined;\n return row?.present === 1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create all database tables, indexes, and record the schema version.\n *\n * Fully idempotent -- safe to call on every startup. Uses `IF NOT EXISTS`\n * for all DDL and `ON CONFLICT DO NOTHING` for the version row.\n *\n * Fresh-install detection reads `sqlite_master` directly; we do NOT use\n * throw-as-control-flow here. If a migration raises, the error propagates\n * so a partially-upgraded vault surfaces the failure instead of being\n * silently stamped at SCHEMA_VERSION.\n *\n * @param db -- better-sqlite3 Database instance.\n * @param machineId -- machine identifier for backfilling existing rows during\n * v3->v4 and v6->v7 migrations. Defaults to `'local'` (tests, init).\n */\nexport function createSchema(db: Database, machineId: string = DEFAULT_MACHINE_ID): void {\n if (hasSchemaVersionTable(db)) {\n const currentVersion = getCurrentVersion(db);\n if (currentVersion === SCHEMA_VERSION) return;\n\n // Run pending migrations in order. Errors propagate intentionally so\n // partial upgrade failures are visible to the caller.\n for (const migration of MIGRATIONS) {\n const version = getCurrentVersion(db);\n if (version < migration.version) {\n migration.migrate(db, machineId);\n }\n }\n return;\n }\n\n // Fresh install: create all tables, FTS, indexes\n for (const ddl of TABLE_DDLS) { db.exec(ddl); }\n for (const ddl of FTS_TABLES) { db.exec(ddl); }\n for (const idx of SECONDARY_INDEXES) { db.exec(idx); }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(SCHEMA_VERSION, epochSeconds());\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/db/schema-ddl.ts","../src/constants/skill-candidate-status.ts","../src/plans/identity.ts","../src/db/migrations.ts","../src/db/schema.ts"],"sourcesContent":["/**\n * DDL constants for all Myco vault tables, FTS5 virtual tables,\n * sync triggers, and secondary indexes.\n *\n * Extracted from schema.ts for readability -- these are pure string\n * constants with no runtime behaviour.\n */\n\n// ---------------------------------------------------------------------------\n// Table DDL statements\n// ---------------------------------------------------------------------------\n\nconst SCHEMA_VERSION_TABLE = `\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )`;\n\n// -- Capture Layer ----------------------------------------------------------\n\nconst SESSIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent TEXT NOT NULL,\n \"user\" TEXT,\n project_root TEXT,\n branch TEXT,\n started_at INTEGER NOT NULL,\n ended_at INTEGER,\n status TEXT DEFAULT 'active',\n prompt_count INTEGER DEFAULT 0,\n tool_count INTEGER DEFAULT 0,\n title TEXT,\n summary TEXT,\n transcript_path TEXT,\n parent_session_id TEXT,\n parent_session_reason TEXT,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst PROMPT_BATCHES_TABLE = `\n CREATE TABLE IF NOT EXISTS prompt_batches (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n prompt_number INTEGER,\n user_prompt TEXT,\n response_summary TEXT,\n classification TEXT,\n started_at INTEGER,\n ended_at INTEGER,\n status TEXT DEFAULT 'active',\n activity_count INTEGER DEFAULT 0,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ACTIVITIES_TABLE = `\n CREATE TABLE IF NOT EXISTS activities (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n tool_name TEXT NOT NULL,\n tool_input TEXT,\n tool_output_summary TEXT,\n file_path TEXT,\n files_affected TEXT,\n duration_ms INTEGER,\n success INTEGER DEFAULT 1,\n error_message TEXT,\n timestamp INTEGER NOT NULL,\n processed INTEGER DEFAULT 0,\n content_hash TEXT UNIQUE,\n created_at INTEGER NOT NULL\n )`;\n\nconst PLANS_TABLE = `\n CREATE TABLE IF NOT EXISTS plans (\n id TEXT PRIMARY KEY,\n logical_key TEXT NOT NULL,\n status TEXT DEFAULT 'active',\n author TEXT,\n title TEXT,\n content TEXT,\n source_path TEXT,\n tags TEXT,\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n content_hash TEXT,\n processed INTEGER DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ARTIFACTS_TABLE = `\n CREATE TABLE IF NOT EXISTS artifacts (\n id TEXT PRIMARY KEY,\n artifact_type TEXT,\n source_path TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT,\n last_captured_by TEXT,\n tags TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst TEAM_MEMBERS_TABLE = `\n CREATE TABLE IF NOT EXISTS team_members (\n id TEXT PRIMARY KEY,\n \"user\" TEXT NOT NULL,\n role TEXT,\n joined TEXT,\n tags TEXT,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ATTACHMENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS attachments (\n id TEXT PRIMARY KEY,\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n file_path TEXT NOT NULL,\n media_type TEXT,\n description TEXT,\n data BLOB,\n content_hash TEXT,\n created_at INTEGER NOT NULL\n )`;\n\n// -- Intelligence Layer -----------------------------------------------------\n\nconst AGENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n provider TEXT,\n model TEXT,\n system_prompt_hash TEXT,\n config TEXT,\n source TEXT NOT NULL DEFAULT 'built-in',\n system_prompt TEXT,\n max_turns INTEGER,\n timeout_seconds INTEGER,\n tool_access TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL,\n updated_at INTEGER\n )`;\n\nconst SPORES_TABLE = `\n CREATE TABLE IF NOT EXISTS spores (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n session_id TEXT REFERENCES sessions(id),\n prompt_batch_id INTEGER REFERENCES prompt_batches(id),\n observation_type TEXT NOT NULL,\n status TEXT DEFAULT 'active',\n content TEXT NOT NULL,\n context TEXT,\n importance INTEGER DEFAULT 5,\n file_path TEXT,\n tags TEXT,\n content_hash TEXT UNIQUE,\n properties TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER,\n embedded INTEGER DEFAULT 0,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ENTITIES_TABLE = `\n CREATE TABLE IF NOT EXISTS entities (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n properties TEXT,\n first_seen INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n status TEXT DEFAULT 'active',\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (agent_id, type, name)\n )`;\n\nconst GRAPH_EDGES_TABLE = `\n CREATE TABLE IF NOT EXISTS graph_edges (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source_id TEXT NOT NULL,\n source_type TEXT NOT NULL,\n target_id TEXT NOT NULL,\n target_type TEXT NOT NULL,\n type TEXT NOT NULL,\n session_id TEXT,\n confidence REAL DEFAULT 1.0,\n properties TEXT,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst ENTITY_MENTIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS entity_mentions (\n entity_id TEXT NOT NULL REFERENCES entities(id),\n note_id TEXT NOT NULL,\n note_type TEXT NOT NULL,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (entity_id, note_id, note_type, agent_id)\n )`;\n\nconst RESOLUTION_EVENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS resolution_events (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n spore_id TEXT NOT NULL REFERENCES spores(id),\n action TEXT NOT NULL,\n new_spore_id TEXT,\n reason TEXT,\n session_id TEXT,\n created_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\nconst DIGEST_EXTRACTS_TABLE = `\n CREATE TABLE IF NOT EXISTS digest_extracts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n tier INTEGER NOT NULL,\n content TEXT NOT NULL,\n substrate_hash TEXT,\n generated_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER,\n UNIQUE (agent_id, tier)\n )`;\n\nexport const CORTEX_INSTRUCTIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS cortex_instructions (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n content TEXT NOT NULL,\n input_hash TEXT NOT NULL,\n source_run_id TEXT,\n generated_at INTEGER NOT NULL,\n machine_id TEXT NOT NULL DEFAULT 'local',\n synced_at INTEGER\n )`;\n\n// -- Agent State Layer ------------------------------------------------------\n\nconst AGENT_RUNS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_runs (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n task TEXT,\n instruction TEXT,\n status TEXT DEFAULT 'pending',\n runtime TEXT,\n provider TEXT,\n model TEXT,\n session_ref TEXT,\n resumable INTEGER DEFAULT 0,\n resume_status TEXT,\n resume_mode TEXT,\n resumed_at INTEGER,\n checkpoints TEXT,\n usage_data TEXT,\n started_at INTEGER,\n completed_at INTEGER,\n tokens_used INTEGER,\n cost_usd REAL,\n actual_cost_usd REAL,\n estimated_cost_usd REAL,\n cost_source TEXT,\n cost_data TEXT,\n actions_taken TEXT,\n error TEXT,\n dry_run INTEGER NOT NULL DEFAULT 0,\n evaluation_id TEXT,\n reasoning_level TEXT,\n execution_overrides TEXT\n )`;\n\nconst AGENT_REPORTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_reports (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id),\n agent_id TEXT NOT NULL REFERENCES agents(id),\n action TEXT NOT NULL,\n summary TEXT NOT NULL,\n details TEXT,\n created_at INTEGER NOT NULL\n )`;\n\nconst AGENT_TURNS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_turns (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id),\n agent_id TEXT NOT NULL REFERENCES agents(id),\n turn_number INTEGER NOT NULL,\n tool_name TEXT NOT NULL,\n tool_input TEXT,\n tool_output_summary TEXT,\n started_at INTEGER,\n completed_at INTEGER\n )`;\n\nconst AGENT_TASKS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_tasks (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n source TEXT NOT NULL DEFAULT 'built-in',\n display_name TEXT,\n description TEXT,\n prompt TEXT NOT NULL,\n is_default INTEGER DEFAULT 0,\n tool_overrides TEXT,\n model TEXT,\n config TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER\n )`;\n\nconst AGENT_STATE_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_state (\n agent_id TEXT NOT NULL REFERENCES agents(id),\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (agent_id, key)\n )`;\n\n// -- Sync Layer -------------------------------------------------------------\n\nexport const TEAM_OUTBOX_TABLE = `\n CREATE TABLE IF NOT EXISTS team_outbox (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n table_name TEXT NOT NULL,\n row_id TEXT NOT NULL,\n operation TEXT NOT NULL DEFAULT 'upsert',\n payload TEXT NOT NULL,\n machine_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n sent_at INTEGER,\n retry_count INTEGER NOT NULL DEFAULT 0,\n last_attempt_at INTEGER\n )`;\n\n// -- Logging Layer ----------------------------------------------------------\n\nexport const LOG_ENTRIES_TABLE = `\n CREATE TABLE IF NOT EXISTS log_entries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp TEXT NOT NULL,\n level TEXT NOT NULL,\n component TEXT NOT NULL,\n kind TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT,\n session_id TEXT\n )`;\n\n// -- Skills Layer -----------------------------------------------------------\n\nexport const SKILL_CANDIDATES_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_candidates (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n topic TEXT NOT NULL,\n rationale TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.0,\n status TEXT NOT NULL DEFAULT 'identified',\n source_ids TEXT NOT NULL DEFAULT '[]',\n skill_id TEXT,\n supersedes TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n approved_at INTEGER,\n synced_at INTEGER\n )`;\n\nexport const SKILL_RECORDS_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_records (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n embedded INTEGER DEFAULT 0,\n generation INTEGER NOT NULL DEFAULT 1,\n candidate_id TEXT REFERENCES skill_candidates(id),\n source_ids TEXT NOT NULL DEFAULT '[]',\n path TEXT NOT NULL,\n usage_count INTEGER NOT NULL DEFAULT 0,\n last_used_at INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n properties TEXT NOT NULL DEFAULT '{}',\n synced_at INTEGER\n )`;\n\nexport const SKILL_LINEAGE_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_lineage (\n id TEXT PRIMARY KEY,\n skill_id TEXT NOT NULL REFERENCES skill_records(id),\n generation INTEGER NOT NULL,\n action TEXT NOT NULL,\n rationale TEXT NOT NULL,\n source_ids_added TEXT NOT NULL DEFAULT '[]',\n content_snapshot TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )`;\n\nexport const SKILL_USAGE_TABLE = `\n CREATE TABLE IF NOT EXISTS skill_usage (\n id TEXT PRIMARY KEY,\n skill_id TEXT NOT NULL REFERENCES skill_records(id),\n session_id TEXT NOT NULL REFERENCES sessions(id),\n machine_id TEXT NOT NULL DEFAULT 'local',\n detected_at INTEGER NOT NULL\n )`;\n\n// -- Notifications Layer ----------------------------------------------------\n\nexport const NOTIFICATIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS notifications (\n id TEXT PRIMARY KEY,\n domain TEXT NOT NULL,\n type TEXT NOT NULL,\n level TEXT NOT NULL DEFAULT 'info',\n title TEXT NOT NULL,\n message TEXT,\n mode TEXT NOT NULL DEFAULT 'banner',\n status TEXT NOT NULL DEFAULT 'unread',\n link TEXT,\n metadata TEXT,\n created_at INTEGER NOT NULL\n )`;\n\n// -- Eval Harness Layer -----------------------------------------------------\n\n/**\n * Append-only log of every write a dry-run attempted. Each row captures the\n * tool that was called, the JSON-encoded arguments, the synthetic payload we\n * returned to the agent, and any stub id we minted for a synthetic resource.\n *\n * Append-only invariant: enforced at the query layer — no UPDATE or DELETE\n * helper is exposed. The ON DELETE CASCADE on `run_id` is intentional so a\n * future purge of `agent_runs` (e.g. retention job) also removes the\n * corresponding intents in a single atomic step. Rows must never be deleted\n * except as a side effect of a parent `agent_runs` delete.\n */\nexport const AGENT_RUN_WRITE_INTENTS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_run_write_intents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,\n phase_id TEXT,\n tool_name TEXT NOT NULL,\n tool_input TEXT NOT NULL,\n synthetic_output TEXT NOT NULL,\n stub_id TEXT,\n recorded_at INTEGER NOT NULL\n )`;\n\n/**\n * Append-only history of digest_extracts rows. A new revision is inserted\n * every time a real (non-dry) run overwrites an existing digest. Rollback\n * restores an old revision and records a fresh revision to preserve the\n * append-only invariant.\n */\nexport const DIGEST_EXTRACT_REVISIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS digest_extract_revisions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n tier INTEGER NOT NULL,\n content TEXT NOT NULL,\n metadata TEXT,\n run_id TEXT REFERENCES agent_runs(id) ON DELETE SET NULL,\n parent_revision_id INTEGER REFERENCES digest_extract_revisions(id),\n created_at INTEGER NOT NULL\n )`;\n\n/**\n * Matrix grouping record for evaluation runs. Child runs link back via\n * `agent_runs.evaluation_id` — code-level integrity, no FK because child\n * runs are themselves normal agent_runs rows.\n */\nexport const AGENT_RUN_EVALUATIONS_TABLE = `\n CREATE TABLE IF NOT EXISTS agent_run_evaluations (\n id TEXT PRIMARY KEY,\n task_id TEXT NOT NULL,\n matrix_json TEXT NOT NULL,\n notes TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n completed_at INTEGER\n )`;\n\n// -- FTS5 Virtual Tables ----------------------------------------------------\n\nexport const FTS_TABLES = [\n `CREATE VIRTUAL TABLE IF NOT EXISTS prompt_batches_fts\n USING fts5(user_prompt, response_summary, content='prompt_batches', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS activities_fts\n USING fts5(tool_name, tool_input, file_path, content='activities', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS log_entries_fts\n USING fts5(message, content='log_entries', content_rowid='id')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS spores_fts\n USING fts5(content, content='spores', content_rowid='rowid')`,\n\n `CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts\n USING fts5(title, summary, content='sessions', content_rowid='rowid')`,\n\n // FTS5 sync triggers for log_entries (external-content table)\n `CREATE TRIGGER IF NOT EXISTS log_entries_ai AFTER INSERT ON log_entries BEGIN\n INSERT INTO log_entries_fts(rowid, message) VALUES (new.id, new.message);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS log_entries_ad AFTER DELETE ON log_entries BEGIN\n INSERT INTO log_entries_fts(log_entries_fts, rowid, message) VALUES('delete', old.id, old.message);\n END`,\n\n // FTS5 sync triggers for prompt_batches\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ai AFTER INSERT ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_au AFTER UPDATE OF user_prompt, response_summary ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ad AFTER DELETE ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n END`,\n\n // FTS5 sync triggers for spores\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ai AFTER INSERT ON spores BEGIN\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS spores_fts_au AFTER UPDATE OF content ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ad AFTER DELETE ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n END`,\n\n // FTS5 sync triggers for sessions\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ai AFTER INSERT ON sessions BEGIN\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_au AFTER UPDATE OF title, summary ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ad AFTER DELETE ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n END`,\n];\n\n// -- Indexes ----------------------------------------------------------------\n\nexport const SECONDARY_INDEXES = [\n // Sessions\n 'CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions (status)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_processed ON sessions (processed)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions (started_at)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions (agent)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_created_at ON sessions (created_at)',\n\n // Prompt batches\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_session_id ON prompt_batches (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_processed ON prompt_batches (processed)',\n 'CREATE INDEX IF NOT EXISTS idx_prompt_batches_status ON prompt_batches (status)',\n\n // Activities\n 'CREATE INDEX IF NOT EXISTS idx_activities_session_id ON activities (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_prompt_batch_id ON activities (prompt_batch_id)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_tool_name ON activities (tool_name)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_timestamp ON activities (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_activities_processed ON activities (processed)',\n\n // Spores\n 'CREATE INDEX IF NOT EXISTS idx_spores_agent_id ON spores (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_session_id ON spores (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_status ON spores (status)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_observation_type ON spores (observation_type)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_created_at ON spores (created_at)',\n\n // Entities\n 'CREATE INDEX IF NOT EXISTS idx_entities_agent_id ON entities (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_entities_type ON entities (type)',\n\n // Graph edges\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id, source_type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id, target_type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_type ON graph_edges (type)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_agent ON graph_edges (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_source_type ON graph_edges (source_id, type)',\n\n // Entity mentions\n 'CREATE INDEX IF NOT EXISTS idx_entity_mentions_entity_id ON entity_mentions (entity_id)',\n 'CREATE INDEX IF NOT EXISTS idx_entity_mentions_agent_id ON entity_mentions (agent_id)',\n\n // Resolution events\n 'CREATE INDEX IF NOT EXISTS idx_resolution_events_spore_id ON resolution_events (spore_id)',\n 'CREATE INDEX IF NOT EXISTS idx_resolution_events_agent_id ON resolution_events (agent_id)',\n\n // Digest extracts\n 'CREATE INDEX IF NOT EXISTS idx_digest_extracts_agent_id ON digest_extracts (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_cortex_instructions_agent_id ON cortex_instructions (agent_id)',\n\n // Agent runs\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent_id ON agent_runs (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs (status)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent_status ON agent_runs (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_completed ON agent_runs (task, status, completed_at)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_status_started_at ON agent_runs (task, status, started_at)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_resumable_task ON agent_runs (task, resumable, completed_at)',\n\n // Agent reports\n 'CREATE INDEX IF NOT EXISTS idx_agent_reports_run_id ON agent_reports (run_id)',\n\n // Agent turns\n 'CREATE INDEX IF NOT EXISTS idx_agent_turns_run_id ON agent_turns (run_id)',\n\n // Agent tasks\n 'CREATE INDEX IF NOT EXISTS idx_agent_tasks_agent_id ON agent_tasks (agent_id)',\n\n // Plans\n 'CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_logical_key ON plans (logical_key)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_session_id ON plans (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_source_path ON plans (source_path)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_content_hash ON plans (content_hash)',\n // Attachments\n 'CREATE INDEX IF NOT EXISTS idx_attachments_file_path ON attachments (file_path)',\n\n // Team outbox\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_pending ON team_outbox (sent_at, created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_table_name ON team_outbox (table_name)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_row_lookup ON team_outbox (table_name, row_id)',\n\n // Machine ID (synced tables)\n 'CREATE INDEX IF NOT EXISTS idx_sessions_machine_id ON sessions (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_machine_id ON spores (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_machine_id ON graph_edges (machine_id)',\n\n // Skill candidates\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_id ON skill_candidates (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_status ON skill_candidates (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_machine_id ON skill_candidates (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_status ON skill_candidates (agent_id, status)',\n\n // Skill records\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_id ON skill_records (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_status ON skill_records (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_name ON skill_records (name)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_machine_id ON skill_records (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_status ON skill_records (agent_id, status)',\n\n // Skill lineage\n 'CREATE INDEX IF NOT EXISTS idx_skill_lineage_skill_id ON skill_lineage (skill_id)',\n\n // Skill usage\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_id ON skill_usage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_session_id ON skill_usage (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_session ON skill_usage (skill_id, session_id)',\n\n // Log entries\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_timestamp ON log_entries (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_level ON log_entries (level)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_component ON log_entries (component)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_kind ON log_entries (kind)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_session_id ON log_entries (session_id)',\n\n // Notifications\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status ON notifications (status)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_domain ON notifications (domain)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications (created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status_created ON notifications (status, created_at)',\n\n // Eval harness\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id ON agent_run_write_intents (run_id)',\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id_tool ON agent_run_write_intents (run_id, tool_name)',\n 'CREATE INDEX IF NOT EXISTS idx_digest_revisions_agent_tier ON digest_extract_revisions (agent_id, tier, created_at DESC)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_evaluation_id ON agent_runs (evaluation_id)',\n];\n\n// -- Ordered table creation -------------------------------------------------\n\nexport const TABLE_DDLS = [\n SCHEMA_VERSION_TABLE,\n // Capture layer (order matters for FK references)\n SESSIONS_TABLE,\n PROMPT_BATCHES_TABLE,\n ACTIVITIES_TABLE,\n PLANS_TABLE,\n ARTIFACTS_TABLE,\n TEAM_MEMBERS_TABLE,\n ATTACHMENTS_TABLE,\n // Intelligence layer\n AGENTS_TABLE,\n SPORES_TABLE,\n ENTITIES_TABLE,\n GRAPH_EDGES_TABLE,\n ENTITY_MENTIONS_TABLE,\n RESOLUTION_EVENTS_TABLE,\n DIGEST_EXTRACTS_TABLE,\n CORTEX_INSTRUCTIONS_TABLE,\n // Agent state layer\n AGENT_RUNS_TABLE,\n AGENT_REPORTS_TABLE,\n AGENT_TURNS_TABLE,\n AGENT_TASKS_TABLE,\n AGENT_STATE_TABLE,\n // Skills layer\n SKILL_CANDIDATES_TABLE,\n SKILL_RECORDS_TABLE,\n SKILL_LINEAGE_TABLE,\n SKILL_USAGE_TABLE,\n // Sync layer\n TEAM_OUTBOX_TABLE,\n // Logging layer\n LOG_ENTRIES_TABLE,\n // Notifications layer\n NOTIFICATIONS_TABLE,\n // Eval harness layer\n AGENT_RUN_WRITE_INTENTS_TABLE,\n DIGEST_EXTRACT_REVISIONS_TABLE,\n AGENT_RUN_EVALUATIONS_TABLE,\n];\n","/**\n * Canonical string values for skill_candidates.status.\n *\n * The skill lifecycle uses four states:\n * - identified: discovered by skill-survey, awaiting human review\n * - approved: human approved; queued for skill-generate\n * - generated: vault_finalize_skill promoted the staged skill to live\n * - dismissed: retired (human or agent)\n *\n * See docs/superpowers/plans/2026-04-08-skill-lifecycle-audit-and-staging.md\n * for the lifecycle transitions and who is allowed to make each one.\n */\nexport const CANDIDATE_STATUS = {\n IDENTIFIED: 'identified',\n APPROVED: 'approved',\n GENERATED: 'generated',\n DISMISSED: 'dismissed',\n} as const;\n\nexport type SkillCandidateStatus = (typeof CANDIDATE_STATUS)[keyof typeof CANDIDATE_STATUS];\n\n/**\n * Statuses the agent-facing vault_skill_candidates tool is allowed to set\n * on an update. Human-only transitions (approved) and internal-only\n * transitions (generated, via vault_finalize_skill) are excluded.\n */\nexport const AGENT_SETTABLE_STATUSES: readonly SkillCandidateStatus[] = [\n CANDIDATE_STATUS.IDENTIFIED,\n CANDIDATE_STATUS.DISMISSED,\n];\n\n/**\n * Statuses REST callers (UI + MCP myco_skill_candidates) are allowed to\n * set. 'generated' is internal — only vault_finalize_skill sets it, and\n * that path calls updateCandidate directly rather than going through REST.\n */\nexport const REST_SETTABLE_STATUSES: readonly SkillCandidateStatus[] = [\n CANDIDATE_STATUS.IDENTIFIED,\n CANDIDATE_STATUS.APPROVED,\n CANDIDATE_STATUS.DISMISSED,\n];\n\n/**\n * Composite UI filter value that the REST handler translates into a\n * multi-status query (`status IN ('approved', 'generated')`). Kept here\n * so the UI and backend share a single source of truth for the wire\n * encoding.\n */\nexport const PIPELINE_FILTER_VALUE = `${CANDIDATE_STATUS.APPROVED},${CANDIDATE_STATUS.GENERATED}`;\n","import { createHash } from 'node:crypto';\nimport path from 'node:path';\n\nexport const TRANSCRIPT_SOURCE_PREFIX = 'transcript:';\n\nconst PLAN_ID_HASH_LENGTH = 16;\nconst PLAN_PATH_KEY_PREFIX = 'path:';\nconst PLAN_SESSION_KEY_PREFIX = 'session:';\nconst PLAN_TAG_KEY_SEGMENT = ':tag:';\nconst PLAN_PLAN_KEY_SEGMENT = ':key:';\nconst PLAN_LEGACY_KEY_PREFIX = 'legacy:';\nconst PLAN_SESSION_LEGACY_KEY_SEGMENT = ':legacy:';\nconst WINDOWS_SEPARATOR = '\\\\';\nconst POSIX_SEPARATOR = '/';\nconst ABSOLUTE_PATH_PREFIXES = ['..', `..${path.sep}`];\n\nfunction normalizePathSeparators(value: string): string {\n return value.replaceAll(WINDOWS_SEPARATOR, POSIX_SEPARATOR);\n}\n\nfunction isInsideRoot(relativePath: string): boolean {\n if (relativePath === '') return true;\n return !ABSOLUTE_PATH_PREFIXES.some((prefix) => relativePath === prefix || relativePath.startsWith(prefix))\n && !path.isAbsolute(relativePath);\n}\n\nexport function normalizePlanSourcePath(sourcePath: string, projectRoot?: string): string {\n if (sourcePath.startsWith(TRANSCRIPT_SOURCE_PREFIX)) return sourcePath;\n\n const normalizedProjectRoot = projectRoot ? path.resolve(projectRoot) : null;\n const resolvedSourcePath = normalizedProjectRoot\n ? path.resolve(normalizedProjectRoot, sourcePath)\n : path.resolve(sourcePath);\n\n if (normalizedProjectRoot) {\n const relativePath = path.relative(normalizedProjectRoot, resolvedSourcePath);\n if (isInsideRoot(relativePath)) {\n return normalizePathSeparators(path.normalize(relativePath));\n }\n }\n\n if (path.isAbsolute(sourcePath)) {\n return normalizePathSeparators(path.normalize(resolvedSourcePath));\n }\n\n return normalizePathSeparators(path.normalize(sourcePath));\n}\n\nexport function buildPathPlanLogicalKey(sourcePath: string, projectRoot?: string): string {\n return `${PLAN_PATH_KEY_PREFIX}${normalizePlanSourcePath(sourcePath, projectRoot)}`;\n}\n\nexport function buildSessionTagPlanLogicalKey(sessionId: string, tag: string): string {\n return `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_TAG_KEY_SEGMENT}${tag}`;\n}\n\nexport function buildSessionPlanLogicalKey(sessionId: string, planKey: string): string {\n return `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_PLAN_KEY_SEGMENT}${planKey}`;\n}\n\nexport function buildLegacyPlanLogicalKey(\n id: string,\n sessionId?: string | null,\n): string {\n return sessionId\n ? `${PLAN_SESSION_KEY_PREFIX}${sessionId}${PLAN_SESSION_LEGACY_KEY_SEGMENT}${id}`\n : `${PLAN_LEGACY_KEY_PREFIX}${id}`;\n}\n\nexport function deriveStoredPlanLogicalKey(row: {\n id: string;\n source_path?: string | null;\n session_id?: string | null;\n}): string {\n const sourcePath = row.source_path ?? null;\n if (sourcePath) {\n if (sourcePath.startsWith(TRANSCRIPT_SOURCE_PREFIX) && row.session_id) {\n return buildSessionTagPlanLogicalKey(\n row.session_id,\n sourcePath.slice(TRANSCRIPT_SOURCE_PREFIX.length),\n );\n }\n return buildPathPlanLogicalKey(sourcePath);\n }\n return buildLegacyPlanLogicalKey(row.id, row.session_id);\n}\n\nexport function buildPlanId(logicalKey: string): string {\n return createHash('md5').update(logicalKey).digest('hex').slice(0, PLAN_ID_HASH_LENGTH);\n}\n\nexport function humanizePlanToken(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n","/**\n * Schema migrations for the Myco vault database.\n *\n * Each migration is a function that upgrades the database from version N-1 to N.\n * The MIGRATIONS registry provides a declarative list that createSchema() can\n * iterate over instead of hand-coding version checks.\n */\n\nimport type { Database } from 'better-sqlite3';\nimport { epochSeconds, DEFAULT_MACHINE_ID } from '@myco/constants.js';\nimport { CANDIDATE_STATUS } from '@myco/constants/skill-candidate-status.js';\nimport {\n LOG_ENTRIES_TABLE,\n TEAM_OUTBOX_TABLE,\n SKILL_CANDIDATES_TABLE,\n SKILL_RECORDS_TABLE,\n SKILL_LINEAGE_TABLE,\n SKILL_USAGE_TABLE,\n NOTIFICATIONS_TABLE,\n AGENT_RUN_WRITE_INTENTS_TABLE,\n DIGEST_EXTRACT_REVISIONS_TABLE,\n AGENT_RUN_EVALUATIONS_TABLE,\n CORTEX_INSTRUCTIONS_TABLE,\n} from './schema-ddl.js';\nimport {\n buildPlanId,\n deriveStoredPlanLogicalKey,\n} from '@myco/plans/identity.js';\n\n// ---------------------------------------------------------------------------\n// Migration interface + registry\n// ---------------------------------------------------------------------------\n\nexport interface Migration {\n version: number;\n migrate: (db: Database, machineId: string) => void;\n}\n\nexport const MIGRATIONS: Migration[] = [\n { version: 2, migrate: (db) => migrateV1ToV2(db) },\n { version: 3, migrate: (db) => migrateV2ToV3(db) },\n { version: 4, migrate: migrateV3ToV4 },\n { version: 5, migrate: (db) => migrateV4ToV5(db) },\n { version: 6, migrate: (db) => migrateV5ToV6(db) },\n { version: 7, migrate: migrateV6ToV7 },\n { version: 8, migrate: (db) => migrateV7ToV8(db) },\n { version: 9, migrate: (db) => migrateV8ToV9(db) },\n { version: 10, migrate: (db) => migrateV9ToV10(db) },\n { version: 11, migrate: (db) => migrateV10ToV11(db) },\n { version: 12, migrate: (db) => migrateV11ToV12(db) },\n { version: 13, migrate: (db) => migrateV12ToV13(db) },\n { version: 14, migrate: (db) => migrateV13ToV14(db) },\n { version: 15, migrate: (db) => migrateV14ToV15(db) },\n { version: 16, migrate: (db) => migrateV15ToV16(db) },\n { version: 17, migrate: (db) => migrateV16ToV17(db) },\n { version: 18, migrate: (db) => migrateV17ToV18(db) },\n { version: 19, migrate: (db) => migrateV18ToV19(db) },\n { version: 20, migrate: (db, machineId) => migrateV19ToV20(db, machineId) },\n { version: 21, migrate: (db) => migrateV20ToV21(db) },\n];\n\n// ---------------------------------------------------------------------------\n// Individual migration functions\n// ---------------------------------------------------------------------------\n\n/**\n * Return the set of column names on a table, via PRAGMA table_info.\n * Used to make ADD COLUMN migrations idempotent without wrapping each\n * statement in a try/catch inside a transaction (which poisons the txn).\n */\nfunction getTableColumnSet(db: Database, tableName: string): Set<string> {\n const rows = db.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\n/**\n * Migrate a version-1 database to version-2.\n *\n * Version 2 adds:\n * - plans.session_id, plans.prompt_batch_id, plans.content_hash\n * - attachments.data, attachments.content_hash\n * - indexes: idx_plans_session_id, idx_plans_source_path, idx_plans_content_hash\n *\n * Each ALTER TABLE is wrapped in try/catch so re-running is safe -- SQLite\n * throws \"duplicate column name\" if the column already exists, which we ignore.\n */\nfunction migrateV1ToV2(db: Database): void {\n db.exec('BEGIN');\n try {\n const alterStatements = [\n 'ALTER TABLE plans ADD COLUMN session_id TEXT REFERENCES sessions(id)',\n 'ALTER TABLE plans ADD COLUMN prompt_batch_id INTEGER REFERENCES prompt_batches(id)',\n 'ALTER TABLE plans ADD COLUMN content_hash TEXT',\n 'ALTER TABLE attachments ADD COLUMN data BLOB',\n 'ALTER TABLE attachments ADD COLUMN content_hash TEXT',\n ];\n\n for (const stmt of alterStatements) {\n try {\n db.exec(stmt);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n }\n\n // Indexes use IF NOT EXISTS so they are idempotent\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_plans_session_id ON plans (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_source_path ON plans (source_path)',\n 'CREATE INDEX IF NOT EXISTS idx_plans_content_hash ON plans (content_hash)',\n 'CREATE INDEX IF NOT EXISTS idx_attachments_file_path ON attachments (file_path)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(2, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-2 database to version-3.\n *\n * Version 3 adds:\n * - log_entries table\n * - log_entries_fts virtual table (FTS5)\n * - indexes: idx_log_entries_timestamp, _level, _component, _kind, _session_id\n *\n * Uses `CREATE ... IF NOT EXISTS` throughout for idempotency.\n */\nfunction migrateV2ToV3(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(LOG_ENTRIES_TABLE);\n\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS log_entries_fts\n USING fts5(message, content='log_entries', content_rowid='id')`\n );\n\n // FTS5 sync triggers for log_entries\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS log_entries_ai AFTER INSERT ON log_entries BEGIN\n INSERT INTO log_entries_fts(rowid, message) VALUES (new.id, new.message);\n END`\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS log_entries_ad AFTER DELETE ON log_entries BEGIN\n INSERT INTO log_entries_fts(log_entries_fts, rowid, message) VALUES('delete', old.id, old.message);\n END`\n );\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_timestamp ON log_entries (timestamp)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_level ON log_entries (level)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_component ON log_entries (component)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_kind ON log_entries (kind)',\n 'CREATE INDEX IF NOT EXISTS idx_log_entries_session_id ON log_entries (session_id)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(3, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-3 database to version-4.\n *\n * Version 4 adds multi-machine support:\n * - machine_id TEXT NOT NULL DEFAULT 'local' on all synced tables\n * - synced_at INTEGER on all synced tables\n * - team_outbox table + indexes\n * - machine_id indexes on high-traffic tables\n *\n * Backfills existing rows with the provided machineId.\n */\nfunction migrateV3ToV4(db: Database, machineId: string): void {\n db.exec('BEGIN');\n try {\n // Tables that need machine_id + synced_at columns\n const syncedTables = [\n 'sessions',\n 'prompt_batches',\n 'spores',\n 'entities',\n 'graph_edges',\n 'entity_mentions',\n 'resolution_events',\n 'plans',\n 'artifacts',\n 'digest_extracts',\n 'team_members',\n ];\n\n for (const table of syncedTables) {\n try {\n db.exec(`ALTER TABLE ${table} ADD COLUMN machine_id TEXT NOT NULL DEFAULT 'local'`);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n try {\n db.exec(`ALTER TABLE ${table} ADD COLUMN synced_at INTEGER`);\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n }\n\n // Backfill machine_id on existing rows\n for (const table of syncedTables) {\n db.prepare(`UPDATE ${table} SET machine_id = ? WHERE machine_id = 'local'`).run(machineId);\n }\n\n // Create team_outbox table\n db.exec(TEAM_OUTBOX_TABLE);\n\n // Create new indexes (IF NOT EXISTS for idempotency)\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_pending ON team_outbox (sent_at, created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_table_name ON team_outbox (table_name)',\n 'CREATE INDEX IF NOT EXISTS idx_team_outbox_row_lookup ON team_outbox (table_name, row_id)',\n 'CREATE INDEX IF NOT EXISTS idx_sessions_machine_id ON sessions (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_spores_machine_id ON spores (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_graph_edges_machine_id ON graph_edges (machine_id)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(4, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nfunction migrateV17ToV18(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(CORTEX_INSTRUCTIONS_TABLE);\n db.exec(\n 'CREATE INDEX IF NOT EXISTS idx_cortex_instructions_agent_id ON cortex_instructions (agent_id)',\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(18, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 19 removes Cortex instructions from the team-sync surface.\n *\n * Cortex instructions are local operating guidance, not shared team\n * knowledge. No current call site enqueues `cortex_instructions` outbox\n * rows, so this DELETE is a safety net — the real invariant is enforced\n * in `enqueueOutbox` via LOCAL_ONLY_OUTBOX_TABLES. Any rows that slipped\n * in from an older build are cleared here so they don't linger as\n * futile retries; new inserts for this table name are rejected at the\n * enqueue layer.\n */\nfunction migrateV18ToV19(db: Database): void {\n db.exec('BEGIN');\n try {\n db.prepare(\n 'DELETE FROM team_outbox WHERE table_name = ?',\n ).run('cortex_instructions');\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(19, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-12 database to version-13.\n *\n * Version 13 adds first-class agent runtime/checkpoint fields so runs can be\n * resumed without overloading actions_taken JSON.\n */\nfunction migrateV12ToV13(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['runtime', 'TEXT'],\n ['provider', 'TEXT'],\n ['model', 'TEXT'],\n ['session_ref', 'TEXT'],\n ['resumable', 'INTEGER DEFAULT 0'],\n ['resume_status', 'TEXT'],\n ['resume_mode', 'TEXT'],\n ['resumed_at', 'INTEGER'],\n ['checkpoints', 'TEXT'],\n ['usage_data', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n const newIndexes = [\n `CREATE INDEX IF NOT EXISTS idx_agent_runs_task_status_started_at ON agent_runs (task, status, started_at)`,\n `CREATE INDEX IF NOT EXISTS idx_agent_runs_resumable_task ON agent_runs (task, resumable, completed_at)`,\n ];\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(13, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-13 database to version-14.\n *\n * Version 14 adds richer local-only cost accounting metadata for agent runs.\n * These columns intentionally stay on the local SQLite vault only and are not\n * part of team sync / outbox payloads.\n */\nfunction migrateV13ToV14(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['actual_cost_usd', 'REAL'],\n ['estimated_cost_usd', 'REAL'],\n ['cost_source', 'TEXT'],\n ['cost_data', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(14, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Test-only re-export of the v20 collision resolver. The migration itself is\n * internal; tests need this entry point to assert the collision guard.\n */\nexport function resolveV20PlanIdentityCollisionsForTest(db: Database): void {\n resolveV20PlanIdentityCollisions(db);\n}\n\n/**\n * Resolve any logical-key collisions in the v20 plan migration staging pass.\n *\n * Used after the first pass populates id_next / logical_key_next on every\n * plan row. Rows whose derived logical key was already taken by an earlier\n * row get moved onto a per-row legacy key so no two rows end up with the\n * same plan id after the swap.\n *\n * Throws a descriptive error if a collision would remain even after the\n * legacy-key fallback -- the caller is expected to fail the migration and\n * surface the conflicting keys to the operator.\n */\nfunction resolveV20PlanIdentityCollisions(db: Database): void {\n const dupes = db.prepare(\n `SELECT logical_key_next, COUNT(*) AS n\n FROM plans\n WHERE logical_key_next <> ''\n GROUP BY logical_key_next\n HAVING n > 1`,\n ).all() as Array<{ logical_key_next: string; n: number }>;\n\n if (dupes.length > 0) {\n const rowsForKey = db.prepare(\n `SELECT id, session_id\n FROM plans\n WHERE logical_key_next = ?\n ORDER BY created_at ASC, id ASC`,\n );\n const updateStaging = db.prepare(\n `UPDATE plans\n SET logical_key_next = ?, id_next = ?\n WHERE id = ?`,\n );\n\n for (const dupe of dupes) {\n const conflicting = rowsForKey.all(dupe.logical_key_next) as Array<{ id: string; session_id: string | null }>;\n for (let i = 1; i < conflicting.length; i += 1) {\n const row = conflicting[i];\n const legacyKey = row.session_id\n ? `session:${row.session_id}:legacy:${row.id}`\n : `legacy:${row.id}`;\n updateStaging.run(legacyKey, buildPlanId(legacyKey), row.id);\n }\n }\n }\n\n // Final guard: ensure the staging pass is collision-free. If a collision\n // still exists after the legacy fallback, surface a descriptive error\n // listing the offending keys rather than letting the swap hit a UNIQUE\n // constraint violation.\n const remaining = db.prepare(\n `SELECT id_next, GROUP_CONCAT(id, ',') AS ids\n FROM plans\n WHERE id_next IS NOT NULL\n GROUP BY id_next\n HAVING COUNT(*) > 1`,\n ).all() as Array<{ id_next: string; ids: string }>;\n\n if (remaining.length > 0) {\n const detail = remaining\n .map((r) => `${r.id_next} <= [${r.ids}]`)\n .join('; ');\n throw new Error(\n `v20 plan migration: plan id collisions after legacy fallback: ${detail}`,\n );\n }\n}\n\n/**\n * Version 20 adds plans.logical_key and backfills plan identity so capture\n * channels can converge on one last-write-wins row per logical plan.\n *\n * This is intentionally a forward migration on top of the shipped v19 schema\n * chain from main. Earlier versions keep their original meaning; logical-key\n * plan identity is introduced only once the vault reaches v20.\n *\n * Identity swap is performed in two passes:\n * 1. Populate `id_next` / `logical_key_next` staging columns with the\n * computed values for every row, and resolve any collisions before\n * touching the primary key.\n * 2. Swap `id` / `logical_key` from the staging columns in a single\n * UPDATE. This guarantees we never attempt an UPDATE that would fail\n * with a UNIQUE violation mid-loop and leave the vault stuck at v19.\n */\nfunction migrateV19ToV20(db: Database, machineId: string): void {\n db.exec('BEGIN');\n try {\n const planColumns = getTableColumnSet(db, 'plans');\n if (!planColumns.has('logical_key')) {\n db.exec(`ALTER TABLE plans ADD COLUMN logical_key TEXT NOT NULL DEFAULT ''`);\n }\n // Staging columns for the two-pass identity swap. Dropped before COMMIT.\n if (!planColumns.has('id_next')) {\n db.exec(`ALTER TABLE plans ADD COLUMN id_next TEXT`);\n }\n if (!planColumns.has('logical_key_next')) {\n db.exec(`ALTER TABLE plans ADD COLUMN logical_key_next TEXT NOT NULL DEFAULT ''`);\n }\n\n const rows = db.prepare(\n `SELECT *\n FROM plans\n ORDER BY created_at ASC, id ASC`,\n ).all() as Array<{\n id: string;\n logical_key?: string;\n status: string | null;\n author: string | null;\n title: string | null;\n content: string | null;\n source_path: string | null;\n tags: string | null;\n session_id: string | null;\n prompt_batch_id: number | null;\n content_hash: string | null;\n processed: number | null;\n created_at: number;\n updated_at: number | null;\n embedded: number | null;\n machine_id: string | null;\n synced_at: number | null;\n }>;\n\n // Pass 1: populate staging columns with derived identities.\n const writeStaging = db.prepare(\n `UPDATE plans\n SET id_next = ?, logical_key_next = ?\n WHERE id = ?`,\n );\n for (const row of rows) {\n const derivedLogicalKey = deriveStoredPlanLogicalKey(row);\n writeStaging.run(buildPlanId(derivedLogicalKey), derivedLogicalKey, row.id);\n }\n\n // Pass 1b: reassign any colliding rows onto per-row legacy keys and\n // verify the staging pass is collision-free before the swap.\n resolveV20PlanIdentityCollisions(db);\n\n // Build a map of each row's previous logical_key (pre-swap) so the\n // outbox re-enqueue step can detect whether identity actually changed.\n const previousLogicalKeyByOldId = new Map<string, string>();\n const previousIdNextByOldId = new Map<string, string>();\n const staged = db.prepare(\n `SELECT id, id_next, logical_key, logical_key_next FROM plans`,\n ).all() as Array<{ id: string; id_next: string | null; logical_key: string | null; logical_key_next: string }>;\n for (const row of staged) {\n previousLogicalKeyByOldId.set(row.id, row.logical_key ?? '');\n if (row.id_next) previousIdNextByOldId.set(row.id, row.id_next);\n }\n\n // Pass 2: swap identity columns in place. Because the staging columns\n // are collision-free, the final id update cannot violate UNIQUE.\n db.prepare(\n `UPDATE plans\n SET embedded = CASE WHEN id = id_next THEN embedded ELSE 0 END,\n synced_at = CASE WHEN id = id_next THEN synced_at ELSE NULL END\n WHERE id_next IS NOT NULL`,\n ).run();\n db.prepare(\n `UPDATE plans\n SET id = id_next,\n logical_key = logical_key_next\n WHERE id_next IS NOT NULL`,\n ).run();\n\n // Finalize: enqueue outbox operations for rows whose identity changed.\n // Skip enqueuing upserts for rows whose id AND logical_key match their\n // prior values -- those rows retain their existing outbox state.\n // (Finding #39)\n const deleteOutboxEntries = db.prepare(\n `DELETE FROM team_outbox\n WHERE table_name = 'plans' AND row_id IN (?, ?)`,\n );\n const enqueueOutbox = db.prepare(\n `INSERT INTO team_outbox (table_name, row_id, operation, payload, machine_id, created_at)\n VALUES ('plans', ?, ?, ?, ?, ?)`,\n );\n const now = epochSeconds();\n\n for (const row of rows) {\n const nextId = previousIdNextByOldId.get(row.id) ?? row.id;\n const fresh = db.prepare(`SELECT * FROM plans WHERE id = ?`).get(nextId) as Record<string, unknown> | undefined;\n\n const identityChanged = nextId !== row.id;\n const previousLogicalKey = previousLogicalKeyByOldId.get(row.id) ?? '';\n const currentLogicalKey = (fresh?.logical_key as string | undefined) ?? '';\n const logicalKeyChanged = previousLogicalKey !== currentLogicalKey;\n const needsResync = identityChanged || logicalKeyChanged;\n\n if (!needsResync) continue;\n\n deleteOutboxEntries.run(row.id, nextId);\n\n if (identityChanged) {\n enqueueOutbox.run(\n row.id,\n 'delete',\n JSON.stringify({ id: row.id }),\n row.machine_id ?? machineId,\n now,\n );\n }\n\n if (fresh) {\n const { id_next: _idNext, logical_key_next: _lkNext, ...publishable } = fresh;\n enqueueOutbox.run(\n nextId,\n 'upsert',\n JSON.stringify(publishable),\n (fresh.machine_id as string | null) ?? machineId,\n now,\n );\n }\n }\n\n db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_logical_key ON plans (logical_key)`);\n\n // Drop staging columns now that the swap is committed. SQLite supports\n // DROP COLUMN since 3.35; our minimum version is newer.\n db.exec(`ALTER TABLE plans DROP COLUMN id_next`);\n db.exec(`ALTER TABLE plans DROP COLUMN logical_key_next`);\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(20, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-4 database to version-5.\n *\n * Version 5 adds the Skills layer:\n * - skill_candidates table\n * - skill_records table\n * - skill_lineage table\n * - skill_usage table\n * - indexes for all new tables\n *\n * Uses `CREATE TABLE IF NOT EXISTS` throughout for idempotency.\n */\nfunction migrateV4ToV5(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(SKILL_CANDIDATES_TABLE);\n db.exec(SKILL_RECORDS_TABLE);\n db.exec(SKILL_LINEAGE_TABLE);\n db.exec(SKILL_USAGE_TABLE);\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_id ON skill_candidates (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_status ON skill_candidates (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_machine_id ON skill_candidates (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_candidates_agent_status ON skill_candidates (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_id ON skill_records (agent_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_status ON skill_records (status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_name ON skill_records (name)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_machine_id ON skill_records (machine_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_records_agent_status ON skill_records (agent_id, status)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_lineage_skill_id ON skill_lineage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_id ON skill_usage (skill_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_session_id ON skill_usage (session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_skill_usage_skill_session ON skill_usage (skill_id, session_id)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_task_completed ON agent_runs (task, status, completed_at)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(5, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate a version-5 database to version-6.\n *\n * Version 6 expands FTS5 coverage:\n * - prompt_batches_fts gains response_summary column (drop + recreate)\n * - spores_fts new virtual table (content column, hidden rowid)\n * - sessions_fts new virtual table (title + summary, hidden rowid)\n * - sync triggers for all three tables (insert / update / delete)\n * - backfills FTS from existing data\n *\n * Uses `IF NOT EXISTS` throughout for idempotency where possible.\n * The prompt_batches_fts table must be dropped first since its column\n * definition changed.\n */\nfunction migrateV5ToV6(db: Database): void {\n db.exec('BEGIN');\n try {\n // Drop old prompt_batches_fts (column definition changed)\n db.exec('DROP TABLE IF EXISTS prompt_batches_fts');\n\n // Recreate with response_summary added\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS prompt_batches_fts\n USING fts5(user_prompt, response_summary, content='prompt_batches', content_rowid='id')`,\n );\n\n // New FTS tables\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS spores_fts\n USING fts5(content, content='spores', content_rowid='rowid')`,\n );\n db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts\n USING fts5(title, summary, content='sessions', content_rowid='rowid')`,\n );\n\n // Triggers for prompt_batches\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ai AFTER INSERT ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_au AFTER UPDATE OF user_prompt, response_summary ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary) VALUES (new.id, new.user_prompt, new.response_summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS prompt_batches_fts_ad AFTER DELETE ON prompt_batches BEGIN\n INSERT INTO prompt_batches_fts(prompt_batches_fts, rowid, user_prompt, response_summary) VALUES('delete', old.id, old.user_prompt, old.response_summary);\n END`,\n );\n\n // Triggers for spores\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ai AFTER INSERT ON spores BEGIN\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_au AFTER UPDATE OF content ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n INSERT INTO spores_fts(rowid, content) VALUES (new.rowid, new.content);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS spores_fts_ad AFTER DELETE ON spores BEGIN\n INSERT INTO spores_fts(spores_fts, rowid, content) VALUES('delete', old.rowid, old.content);\n END`,\n );\n\n // Triggers for sessions\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ai AFTER INSERT ON sessions BEGIN\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_au AFTER UPDATE OF title, summary ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n INSERT INTO sessions_fts(rowid, title, summary) VALUES (new.rowid, new.title, new.summary);\n END`,\n );\n db.exec(\n `CREATE TRIGGER IF NOT EXISTS sessions_fts_ad AFTER DELETE ON sessions BEGIN\n INSERT INTO sessions_fts(sessions_fts, rowid, title, summary) VALUES('delete', old.rowid, old.title, old.summary);\n END`,\n );\n\n // Backfill FTS from existing data\n db.exec(\n `INSERT INTO prompt_batches_fts(rowid, user_prompt, response_summary)\n SELECT rowid, user_prompt, response_summary FROM prompt_batches`,\n );\n db.exec(\n `INSERT INTO spores_fts(rowid, content)\n SELECT rowid, content FROM spores`,\n );\n db.exec(\n `INSERT INTO sessions_fts(rowid, title, summary)\n SELECT rowid, title, summary FROM sessions`,\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(6, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v6 -> v7: fix stale 'local' machine_id on ALL synced tables.\n *\n * The agent vault tools historically used DEFAULT_MACHINE_ID ('local')\n * instead of the resolved machine identity. This one-time data migration\n * fixes all affected records and re-queues them for team sync.\n */\nfunction migrateV6ToV7(db: Database, machineId: string): void {\n if (machineId === 'local' || machineId === DEFAULT_MACHINE_ID) return; // Nothing to fix\n\n db.exec('BEGIN');\n try {\n // entity_mentions excluded -- no `id` column (composite key: entity_id, note_id, note_type)\n const tables = [\n 'sessions', 'prompt_batches', 'spores', 'entities', 'graph_edges',\n 'resolution_events', 'plans', 'artifacts',\n 'digest_extracts', 'skill_candidates', 'skill_records',\n ];\n\n for (const table of tables) {\n try {\n // Find rows that need fixing BEFORE updating\n const staleRows = db.prepare(\n `SELECT id FROM ${table} WHERE machine_id = 'local'`,\n ).all() as Array<{ id: string }>;\n\n if (staleRows.length === 0) continue;\n\n // Fix machine_id and clear synced_at\n db.prepare(\n `UPDATE ${table} SET machine_id = ?, synced_at = NULL WHERE machine_id = 'local'`,\n ).run(machineId);\n\n // Clear stale outbox entries for affected rows only\n for (const row of staleRows) {\n db.prepare(\n `DELETE FROM team_outbox WHERE table_name = ? AND row_id = ?`,\n ).run(table, String(row.id));\n }\n\n // Re-enqueue only the fixed rows with full payload\n const enqueueStmt = db.prepare(\n `INSERT INTO team_outbox (table_name, row_id, operation, payload, machine_id, created_at)\n VALUES (?, ?, 'upsert', ?, ?, ?)`,\n );\n const now = epochSeconds();\n for (const stale of staleRows) {\n const fresh = db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(stale.id) as Record<string, unknown>;\n if (fresh) {\n enqueueStmt.run(table, String(stale.id), JSON.stringify(fresh), machineId, now);\n }\n }\n } catch (tableErr) {\n // Skip if table doesn't exist; re-throw for other errors (I/O, constraint)\n const msg = tableErr instanceof Error ? tableErr.message : String(tableErr);\n if (!msg.includes('no such table')) throw tableErr;\n }\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(7, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v7 -> v8: add notifications table.\n *\n * Uses `CREATE TABLE IF NOT EXISTS` for idempotency.\n */\nfunction migrateV7ToV8(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(NOTIFICATIONS_TABLE);\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status ON notifications (status)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_domain ON notifications (domain)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications (created_at)',\n 'CREATE INDEX IF NOT EXISTS idx_notifications_status_created ON notifications (status, created_at)',\n ];\n\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(8, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 9 adds retry tracking to the team outbox:\n * - retry_count INTEGER NOT NULL DEFAULT 0\n * - last_attempt_at INTEGER\n *\n * Records exceeding the max retry count are dead-lettered (excluded from\n * pending queries) so they don't block the sync flush or deep sleep.\n */\nfunction migrateV8ToV9(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec('ALTER TABLE team_outbox ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0');\n db.exec('ALTER TABLE team_outbox ADD COLUMN last_attempt_at INTEGER');\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(9, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 10 adds an audit trail for skill candidate approvals.\n *\n * - skill_candidates.approved_at INTEGER (nullable) — timestamp of the\n * first transition into status='approved'. Auto-managed by\n * updateCandidate going forward.\n *\n * Backfill: rows currently in status 'approved' or 'generated' get\n * approved_at set to the migration timestamp. This is a one-time,\n * deliberately-imprecise assumption — the true approval time is lost\n * for existing rows, so we record \"as of the migration, these were\n * considered approved\" rather than inventing timestamps.\n *\n * Rows in 'identified' or 'dismissed' state keep approved_at = NULL.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column; the backfill uses `WHERE approved_at IS NULL` so it\n * never overwrites a previously-recorded timestamp.\n */\nfunction migrateV9ToV10(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_candidates ADD COLUMN approved_at INTEGER');\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n\n const now = epochSeconds();\n db.prepare(\n `UPDATE skill_candidates\n SET approved_at = ?\n WHERE approved_at IS NULL\n AND status IN (?, ?)`,\n ).run(now, CANDIDATE_STATUS.APPROVED, CANDIDATE_STATUS.GENERATED);\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(10, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 11 adds supersedes tracking to skill candidates.\n *\n * - skill_candidates.supersedes TEXT (nullable) — JSON array of skill\n * record names that this candidate would replace. Used by the skill\n * survey task to create domain-level candidates that explicitly\n * subsume existing narrow skills.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column.\n */\nfunction migrateV10ToV11(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_candidates ADD COLUMN supersedes TEXT');\n } catch {\n // Column already exists -- safe to ignore on re-run\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(11, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 12 adds embedding support for skill records.\n *\n * - skill_records.embedded INTEGER DEFAULT 0 — flag for the embedding\n * pipeline to know which rows still need vectors.\n *\n * Idempotent: the ALTER is wrapped in try/catch so re-runs tolerate the\n * existing column.\n */\nfunction migrateV11ToV12(db: Database): void {\n db.exec('BEGIN');\n try {\n try {\n db.exec('ALTER TABLE skill_records ADD COLUMN embedded INTEGER DEFAULT 0');\n } catch {\n // Column already exists\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(12, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 15 adds the evaluation / dry-run harness storage:\n * - agent_run_write_intents — append-only log of dry-run attempted writes\n * - digest_extract_revisions — append-only history of digest_extracts rows\n * - agent_run_evaluations — matrix grouping record\n * - agent_runs.dry_run INTEGER NOT NULL DEFAULT 0\n * - agent_runs.evaluation_id TEXT (nullable, no FK)\n *\n * Each ALTER uses the standard idempotency guard. Table creation uses\n * `CREATE TABLE IF NOT EXISTS`.\n *\n * Note on write_intents: the table is append-only from the query layer\n * (no UPDATE/DELETE helper exposed). ON DELETE CASCADE on run_id is\n * intentional so parent-row purges still cleanly cascade.\n */\n/**\n * Version 16 persists the reasoning level and full execution override packet\n * used for each run, so downstream consumers (eval comparison, RunTaskDialog\n * override editor, phase-level override execution) can reconstruct exactly\n * what configuration produced a run independent of the current task definition.\n *\n * - agent_runs.reasoning_level TEXT (nullable) -- 'low' | 'default' | 'high'\n * - agent_runs.execution_overrides TEXT (nullable) -- JSON of RunOptions.executionOverrides\n *\n * Each ALTER uses the standard idempotency guard; NULL is the expected value\n * for runs that used the task default config throughout.\n */\nfunction migrateV15ToV16(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['reasoning_level', 'TEXT'],\n ['execution_overrides', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(16, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Version 17 adds a composite index on agent_run_write_intents to speed up\n * the per-evaluation batched tool-count query:\n *\n * SELECT wi.run_id, wi.tool_name, COUNT(*)\n * FROM agent_run_write_intents wi\n * JOIN agent_runs r ON r.id = wi.run_id\n * WHERE r.evaluation_id = ?\n * GROUP BY wi.run_id, wi.tool_name\n *\n * The existing `idx_write_intents_run_id` already serves the equality\n * filter, but the composite index lets SQLite resolve the GROUP BY\n * directly from the index without a separate sort pass.\n *\n * Fully idempotent: `IF NOT EXISTS` on the index, no DDL on the base\n * table.\n */\nfunction migrateV16ToV17(db: Database): void {\n db.exec('BEGIN');\n try {\n db.exec(\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id_tool ON agent_run_write_intents (run_id, tool_name)',\n );\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(17, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nfunction migrateV14ToV15(db: Database): void {\n const existing = getTableColumnSet(db, 'agent_runs');\n const columnAdds: Array<[string, string]> = [\n ['dry_run', 'INTEGER NOT NULL DEFAULT 0'],\n ['evaluation_id', 'TEXT'],\n ];\n const pendingAdds = columnAdds.filter(([name]) => !existing.has(name));\n\n db.exec('BEGIN');\n try {\n db.exec(AGENT_RUN_WRITE_INTENTS_TABLE);\n db.exec(DIGEST_EXTRACT_REVISIONS_TABLE);\n db.exec(AGENT_RUN_EVALUATIONS_TABLE);\n\n for (const [name, decl] of pendingAdds) {\n db.exec(`ALTER TABLE agent_runs ADD COLUMN ${name} ${decl}`);\n }\n\n const newIndexes = [\n 'CREATE INDEX IF NOT EXISTS idx_write_intents_run_id ON agent_run_write_intents (run_id)',\n 'CREATE INDEX IF NOT EXISTS idx_digest_revisions_agent_tier ON digest_extract_revisions (agent_id, tier, created_at DESC)',\n 'CREATE INDEX IF NOT EXISTS idx_agent_runs_evaluation_id ON agent_runs (evaluation_id)',\n ];\n for (const idx of newIndexes) {\n db.exec(idx);\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(15, epochSeconds());\n\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\n/**\n * Migrate v20 → v21: retire the semantic knowledge graph.\n *\n * Prunes agent-created entities, entity mentions, and semantic-typed edges.\n * Lineage edges remain (daemon-created). Tables preserved for reversibility.\n */\nfunction migrateV20ToV21(db: Database): void {\n db.prepare('BEGIN').run();\n try {\n // Guard each DELETE — the v13 migration-chain test scaffold omits these tables.\n if (tableExists(db, 'graph_edges')) {\n db.prepare(\n `DELETE FROM graph_edges WHERE type IN ('REFERENCES', 'AFFECTS', 'DEPENDS_ON', 'RELATES_TO')`,\n ).run();\n }\n if (tableExists(db, 'entity_mentions')) {\n db.prepare(`DELETE FROM entity_mentions`).run();\n }\n if (tableExists(db, 'entities')) {\n db.prepare(`DELETE FROM entities`).run();\n }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`,\n ).run(21, epochSeconds());\n\n db.prepare('COMMIT').run();\n } catch (err) {\n db.prepare('ROLLBACK').run();\n throw err;\n }\n}\n\nfunction tableExists(db: Database, name: string): boolean {\n const row = db.prepare(\n `SELECT count(*) AS c FROM sqlite_master WHERE type = 'table' AND name = ?`,\n ).get(name) as { c: number };\n return row.c > 0;\n}\n","/**\n * SQLite database schema -- all capture, intelligence, and agent state tables.\n *\n * Uses `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` throughout\n * for idempotency. Running `createSchema()` multiple times is always safe.\n *\n * Timestamp convention: all timestamps are INTEGER (Unix epoch seconds).\n * Content hashing: all `content_hash` columns are TEXT with UNIQUE constraint.\n * Embedding dimensions: 1024 (bge-m3 default) -- used by external sqlite-vec store.\n *\n * Vector columns live in a separate sqlite-vec virtual table, not inline.\n * Tables that participate in vector search carry an `embedded INTEGER DEFAULT 0`\n * flag so the embedder knows which rows still need vectors.\n */\n\nimport type { Database } from 'better-sqlite3';\nimport { epochSeconds, DEFAULT_MACHINE_ID } from '@myco/constants.js';\nimport { TABLE_DDLS, FTS_TABLES, SECONDARY_INDEXES } from './schema-ddl.js';\nimport { MIGRATIONS } from './migrations.js';\n\n/** Current schema version -- fresh start for the SQLite era. */\nexport const SCHEMA_VERSION = 21;\n\n// Re-export for backwards compat (other modules import from schema.ts)\nexport { DEFAULT_MACHINE_ID };\n\n/** Embedding vector dimensions (bge-m3 default). */\nexport const EMBEDDING_DIMENSIONS = 1024;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getCurrentVersion(db: Database): number {\n const row = db.prepare(\n 'SELECT version FROM schema_version ORDER BY version DESC LIMIT 1'\n ).get() as { version: number } | undefined;\n return row?.version ?? 0;\n}\n\n/**\n * Detect whether the `schema_version` table exists.\n *\n * Used to distinguish a truly fresh database (no schema at all) from one\n * that has a `schema_version` row but is mid-upgrade. We read\n * `sqlite_master` directly instead of catching exceptions from the version\n * query, so actual errors during migration propagate instead of silently\n * falling through to the fresh-install path.\n */\nfunction hasSchemaVersionTable(db: Database): boolean {\n const row = db.prepare(\n `SELECT 1 AS present FROM sqlite_master\n WHERE type = 'table' AND name = 'schema_version'\n LIMIT 1`,\n ).get() as { present: number } | undefined;\n return row?.present === 1;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create all database tables, indexes, and record the schema version.\n *\n * Fully idempotent -- safe to call on every startup. Uses `IF NOT EXISTS`\n * for all DDL and `ON CONFLICT DO NOTHING` for the version row.\n *\n * Fresh-install detection reads `sqlite_master` directly; we do NOT use\n * throw-as-control-flow here. If a migration raises, the error propagates\n * so a partially-upgraded vault surfaces the failure instead of being\n * silently stamped at SCHEMA_VERSION.\n *\n * @param db -- better-sqlite3 Database instance.\n * @param machineId -- machine identifier for backfilling existing rows during\n * v3->v4 and v6->v7 migrations. Defaults to `'local'` (tests, init).\n */\nexport function createSchema(db: Database, machineId: string = DEFAULT_MACHINE_ID): void {\n if (hasSchemaVersionTable(db)) {\n const currentVersion = getCurrentVersion(db);\n if (currentVersion === SCHEMA_VERSION) {\n reapplyCurrentSchemaDdl(db);\n return;\n }\n\n // Run pending migrations in order. Errors propagate intentionally so\n // partial upgrade failures are visible to the caller.\n for (const migration of MIGRATIONS) {\n const version = getCurrentVersion(db);\n if (version < migration.version) {\n migration.migrate(db, machineId);\n }\n }\n reapplyCurrentSchemaDdl(db);\n return;\n }\n\n // Fresh install: create all tables, FTS, indexes\n for (const ddl of TABLE_DDLS) { db.exec(ddl); }\n for (const ddl of FTS_TABLES) { db.exec(ddl); }\n for (const idx of SECONDARY_INDEXES) { db.exec(idx); }\n\n db.prepare(\n `INSERT INTO schema_version (version, applied_at)\n VALUES (?, ?)\n ON CONFLICT (version) DO NOTHING`\n ).run(SCHEMA_VERSION, epochSeconds());\n}\n\n// ---------------------------------------------------------------------------\n// Schema-drift reconciliation\n//\n// createSchema installs everything on fresh install and each migration\n// adds its own delta, but existing DBs at the current schema version\n// skip all that — so if any schema object goes missing after the fact\n// (restore from a partial dump, manual DROP, an aborted beta build), the\n// version gate lets the drift ride until somebody notices the symptom.\n// Observed 2026-04: a vault at v21 had the log_entries FTS sync triggers\n// silently dropped, so 141k log entries piled up with an empty FTS index\n// and search returned zero hits.\n//\n// Rather than proliferate one reconcile-X helper per schema-object class\n// (triggers, indexes, tables, FTS virtual tables), this reapplies ALL\n// current-schema DDL on every createSchema() call. Every CREATE uses\n// IF NOT EXISTS, so replay is idempotent and costs microseconds. The\n// only non-DDL step is rebuilding an FTS index when its sync trigger\n// was just reinstalled — the DDL reinstalls the trigger but doesn't\n// repopulate writes it missed while absent.\n//\n// Known gap: column drift (someone dropping a column) is not covered —\n// SQLite's ALTER TABLE ADD COLUMN isn't IF NOT EXISTS-aware, and today's\n// migrations don't re-run past ALTERs either. We haven't seen this in\n// the wild; defer until we do.\n// ---------------------------------------------------------------------------\n\ninterface FtsTriggerGroup {\n ftsTable: string;\n baseTable: string;\n triggers: readonly string[];\n}\n\nconst FTS_TRIGGER_GROUPS: readonly FtsTriggerGroup[] = [\n { ftsTable: 'log_entries_fts', baseTable: 'log_entries', triggers: ['log_entries_ai', 'log_entries_ad'] },\n { ftsTable: 'prompt_batches_fts', baseTable: 'prompt_batches', triggers: ['prompt_batches_fts_ai', 'prompt_batches_fts_au', 'prompt_batches_fts_ad'] },\n { ftsTable: 'activities_fts', baseTable: 'activities', triggers: ['activities_fts_ai', 'activities_fts_au', 'activities_fts_ad'] },\n { ftsTable: 'spores_fts', baseTable: 'spores', triggers: ['spores_fts_ai', 'spores_fts_au', 'spores_fts_ad'] },\n { ftsTable: 'sessions_fts', baseTable: 'sessions', triggers: ['sessions_fts_ai', 'sessions_fts_au', 'sessions_fts_ad'] },\n];\n\nfunction reapplyCurrentSchemaDdl(db: Database): void {\n const triggersBefore = new Set(\n (db.prepare(\"SELECT name FROM sqlite_master WHERE type = 'trigger'\").all() as Array<{ name: string }>)\n .map((row) => row.name),\n );\n\n for (const ddl of TABLE_DDLS) { db.exec(ddl); }\n for (const ddl of FTS_TABLES) { db.exec(ddl); }\n for (const idx of SECONDARY_INDEXES) { db.exec(idx); }\n\n let rebuiltAny = false;\n for (const group of FTS_TRIGGER_GROUPS) {\n const wasMissing = group.triggers.some((name) => !triggersBefore.has(name));\n if (!wasMissing) continue;\n const baseCount = (db.prepare(`SELECT COUNT(*) AS n FROM ${group.baseTable}`).get() as { n: number }).n;\n if (baseCount === 0) continue;\n db.prepare(`INSERT INTO ${group.ftsTable}(${group.ftsTable}) VALUES('rebuild')`).run();\n rebuiltAny = true;\n }\n\n if (rebuiltAny) {\n process.stderr.write('[schema] Rebuilt one or more FTS indexes after detecting missing sync triggers\\n');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,9 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
2
2
|
import {
|
|
3
3
|
getAtPath
|
|
4
4
|
} from "./chunk-ZXZPJJN3.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_SYMBIONT_NAME
|
|
7
|
+
} from "./chunk-6C6QZ4PM.js";
|
|
5
8
|
|
|
6
9
|
// src/hooks/capture-rules.ts
|
|
7
10
|
function evaluateUserPromptRules(manifests, detectedAgent, ctx) {
|
|
@@ -34,8 +37,9 @@ function evaluateSessionCaptureRules(manifests, detectedAgent, ctx) {
|
|
|
34
37
|
return evaluateSessionStartRules(manifests, detectedAgent, ctx);
|
|
35
38
|
}
|
|
36
39
|
function scopePermits(rule, owningAgent, detectedAgent) {
|
|
37
|
-
if (
|
|
38
|
-
|
|
40
|
+
if (owningAgent === detectedAgent) return true;
|
|
41
|
+
if (rule.scope !== "any_agent") return false;
|
|
42
|
+
return detectedAgent === DEFAULT_SYMBIONT_NAME;
|
|
39
43
|
}
|
|
40
44
|
function whenMatches(rule, ctx) {
|
|
41
45
|
const {
|
|
@@ -114,4 +118,4 @@ export {
|
|
|
114
118
|
evaluateSessionCaptureRules,
|
|
115
119
|
readTranscriptMeta
|
|
116
120
|
};
|
|
117
|
-
//# sourceMappingURL=chunk-
|
|
121
|
+
//# sourceMappingURL=chunk-EKZG2MCD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/capture-rules.ts","../src/hooks/transcript-meta.ts"],"sourcesContent":["/**\n * Generic capture-rule evaluator.\n *\n * Each symbiont manifest declares `capture.rules` — a list of `{ event,\n * when, action }` records that describe how Myco should filter captured\n * events for that agent. This module loads rules from every manifest\n * in one place and exposes a pure evaluator the hook handlers call\n * without knowing anything symbiont-specific.\n *\n * Adding a new symbiont's capture behavior is a YAML-only change: edit\n * that agent's manifest file, no hook or evaluator changes needed.\n *\n * Rule scope (`this_agent` vs `any_agent`) lets rules opt into running\n * even when agent detection itself fails — useful for ephemeral\n * sub-invocations that legitimately lack the signals we key on.\n *\n * Conditions should prefer structural signals (e.g.,\n * `transcript_path_missing`) over text matching so rules stay robust\n * across upstream agent updates.\n */\n\nimport type { CaptureRule, SymbiontManifest } from '../symbionts/manifest-schema.js';\nimport { getAtPath } from '../utils/dot-path.js';\nimport { DEFAULT_SYMBIONT_NAME } from '../constants.js';\n\n/** Structured context a rule can match against at UserPromptSubmit time. */\nexport interface UserPromptRuleContext {\n /** The user prompt text as received from the hook. */\n prompt: string;\n /** Transcript path from the hook payload, if any. Empty/undefined signals an ephemeral session. */\n transcriptPath?: string;\n /** Parsed first JSON line (session_meta) from the transcript, if available. */\n transcriptMeta?: Record<string, unknown>;\n}\n\n/** Structured context a rule can match against at SessionStart time. */\nexport interface SessionStartRuleContext {\n /** Transcript path from the hook payload, if any. Empty/undefined signals an ephemeral session. */\n transcriptPath?: string;\n /** Parsed first JSON line (session_meta) from the transcript, if available. */\n transcriptMeta?: Record<string, unknown>;\n}\n\n/** Outcome of evaluating user_prompt rules. */\nexport type UserPromptDecision =\n | { action: 'pass'; prompt: string }\n | { action: 'rewrite'; prompt: string; reason?: string }\n | { action: 'drop'; reason?: string };\n\n/** Outcome of evaluating session capture rules. No rewrite — there's no prompt text yet. */\nexport type SessionStartDecision =\n | { action: 'pass' }\n | { action: 'drop'; reason?: string };\n\n/**\n * Evaluate all user_prompt rules from every manifest against one context.\n *\n * Rules are checked in declaration order, first-match-wins. A rule only\n * fires when:\n * 1. its `event` is `user_prompt`,\n * 2. its scope permits it (see scope semantics in manifest-schema.ts),\n * 3. every condition in its `when` block matches the context.\n *\n * If no rule matches, the prompt passes through unchanged.\n */\nexport function evaluateUserPromptRules(\n manifests: SymbiontManifest[],\n detectedAgent: string,\n ctx: UserPromptRuleContext,\n): UserPromptDecision {\n for (const manifest of manifests) {\n const rules = manifest.capture?.rules ?? [];\n for (const rule of rules) {\n if (rule.event !== 'user_prompt') continue;\n if (!scopePermits(rule, manifest.name, detectedAgent)) continue;\n if (!whenMatches(rule, ctx)) continue;\n return applyAction(rule, ctx);\n }\n }\n return { action: 'pass', prompt: ctx.prompt };\n}\n\n/**\n * Evaluate all session_start rules from every manifest.\n *\n * Same first-match-wins semantics as user_prompt rules. The only action\n * session_start rules can take is `drop` — text rewriting doesn't apply\n * because there's no prompt text at SessionStart time. Rules that\n * specify prompt-based conditions (prompt_starts_with / prompt_contains)\n * match against an empty prompt here, so they'll never fire on the\n * session_start pass.\n *\n * Callers should skip session registration when the result is `drop`.\n */\nexport function evaluateSessionStartRules(\n manifests: SymbiontManifest[],\n detectedAgent: string,\n ctx: SessionStartRuleContext,\n): SessionStartDecision {\n for (const manifest of manifests) {\n const rules = manifest.capture?.rules ?? [];\n for (const rule of rules) {\n if (rule.event !== 'session_start') continue;\n if (!scopePermits(rule, manifest.name, detectedAgent)) continue;\n if (!whenMatches(rule, { prompt: '', transcriptPath: ctx.transcriptPath, transcriptMeta: ctx.transcriptMeta })) continue;\n if (rule.action === 'drop') {\n return { action: 'drop', reason: rule.reason };\n }\n // rewrite_prompt is meaningless at session_start — skip and let\n // later rules have a chance to match.\n }\n }\n return { action: 'pass' };\n}\n\n/**\n * Evaluate whether a session should be materialized at a lifecycle boundary.\n *\n * SessionStart uses this before registering a session row. Stop processing uses\n * the same decision before transcript-backed capture or stop-driven\n * auto-registration. Keeping both boundaries on the same manifest-driven\n * evaluator makes the rule sustainable for every symbiont, not just the one\n * that first exposed the gap.\n */\nexport function evaluateSessionCaptureRules(\n manifests: SymbiontManifest[],\n detectedAgent: string,\n ctx: SessionStartRuleContext,\n): SessionStartDecision {\n return evaluateSessionStartRules(manifests, detectedAgent, ctx);\n}\n\n/**\n * `any_agent` scope lets a manifest's rule fire even when its own agent isn't\n * the detected one — designed for phantom sub-invocations that arrive with\n * ambiguous attribution. Detection falls back to `DEFAULT_SYMBIONT_NAME` when\n * it fails (see normalize.ts and event-dispatch.ts), so `any_agent` only\n * crosses the agent boundary in that exact case. Events carrying a specific\n * non-default agent (e.g., plugin-delivered `agent: \"opencode\"`) are trusted\n * attribution and must not be touched by another manifest's `any_agent` rules\n * — otherwise codex's phantom-drop rule contaminates opencode, silently\n * dropping every event for any opencode session whose registry was lost.\n */\nfunction scopePermits(rule: CaptureRule, owningAgent: string, detectedAgent: string): boolean {\n if (owningAgent === detectedAgent) return true;\n if (rule.scope !== 'any_agent') return false;\n return detectedAgent === DEFAULT_SYMBIONT_NAME;\n}\n\nfunction whenMatches(rule: CaptureRule, ctx: UserPromptRuleContext): boolean {\n const {\n prompt_starts_with,\n prompt_contains,\n transcript_path_missing,\n transcript_meta_field_exists,\n transcript_meta_field_equals,\n } = rule.when;\n\n // Refuse rules with no conditions — prevents a mistyped YAML file from\n // accidentally creating a blanket \"drop everything\" rule.\n const hasAnyCondition =\n prompt_starts_with !== undefined ||\n prompt_contains !== undefined ||\n transcript_path_missing !== undefined ||\n transcript_meta_field_exists !== undefined ||\n transcript_meta_field_equals !== undefined;\n if (!hasAnyCondition) return false;\n\n if (prompt_starts_with && !ctx.prompt.startsWith(prompt_starts_with)) return false;\n if (prompt_contains && !ctx.prompt.includes(prompt_contains)) return false;\n\n if (transcript_path_missing !== undefined) {\n const missing = !ctx.transcriptPath || ctx.transcriptPath.length === 0;\n if (transcript_path_missing && !missing) return false;\n if (!transcript_path_missing && missing) return false;\n }\n\n if (transcript_meta_field_exists !== undefined) {\n if (!ctx.transcriptMeta) return false;\n if (!getAtPath(ctx.transcriptMeta, transcript_meta_field_exists)) return false;\n }\n\n if (transcript_meta_field_equals !== undefined) {\n if (!ctx.transcriptMeta) return false;\n if (getAtPath(ctx.transcriptMeta, transcript_meta_field_equals.path) !== transcript_meta_field_equals.value) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction applyAction(rule: CaptureRule, ctx: UserPromptRuleContext): UserPromptDecision {\n if (rule.action === 'drop') {\n return { action: 'drop', reason: rule.reason };\n }\n // rewrite_prompt — keep only the substring after the extract_after marker.\n // If the marker isn't in the prompt, fall through to `pass` so we don't\n // accidentally blank out a prompt that turned out not to match after all.\n const marker = rule.extract_after;\n if (!marker) return { action: 'pass', prompt: ctx.prompt };\n const idx = ctx.prompt.indexOf(marker);\n if (idx === -1) return { action: 'pass', prompt: ctx.prompt };\n const after = ctx.prompt.slice(idx + marker.length);\n const next = rule.trim ? after.trim() : after;\n if (!next) return { action: 'pass', prompt: ctx.prompt };\n return { action: 'rewrite', prompt: next, reason: rule.reason };\n}\n","/**\n * Read the first JSON line (session_meta) from an agent's transcript file.\n *\n * Every supported agent writes a JSONL transcript where the first entry\n * is a `session_meta` record containing session identity, source info,\n * model, and other structural signals. This reader extracts that record\n * so capture rules can make decisions based on it — e.g., detecting\n * sub-agent thread spawns that have real transcript files but aren't\n * user-initiated sessions.\n *\n * Returns the parsed `payload` object from the session_meta entry, or\n * null if the file doesn't exist, isn't readable, or doesn't contain\n * valid session_meta JSON.\n */\n\nimport fs from 'node:fs';\n\n/**\n * Read and parse the session_meta payload from a transcript file.\n *\n * @param transcriptPath - Absolute path to the JSONL transcript.\n * @returns The session_meta payload object, or null on any failure.\n */\nexport function readTranscriptMeta(transcriptPath: string): Record<string, unknown> | null {\n try {\n const fd = fs.openSync(transcriptPath, 'r');\n try {\n // Read enough bytes for the first line. Session meta can be large\n // when it embeds the full system prompt (base_instructions) — Codex\n // sessions routinely exceed 16 KB. 128 KB covers all known cases.\n const buf = Buffer.alloc(131072);\n const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);\n if (bytesRead === 0) return null;\n\n const chunk = buf.toString('utf-8', 0, bytesRead);\n const newlineIdx = chunk.indexOf('\\n');\n const firstLine = newlineIdx >= 0 ? chunk.slice(0, newlineIdx) : chunk;\n if (!firstLine) return null;\n\n const entry = JSON.parse(firstLine);\n\n // session_meta entries have { type: \"session_meta\", payload: {...} }\n if (entry?.type === 'session_meta' && typeof entry.payload === 'object') {\n return entry.payload as Record<string, unknown>;\n }\n\n // Some agents may write the meta directly without the wrapper\n if (typeof entry === 'object' && entry !== null) {\n return entry as Record<string, unknown>;\n }\n\n return null;\n } finally {\n fs.closeSync(fd);\n }\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
|