@mrc2204/agent-smart-memo 5.1.16 → 5.1.18

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.
Files changed (48) hide show
  1. package/dist/cli/platform-installers.d.ts.map +1 -1
  2. package/dist/cli/platform-installers.js +259 -56
  3. package/dist/cli/platform-installers.js.map +1 -1
  4. package/dist/core/precedence/recall-precedence.d.ts +20 -0
  5. package/dist/core/precedence/recall-precedence.d.ts.map +1 -0
  6. package/dist/core/precedence/recall-precedence.js +36 -0
  7. package/dist/core/precedence/recall-precedence.js.map +1 -0
  8. package/dist/core/promotion/promotion-lifecycle.d.ts +20 -0
  9. package/dist/core/promotion/promotion-lifecycle.d.ts.map +1 -0
  10. package/dist/core/promotion/promotion-lifecycle.js +46 -0
  11. package/dist/core/promotion/promotion-lifecycle.js.map +1 -0
  12. package/dist/core/retrieval-policy.d.ts +26 -0
  13. package/dist/core/retrieval-policy.d.ts.map +1 -0
  14. package/dist/core/retrieval-policy.js +44 -0
  15. package/dist/core/retrieval-policy.js.map +1 -0
  16. package/dist/core/usecases/semantic-memory-usecase.d.ts +4 -3
  17. package/dist/core/usecases/semantic-memory-usecase.d.ts.map +1 -1
  18. package/dist/core/usecases/semantic-memory-usecase.js +54 -16
  19. package/dist/core/usecases/semantic-memory-usecase.js.map +1 -1
  20. package/dist/db/slot-db.d.ts +25 -1
  21. package/dist/db/slot-db.d.ts.map +1 -1
  22. package/dist/db/slot-db.js +391 -167
  23. package/dist/db/slot-db.js.map +1 -1
  24. package/dist/hooks/auto-capture.d.ts +4 -4
  25. package/dist/hooks/auto-capture.d.ts.map +1 -1
  26. package/dist/hooks/auto-capture.js +89 -26
  27. package/dist/hooks/auto-capture.js.map +1 -1
  28. package/dist/hooks/auto-recall.d.ts +3 -3
  29. package/dist/hooks/auto-recall.d.ts.map +1 -1
  30. package/dist/hooks/auto-recall.js +91 -49
  31. package/dist/hooks/auto-recall.js.map +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/services/qdrant.d.ts +3 -2
  36. package/dist/services/qdrant.d.ts.map +1 -1
  37. package/dist/services/qdrant.js +24 -7
  38. package/dist/services/qdrant.js.map +1 -1
  39. package/dist/shared/memory-config.d.ts +7 -0
  40. package/dist/shared/memory-config.d.ts.map +1 -1
  41. package/dist/shared/memory-config.js +67 -16
  42. package/dist/shared/memory-config.js.map +1 -1
  43. package/dist/types.d.ts +13 -5
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +27 -62
  46. package/bin/asm.mjs +0 -450
  47. package/bin/opencode-mcp-server.mjs +0 -318
  48. package/scripts/init-openclaw.mjs +0 -721
@@ -6,17 +6,17 @@
6
6
  * Keys use dot-notation: "profile.name", "preferences.theme", etc.
7
7
  * Values are stored as JSON strings.
8
8
  */
9
- import { DatabaseSync } from "node:sqlite";
10
9
  import { randomUUID } from "node:crypto";
10
+ import { existsSync, mkdirSync } from "node:fs";
11
11
  import { join } from "node:path";
12
- import { mkdirSync, existsSync } from "node:fs";
12
+ import { DatabaseSync } from "node:sqlite";
13
+ import { populateUniversalCodeGraphForFile } from "../core/graph/code-graph-populator.js";
14
+ import { buildSymbolId } from "../core/ingest/ids.js";
13
15
  import { buildChunkArtifacts } from "../core/ingest/ingest-pipeline.js";
14
16
  import { extractSemanticBlocks } from "../core/ingest/semantic-block-extractor.js";
15
- import { buildSymbolId } from "../core/ingest/ids.js";
16
- import { populateUniversalCodeGraphForFile } from "../core/graph/code-graph-populator.js";
17
- import { GraphDB } from "./graph-db.js";
18
17
  import { getSlotTTL } from "../shared/memory-config.js";
19
- import { resolveLegacyStateDirInput, resolveSlotDbDir } from "../shared/slotdb-path.js";
18
+ import { resolveLegacyStateDirInput, resolveSlotDbDir, } from "../shared/slotdb-path.js";
19
+ import { GraphDB } from "./graph-db.js";
20
20
  // Re-export GraphDB types
21
21
  export { GraphDB };
22
22
  // ============================================================================
@@ -32,7 +32,10 @@ export class SlotDB {
32
32
  // Priority resolver for new config/env flow.
33
33
  // If explicit slotDbDir option is provided, it is treated as already-resolved target dir.
34
34
  if (options?.slotDbDir) {
35
- this.slotDbDir = resolveSlotDbDir({ slotDbDir: options.slotDbDir, stateDir: stateDirOrSlotDbDir });
35
+ this.slotDbDir = resolveSlotDbDir({
36
+ slotDbDir: options.slotDbDir,
37
+ stateDir: stateDirOrSlotDbDir,
38
+ });
36
39
  }
37
40
  else {
38
41
  // Backward compatibility for legacy constructor callsites that pass OPENCLAW_STATE_DIR.
@@ -330,7 +333,9 @@ export class SlotDB {
330
333
  confidence,
331
334
  version: newVersion,
332
335
  updated_at: now,
333
- expires_at: input.expires_at !== undefined ? input.expires_at : existing.expires_at,
336
+ expires_at: input.expires_at !== undefined
337
+ ? input.expires_at
338
+ : existing.expires_at,
334
339
  };
335
340
  }
336
341
  // Insert new slot
@@ -452,8 +457,10 @@ export class SlotDB {
452
457
  const normalizedRepoRoot = this.normalizeRepoRoot(input.repo_root);
453
458
  const normalizedRepoRemote = this.normalizeRepoRemote(input.repo_remote);
454
459
  const existingAlias = this.getProjectByAlias(scopeUserId, scopeAgentId, projectAlias);
455
- if (existingAlias && existingAlias.project.project_id !== projectId && !input.allow_alias_update) {
456
- throw new Error(`project_alias \"${projectAlias}\" is already mapped to another project_id`);
460
+ if (existingAlias &&
461
+ existingAlias.project.project_id !== projectId &&
462
+ !input.allow_alias_update) {
463
+ throw new Error(`project_alias "${projectAlias}" is already mapped to another project_id`);
457
464
  }
458
465
  let targetProjectId = projectId;
459
466
  const existingByRepoRoot = input.reuse_existing_repo_root && normalizedRepoRoot
@@ -532,7 +539,9 @@ export class SlotDB {
532
539
  scope_agent_id: String(row.scope_agent_id),
533
540
  project_name: String(row.project_name),
534
541
  repo_root: row.repo_root ? String(row.repo_root) : null,
535
- repo_remote_primary: row.repo_remote_primary ? String(row.repo_remote_primary) : null,
542
+ repo_remote_primary: row.repo_remote_primary
543
+ ? String(row.repo_remote_primary)
544
+ : null,
536
545
  active_version: row.active_version ? String(row.active_version) : null,
537
546
  lifecycle_status: String(row.lifecycle_status),
538
547
  created_at: String(row.created_at),
@@ -652,18 +661,26 @@ export class SlotDB {
652
661
  const files = Number(fileCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
653
662
  const chunks = Number(chunkCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
654
663
  const symbols = Number(symbolCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
655
- this.db.prepare(`UPDATE file_index_state
664
+ this.db
665
+ .prepare(`UPDATE file_index_state
656
666
  SET index_state = 'stale', active = 0, tombstone_at = ?, indexed_at = ?
657
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`).run(now, now, scopeUserId, scopeAgentId, projectId);
658
- this.db.prepare(`UPDATE chunk_registry
667
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`)
668
+ .run(now, now, scopeUserId, scopeAgentId, projectId);
669
+ this.db
670
+ .prepare(`UPDATE chunk_registry
659
671
  SET index_state = 'stale', active = 0, tombstone_at = ?, indexed_at = ?
660
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`).run(now, now, scopeUserId, scopeAgentId, projectId);
661
- this.db.prepare(`UPDATE symbol_registry
672
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`)
673
+ .run(now, now, scopeUserId, scopeAgentId, projectId);
674
+ this.db
675
+ .prepare(`UPDATE symbol_registry
662
676
  SET index_state = 'stale', active = 0, tombstone_at = ?, indexed_at = ?
663
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`).run(now, now, scopeUserId, scopeAgentId, projectId);
664
- this.db.prepare(`UPDATE projects
677
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`)
678
+ .run(now, now, scopeUserId, scopeAgentId, projectId);
679
+ this.db
680
+ .prepare(`UPDATE projects
665
681
  SET lifecycle_status = 'deindexed', updated_at = ?
666
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(now, scopeUserId, scopeAgentId, projectId);
682
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
683
+ .run(now, scopeUserId, scopeAgentId, projectId);
667
684
  this.insertIndexRun(scopeUserId, scopeAgentId, {
668
685
  run_id: randomUUID(),
669
686
  project_id: projectId,
@@ -703,17 +720,27 @@ export class SlotDB {
703
720
  });
704
721
  }
705
722
  const now = new Date().toISOString();
706
- const aliasesRemoved = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM project_aliases
707
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
708
- const trackerMappingsRemoved = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM project_tracker_mappings
709
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
710
- this.db.prepare(`DELETE FROM project_aliases
711
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
712
- this.db.prepare(`DELETE FROM project_tracker_mappings
713
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
714
- this.db.prepare(`UPDATE projects
723
+ const aliasesRemoved = Number(this.db
724
+ .prepare(`SELECT COUNT(*) as cnt FROM project_aliases
725
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
726
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
727
+ const trackerMappingsRemoved = Number(this.db
728
+ .prepare(`SELECT COUNT(*) as cnt FROM project_tracker_mappings
729
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
730
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
731
+ this.db
732
+ .prepare(`DELETE FROM project_aliases
733
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
734
+ .run(scopeUserId, scopeAgentId, projectId);
735
+ this.db
736
+ .prepare(`DELETE FROM project_tracker_mappings
737
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
738
+ .run(scopeUserId, scopeAgentId, projectId);
739
+ this.db
740
+ .prepare(`UPDATE projects
715
741
  SET lifecycle_status = 'detached', repo_root = NULL, repo_remote_primary = NULL, active_version = NULL, updated_at = ?
716
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(now, scopeUserId, scopeAgentId, projectId);
742
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
743
+ .run(now, scopeUserId, scopeAgentId, projectId);
717
744
  return {
718
745
  project_id: projectId,
719
746
  lifecycle_status: "detached",
@@ -758,17 +785,27 @@ export class SlotDB {
758
785
  deindexedFirst = true;
759
786
  }
760
787
  const now = new Date().toISOString();
761
- const aliasesRemoved = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM project_aliases
762
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
763
- const trackerMappingsRemoved = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM project_tracker_mappings
764
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
765
- this.db.prepare(`DELETE FROM project_aliases
766
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
767
- this.db.prepare(`DELETE FROM project_tracker_mappings
768
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
769
- this.db.prepare(`UPDATE projects
788
+ const aliasesRemoved = Number(this.db
789
+ .prepare(`SELECT COUNT(*) as cnt FROM project_aliases
790
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
791
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
792
+ const trackerMappingsRemoved = Number(this.db
793
+ .prepare(`SELECT COUNT(*) as cnt FROM project_tracker_mappings
794
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
795
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
796
+ this.db
797
+ .prepare(`DELETE FROM project_aliases
798
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
799
+ .run(scopeUserId, scopeAgentId, projectId);
800
+ this.db
801
+ .prepare(`DELETE FROM project_tracker_mappings
802
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
803
+ .run(scopeUserId, scopeAgentId, projectId);
804
+ this.db
805
+ .prepare(`UPDATE projects
770
806
  SET lifecycle_status = 'disabled', repo_root = NULL, repo_remote_primary = NULL, active_version = NULL, updated_at = ?
771
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(now, scopeUserId, scopeAgentId, projectId);
807
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
808
+ .run(now, scopeUserId, scopeAgentId, projectId);
772
809
  this.upsertProjectRegistrationState(scopeUserId, scopeAgentId, {
773
810
  project_id: projectId,
774
811
  registration_status: "draft",
@@ -807,8 +844,10 @@ export class SlotDB {
807
844
  if (!project) {
808
845
  throw new Error(`project_id '${projectId}' is not registered`);
809
846
  }
810
- const countBy = (table) => Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM ${table}
811
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
847
+ const countBy = (table) => Number(this.db
848
+ .prepare(`SELECT COUNT(*) as cnt FROM ${table}
849
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
850
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
812
851
  const canPurge = project.lifecycle_status === "disabled";
813
852
  const reason = canPurge
814
853
  ? "safe to purge: lifecycle_status is disabled and explicit confirm is still required"
@@ -845,7 +884,9 @@ export class SlotDB {
845
884
  if (input.confirm !== true) {
846
885
  throw new Error("project.purge requires explicit confirm=true");
847
886
  }
848
- const preview = this.purgePreviewProject(scopeUserId, scopeAgentId, { project_id: projectId });
887
+ const preview = this.purgePreviewProject(scopeUserId, scopeAgentId, {
888
+ project_id: projectId,
889
+ });
849
890
  if (!preview.purge_guard.allowed) {
850
891
  throw new Error(preview.purge_guard.reason);
851
892
  }
@@ -865,26 +906,46 @@ export class SlotDB {
865
906
  };
866
907
  this.db.exec("BEGIN");
867
908
  try {
868
- this.db.prepare(`DELETE FROM project_aliases
869
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
870
- this.db.prepare(`DELETE FROM project_tracker_mappings
871
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
872
- this.db.prepare(`DELETE FROM project_registration_state
873
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
874
- this.db.prepare(`DELETE FROM index_runs
875
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
876
- this.db.prepare(`DELETE FROM project_index_watch_state
877
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
878
- this.db.prepare(`DELETE FROM file_index_state
879
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
880
- this.db.prepare(`DELETE FROM chunk_registry
881
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
882
- this.db.prepare(`DELETE FROM symbol_registry
883
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
884
- this.db.prepare(`DELETE FROM task_registry
885
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
886
- this.db.prepare(`DELETE FROM projects
887
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(scopeUserId, scopeAgentId, projectId);
909
+ this.db
910
+ .prepare(`DELETE FROM project_aliases
911
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
912
+ .run(scopeUserId, scopeAgentId, projectId);
913
+ this.db
914
+ .prepare(`DELETE FROM project_tracker_mappings
915
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
916
+ .run(scopeUserId, scopeAgentId, projectId);
917
+ this.db
918
+ .prepare(`DELETE FROM project_registration_state
919
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
920
+ .run(scopeUserId, scopeAgentId, projectId);
921
+ this.db
922
+ .prepare(`DELETE FROM index_runs
923
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
924
+ .run(scopeUserId, scopeAgentId, projectId);
925
+ this.db
926
+ .prepare(`DELETE FROM project_index_watch_state
927
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
928
+ .run(scopeUserId, scopeAgentId, projectId);
929
+ this.db
930
+ .prepare(`DELETE FROM file_index_state
931
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
932
+ .run(scopeUserId, scopeAgentId, projectId);
933
+ this.db
934
+ .prepare(`DELETE FROM chunk_registry
935
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
936
+ .run(scopeUserId, scopeAgentId, projectId);
937
+ this.db
938
+ .prepare(`DELETE FROM symbol_registry
939
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
940
+ .run(scopeUserId, scopeAgentId, projectId);
941
+ this.db
942
+ .prepare(`DELETE FROM task_registry
943
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
944
+ .run(scopeUserId, scopeAgentId, projectId);
945
+ this.db
946
+ .prepare(`DELETE FROM projects
947
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`)
948
+ .run(scopeUserId, scopeAgentId, projectId);
888
949
  this.db.exec("COMMIT");
889
950
  }
890
951
  catch (error) {
@@ -998,7 +1059,8 @@ export class SlotDB {
998
1059
  });
999
1060
  }
1000
1061
  for (const block of blocks) {
1001
- if (!block.symbol_name || !["function", "class", "method", "tool"].includes(block.kind))
1062
+ if (!block.symbol_name ||
1063
+ !["function", "class", "method", "tool"].includes(block.kind))
1002
1064
  continue;
1003
1065
  const symbolFqn = block.semantic_path || `${block.kind}:${block.symbol_name}`;
1004
1066
  this.upsertSymbolRegistry(scopeUserId, scopeAgentId, {
@@ -1039,14 +1101,18 @@ export class SlotDB {
1039
1101
  last_checksum_snapshot: checksumSnapshotRecord,
1040
1102
  updated_at: nowIso,
1041
1103
  });
1042
- this.db.prepare(`UPDATE projects
1104
+ this.db
1105
+ .prepare(`UPDATE projects
1043
1106
  SET lifecycle_status = 'active', updated_at = ?
1044
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND lifecycle_status = 'deindexed'`).run(nowIso, scopeUserId, scopeAgentId, input.project_id);
1107
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND lifecycle_status = 'deindexed'`)
1108
+ .run(nowIso, scopeUserId, scopeAgentId, input.project_id);
1045
1109
  this.finishIndexRun(scopeUserId, scopeAgentId, runId, "indexed", null, nowIso);
1046
- this.db.prepare(`UPDATE projects
1110
+ this.db
1111
+ .prepare(`UPDATE projects
1047
1112
  SET lifecycle_status = 'active', updated_at = ?
1048
1113
  WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
1049
- AND lifecycle_status = 'deindexed'`).run(nowIso, scopeUserId, scopeAgentId, input.project_id);
1114
+ AND lifecycle_status = 'deindexed'`)
1115
+ .run(nowIso, scopeUserId, scopeAgentId, input.project_id);
1050
1116
  return {
1051
1117
  run_id: runId,
1052
1118
  project_id: input.project_id,
@@ -1086,7 +1152,9 @@ export class SlotDB {
1086
1152
  }
1087
1153
  const existing = this.getTaskRegistryRecordById(scopeUserId, scopeAgentId, taskId);
1088
1154
  const relatedTaskIds = this.normalizeStringArray(input.related_task_ids);
1089
- const filesTouched = this.normalizeStringArray(input.files_touched).map((p) => this.normalizeRelativePath(p)).filter(Boolean);
1155
+ const filesTouched = this.normalizeStringArray(input.files_touched)
1156
+ .map((p) => this.normalizeRelativePath(p))
1157
+ .filter(Boolean);
1090
1158
  const symbolsTouched = this.normalizeStringArray(input.symbols_touched);
1091
1159
  const commitRefs = this.normalizeStringArray(input.commit_refs);
1092
1160
  const diffRefs = this.normalizeStringArray(input.diff_refs);
@@ -1220,18 +1288,37 @@ export class SlotDB {
1220
1288
  if (!project) {
1221
1289
  throw new Error(`project_id '${projectId}' is not registered`);
1222
1290
  }
1223
- const isSearchDisabled = ["deindexed", "detached", "disabled", "purged"].includes(project.lifecycle_status);
1291
+ const isSearchDisabled = [
1292
+ "deindexed",
1293
+ "detached",
1294
+ "disabled",
1295
+ "purged",
1296
+ ].includes(project.lifecycle_status);
1224
1297
  if (isSearchDisabled) {
1225
- const files = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM file_index_state
1226
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1227
- const chunks = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM chunk_registry
1228
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1229
- const symbols = Number(this.db.prepare(`SELECT COUNT(*) as cnt FROM symbol_registry
1230
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`).get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1298
+ const files = Number(this.db
1299
+ .prepare(`SELECT COUNT(*) as cnt FROM file_index_state
1300
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`)
1301
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1302
+ const chunks = Number(this.db
1303
+ .prepare(`SELECT COUNT(*) as cnt FROM chunk_registry
1304
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`)
1305
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1306
+ const symbols = Number(this.db
1307
+ .prepare(`SELECT COUNT(*) as cnt FROM symbol_registry
1308
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tombstone_at IS NOT NULL`)
1309
+ .get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
1231
1310
  const taskContextSelector = {
1232
- ...(input.task_context?.task_id ? { task_id: String(input.task_context.task_id).trim() } : {}),
1233
- ...(input.task_context?.tracker_issue_key ? { tracker_issue_key: String(input.task_context.tracker_issue_key).trim() } : {}),
1234
- ...(input.task_context?.task_title ? { task_title: String(input.task_context.task_title).trim() } : {}),
1311
+ ...(input.task_context?.task_id
1312
+ ? { task_id: String(input.task_context.task_id).trim() }
1313
+ : {}),
1314
+ ...(input.task_context?.tracker_issue_key
1315
+ ? {
1316
+ tracker_issue_key: String(input.task_context.tracker_issue_key).trim(),
1317
+ }
1318
+ : {}),
1319
+ ...(input.task_context?.task_title
1320
+ ? { task_title: String(input.task_context.task_title).trim() }
1321
+ : {}),
1235
1322
  };
1236
1323
  const reasonByLifecycle = {
1237
1324
  deindexed: "project is deindexed; retrieval is disabled until reindex",
@@ -1248,9 +1335,14 @@ export class SlotDB {
1248
1335
  count: 0,
1249
1336
  task_lineage_context: null,
1250
1337
  task_context_resolution: {
1251
- status: Object.keys(taskContextSelector).length > 0 ? "selector_not_resolved" : "not_requested",
1338
+ status: Object.keys(taskContextSelector).length > 0
1339
+ ? "selector_not_resolved"
1340
+ : "not_requested",
1252
1341
  ...(Object.keys(taskContextSelector).length > 0
1253
- ? { reason: reasonByLifecycle[project.lifecycle_status] || "project lifecycle disables retrieval" }
1342
+ ? {
1343
+ reason: reasonByLifecycle[project.lifecycle_status] ||
1344
+ "project lifecycle disables retrieval",
1345
+ }
1254
1346
  : {}),
1255
1347
  selector: taskContextSelector,
1256
1348
  recoverable: project.lifecycle_status !== "purged",
@@ -1281,7 +1373,10 @@ export class SlotDB {
1281
1373
  }
1282
1374
  const limit = Math.min(Math.max(Number(input.limit || 10), 1), 50);
1283
1375
  const queryLc = query.toLowerCase();
1284
- const queryTokens = Array.from(new Set(queryLc.split(/[^a-z0-9._/-]+/i).map((t) => t.trim()).filter(Boolean)));
1376
+ const queryTokens = Array.from(new Set(queryLc
1377
+ .split(/[^a-z0-9._/-]+/i)
1378
+ .map((t) => t.trim())
1379
+ .filter(Boolean)));
1285
1380
  const tokenScore = (text) => {
1286
1381
  if (!queryTokens.length)
1287
1382
  return 0;
@@ -1294,29 +1389,55 @@ export class SlotDB {
1294
1389
  return matched / queryTokens.length;
1295
1390
  };
1296
1391
  const exactMatchScore = (candidate) => {
1297
- const value = String(candidate || '').trim().toLowerCase();
1392
+ const value = String(candidate || "")
1393
+ .trim()
1394
+ .toLowerCase();
1298
1395
  if (!value)
1299
1396
  return 0;
1300
1397
  if (value === queryLc)
1301
1398
  return 1;
1302
1399
  if (value.endsWith(`.${queryLc}`))
1303
1400
  return 0.92;
1304
- if (value.includes(`/${queryLc}`) || value.includes(`:${queryLc}`) || value.includes(`#${queryLc}`))
1401
+ if (value.includes(`/${queryLc}`) ||
1402
+ value.includes(`:${queryLc}`) ||
1403
+ value.includes(`#${queryLc}`))
1305
1404
  return 0.78;
1306
1405
  return 0;
1307
1406
  };
1308
- const codeIntentHints = ['function', 'class', 'method', 'symbol', 'route', 'endpoint', 'extractor', 'registry', 'chunk', 'snippet', 'code'];
1309
- const looksCodeIntent = codeIntentHints.some((hint) => queryLc.includes(hint)) || query.includes('/') || query.includes('_');
1407
+ const codeIntentHints = [
1408
+ "function",
1409
+ "class",
1410
+ "method",
1411
+ "symbol",
1412
+ "route",
1413
+ "endpoint",
1414
+ "extractor",
1415
+ "registry",
1416
+ "chunk",
1417
+ "snippet",
1418
+ "code",
1419
+ ];
1420
+ const looksCodeIntent = codeIntentHints.some((hint) => queryLc.includes(hint)) ||
1421
+ query.includes("/") ||
1422
+ query.includes("_");
1310
1423
  const looksIdentifierQuery = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(query) ||
1311
1424
  /^[a-zA-Z_][a-zA-Z0-9_.#:-]*$/.test(query) ||
1312
- query.includes('::') ||
1313
- query.includes('.') ||
1314
- query.includes('_');
1425
+ query.includes("::") ||
1426
+ query.includes(".") ||
1427
+ query.includes("_");
1315
1428
  const taskContextInput = input.task_context;
1316
1429
  const taskContextSelector = {
1317
- ...(taskContextInput?.task_id ? { task_id: String(taskContextInput.task_id).trim() } : {}),
1318
- ...(taskContextInput?.tracker_issue_key ? { tracker_issue_key: String(taskContextInput.tracker_issue_key).trim() } : {}),
1319
- ...(taskContextInput?.task_title ? { task_title: String(taskContextInput.task_title).trim() } : {}),
1430
+ ...(taskContextInput?.task_id
1431
+ ? { task_id: String(taskContextInput.task_id).trim() }
1432
+ : {}),
1433
+ ...(taskContextInput?.tracker_issue_key
1434
+ ? {
1435
+ tracker_issue_key: String(taskContextInput.tracker_issue_key).trim(),
1436
+ }
1437
+ : {}),
1438
+ ...(taskContextInput?.task_title
1439
+ ? { task_title: String(taskContextInput.task_title).trim() }
1440
+ : {}),
1320
1441
  };
1321
1442
  let lineageContext = null;
1322
1443
  let taskContextResolution = {
@@ -1353,7 +1474,9 @@ export class SlotDB {
1353
1474
  };
1354
1475
  }
1355
1476
  }
1356
- const lexicalPathPrefix = this.normalizeStringArray(input.path_prefix).map((p) => this.normalizeRelativePath(p)).filter(Boolean);
1477
+ const lexicalPathPrefix = this.normalizeStringArray(input.path_prefix)
1478
+ .map((p) => this.normalizeRelativePath(p))
1479
+ .filter(Boolean);
1357
1480
  const lexicalModules = new Set(this.normalizeStringArray(input.module).map((s) => s.toLowerCase()));
1358
1481
  const lexicalLanguages = new Set(this.normalizeStringArray(input.language).map((s) => s.toLowerCase()));
1359
1482
  const lexicalTaskIds = new Set(this.normalizeStringArray(input.task_id));
@@ -1362,7 +1485,10 @@ export class SlotDB {
1362
1485
  lexicalTaskIds.add(lineageContext.focus.task_id);
1363
1486
  if (lineageContext.focus.tracker_issue_key)
1364
1487
  lexicalIssueKeys.add(lineageContext.focus.tracker_issue_key.toUpperCase());
1365
- for (const t of [...lineageContext.parent_chain, ...lineageContext.related_tasks]) {
1488
+ for (const t of [
1489
+ ...lineageContext.parent_chain,
1490
+ ...lineageContext.related_tasks,
1491
+ ]) {
1366
1492
  lexicalTaskIds.add(t.task_id);
1367
1493
  if (t.tracker_issue_key)
1368
1494
  lexicalIssueKeys.add(t.tracker_issue_key.toUpperCase());
@@ -1389,15 +1515,20 @@ export class SlotDB {
1389
1515
  const relativePath = String(row.relative_path || "");
1390
1516
  const moduleName = row.module ? String(row.module) : null;
1391
1517
  const language = row.language ? String(row.language) : null;
1392
- if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1518
+ if (lexicalPathPrefix.length > 0 &&
1519
+ !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1393
1520
  continue;
1394
1521
  if (lexicalModules.size > 0 && !moduleName)
1395
1522
  continue;
1396
- if (lexicalModules.size > 0 && moduleName && !lexicalModules.has(moduleName.toLowerCase()))
1523
+ if (lexicalModules.size > 0 &&
1524
+ moduleName &&
1525
+ !lexicalModules.has(moduleName.toLowerCase()))
1397
1526
  continue;
1398
1527
  if (lexicalLanguages.size > 0 && !language)
1399
1528
  continue;
1400
- if (lexicalLanguages.size > 0 && language && !lexicalLanguages.has(language.toLowerCase()))
1529
+ if (lexicalLanguages.size > 0 &&
1530
+ language &&
1531
+ !lexicalLanguages.has(language.toLowerCase()))
1401
1532
  continue;
1402
1533
  const text = `${relativePath} ${moduleName || ""} ${language || ""}`.toLowerCase();
1403
1534
  let score = 0;
@@ -1407,12 +1538,16 @@ export class SlotDB {
1407
1538
  if (lineageContext && lineageContext.touched_files.includes(relativePath))
1408
1539
  score += 0.35;
1409
1540
  if (looksCodeIntent) {
1410
- if (relativePath.includes('/docs/') || relativePath.startsWith('docs/') || relativePath.includes('README'))
1541
+ if (relativePath.includes("/docs/") ||
1542
+ relativePath.startsWith("docs/") ||
1543
+ relativePath.includes("README"))
1411
1544
  score -= 0.18;
1412
- if (relativePath.startsWith('src/') || relativePath.startsWith('tests/'))
1545
+ if (relativePath.startsWith("src/") ||
1546
+ relativePath.startsWith("tests/"))
1413
1547
  score += 0.14;
1414
1548
  }
1415
- else if (relativePath.includes("README") || relativePath.includes("docs/")) {
1549
+ else if (relativePath.includes("README") ||
1550
+ relativePath.includes("docs/")) {
1416
1551
  score += 0.05;
1417
1552
  }
1418
1553
  if (looksIdentifierQuery) {
@@ -1420,7 +1555,7 @@ export class SlotDB {
1420
1555
  }
1421
1556
  if (score <= 0.08)
1422
1557
  continue;
1423
- pushDebug('file_index_state', {
1558
+ pushDebug("file_index_state", {
1424
1559
  relative_path: relativePath,
1425
1560
  score: Number(score.toFixed(4)),
1426
1561
  module: moduleName,
@@ -1450,15 +1585,20 @@ export class SlotDB {
1450
1585
  const symbolKind = String(row.symbol_kind || "");
1451
1586
  const symbolFqn = String(row.symbol_fqn || "");
1452
1587
  symbolRowsById.set(String(row.symbol_id), row);
1453
- if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1588
+ if (lexicalPathPrefix.length > 0 &&
1589
+ !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1454
1590
  continue;
1455
1591
  if (lexicalModules.size > 0 && !moduleName)
1456
1592
  continue;
1457
- if (lexicalModules.size > 0 && moduleName && !lexicalModules.has(moduleName.toLowerCase()))
1593
+ if (lexicalModules.size > 0 &&
1594
+ moduleName &&
1595
+ !lexicalModules.has(moduleName.toLowerCase()))
1458
1596
  continue;
1459
1597
  if (lexicalLanguages.size > 0 && !language)
1460
1598
  continue;
1461
- if (lexicalLanguages.size > 0 && language && !lexicalLanguages.has(language.toLowerCase()))
1599
+ if (lexicalLanguages.size > 0 &&
1600
+ language &&
1601
+ !lexicalLanguages.has(language.toLowerCase()))
1462
1602
  continue;
1463
1603
  const text = `${symbolName} ${symbolFqn} ${relativePath} ${moduleName || ""} ${symbolKind}`.toLowerCase();
1464
1604
  let score = 0;
@@ -1479,13 +1619,14 @@ export class SlotDB {
1479
1619
  score += 0.3;
1480
1620
  if (lineageContext && lineageContext.touched_files.includes(relativePath))
1481
1621
  score += 0.12;
1482
- if (looksCodeIntent && (relativePath.startsWith('src/') || relativePath.startsWith('tests/')))
1622
+ if (looksCodeIntent &&
1623
+ (relativePath.startsWith("src/") || relativePath.startsWith("tests/")))
1483
1624
  score += 0.08;
1484
1625
  if (looksIdentifierQuery)
1485
1626
  score += 0.18;
1486
1627
  if (score <= 0.08)
1487
1628
  continue;
1488
- pushDebug('symbol_registry', {
1629
+ pushDebug("symbol_registry", {
1489
1630
  relative_path: relativePath,
1490
1631
  symbol_name: symbolName,
1491
1632
  symbol_fqn: symbolFqn,
@@ -1517,9 +1658,10 @@ export class SlotDB {
1517
1658
  const chunkKind = String(row.chunk_kind || "");
1518
1659
  const symbolId = row.symbol_id ? String(row.symbol_id) : null;
1519
1660
  const symbolRow = symbolId ? symbolRowsById.get(symbolId) : null;
1520
- const symbolName = symbolRow ? String(symbolRow.symbol_name || '') : '';
1521
- const symbolFqn = symbolRow ? String(symbolRow.symbol_fqn || '') : '';
1522
- if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1661
+ const symbolName = symbolRow ? String(symbolRow.symbol_name || "") : "";
1662
+ const symbolFqn = symbolRow ? String(symbolRow.symbol_fqn || "") : "";
1663
+ if (lexicalPathPrefix.length > 0 &&
1664
+ !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
1523
1665
  continue;
1524
1666
  const text = `${relativePath} ${chunkKind} ${symbolId || ""} ${symbolName} ${symbolFqn}`.toLowerCase();
1525
1667
  let score = 0;
@@ -1537,16 +1679,19 @@ export class SlotDB {
1537
1679
  if (lineageContext && lineageContext.touched_files.includes(relativePath))
1538
1680
  score += 0.15;
1539
1681
  if (looksCodeIntent) {
1540
- if (relativePath.includes('/docs/') || relativePath.startsWith('docs/') || relativePath.includes('README'))
1682
+ if (relativePath.includes("/docs/") ||
1683
+ relativePath.startsWith("docs/") ||
1684
+ relativePath.includes("README"))
1541
1685
  score -= 0.14;
1542
- if (relativePath.startsWith('src/') || relativePath.startsWith('tests/'))
1686
+ if (relativePath.startsWith("src/") ||
1687
+ relativePath.startsWith("tests/"))
1543
1688
  score += 0.1;
1544
1689
  }
1545
1690
  if (looksIdentifierQuery)
1546
1691
  score += 0.12;
1547
1692
  if (score <= 0.08)
1548
1693
  continue;
1549
- pushDebug('chunk_registry', {
1694
+ pushDebug("chunk_registry", {
1550
1695
  relative_path: relativePath,
1551
1696
  chunk_kind: chunkKind,
1552
1697
  symbol_name: symbolName || null,
@@ -1562,7 +1707,9 @@ export class SlotDB {
1562
1707
  project_id: projectId,
1563
1708
  relative_path: relativePath,
1564
1709
  symbol_name: symbolName || undefined,
1565
- snippet: symbolName ? `chunk ${chunkKind} for symbol ${symbolName} in ${relativePath}` : `chunk ${chunkKind} in ${relativePath}`,
1710
+ snippet: symbolName
1711
+ ? `chunk ${chunkKind} for symbol ${symbolName} in ${relativePath}`
1712
+ : `chunk ${chunkKind} in ${relativePath}`,
1566
1713
  });
1567
1714
  }
1568
1715
  const taskStmt = this.db.prepare(`SELECT * FROM task_registry
@@ -1570,7 +1717,9 @@ export class SlotDB {
1570
1717
  const taskRows = taskStmt.all(scopeUserId, scopeAgentId, projectId);
1571
1718
  for (const row of taskRows) {
1572
1719
  const task = this.rowToTaskRecord(row);
1573
- const taskIssueKey = task.tracker_issue_key ? task.tracker_issue_key.toUpperCase() : null;
1720
+ const taskIssueKey = task.tracker_issue_key
1721
+ ? task.tracker_issue_key.toUpperCase()
1722
+ : null;
1574
1723
  if (lexicalTaskIds.size > 0 && !lexicalTaskIds.has(task.task_id)) {
1575
1724
  if (!taskIssueKey || !lexicalIssueKeys.has(taskIssueKey)) {
1576
1725
  // keep if user query still lexically matches strongly
@@ -1585,7 +1734,9 @@ export class SlotDB {
1585
1734
  ...(task.symbols_touched || []),
1586
1735
  ...(task.commit_refs || []),
1587
1736
  task.decision_notes || "",
1588
- ].join(" ").toLowerCase();
1737
+ ]
1738
+ .join(" ")
1739
+ .toLowerCase();
1589
1740
  let score = 0;
1590
1741
  if (text.includes(queryLc))
1591
1742
  score += 0.58;
@@ -1604,7 +1755,7 @@ export class SlotDB {
1604
1755
  }
1605
1756
  if (score <= 0.08)
1606
1757
  continue;
1607
- pushDebug('task_registry', {
1758
+ pushDebug("task_registry", {
1608
1759
  task_id: task.task_id,
1609
1760
  tracker_issue_key: task.tracker_issue_key,
1610
1761
  task_title: task.task_title,
@@ -1649,10 +1800,18 @@ export class SlotDB {
1649
1800
  task_registry: debugBuckets.task_registry.length,
1650
1801
  },
1651
1802
  top_candidates: {
1652
- file_index_state: debugBuckets.file_index_state.sort((a, b) => Number(b.score || 0) - Number(a.score || 0)).slice(0, 8),
1653
- symbol_registry: debugBuckets.symbol_registry.sort((a, b) => Number(b.score || 0) - Number(a.score || 0)).slice(0, 8),
1654
- chunk_registry: debugBuckets.chunk_registry.sort((a, b) => Number(b.score || 0) - Number(a.score || 0)).slice(0, 8),
1655
- task_registry: debugBuckets.task_registry.sort((a, b) => Number(b.score || 0) - Number(a.score || 0)).slice(0, 8),
1803
+ file_index_state: debugBuckets.file_index_state
1804
+ .sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
1805
+ .slice(0, 8),
1806
+ symbol_registry: debugBuckets.symbol_registry
1807
+ .sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
1808
+ .slice(0, 8),
1809
+ chunk_registry: debugBuckets.chunk_registry
1810
+ .sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
1811
+ .slice(0, 8),
1812
+ task_registry: debugBuckets.task_registry
1813
+ .sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
1814
+ .slice(0, 8),
1656
1815
  },
1657
1816
  }
1658
1817
  : undefined,
@@ -1683,7 +1842,8 @@ export class SlotDB {
1683
1842
  if (!message.includes("task lineage focus not found for provided selector")) {
1684
1843
  throw error;
1685
1844
  }
1686
- const unresolvedTaskId = taskIdSelector || `unresolved:${trackerSelector || taskTitleSelector || "selector"}`;
1845
+ const unresolvedTaskId = taskIdSelector ||
1846
+ `unresolved:${trackerSelector || taskTitleSelector || "selector"}`;
1687
1847
  const unresolvedTitle = taskTitleSelector || "Unresolved task lineage selector";
1688
1848
  return {
1689
1849
  status: "selector_not_resolved",
@@ -1701,7 +1861,9 @@ export class SlotDB {
1701
1861
  commit_refs: [],
1702
1862
  };
1703
1863
  }
1704
- const changedFiles = this.uniqueSorted((lineage.touched_files || []).map((p) => this.normalizeRelativePath(p)).filter(Boolean));
1864
+ const changedFiles = this.uniqueSorted((lineage.touched_files || [])
1865
+ .map((p) => this.normalizeRelativePath(p))
1866
+ .filter(Boolean));
1705
1867
  const relatedSymbolsMap = new Map();
1706
1868
  for (const symbolName of lineage.touched_symbols || []) {
1707
1869
  const normalized = String(symbolName || "").trim();
@@ -1727,7 +1889,9 @@ export class SlotDB {
1727
1889
  const symbolName = String(row.symbol_name || "").trim();
1728
1890
  if (!symbolName)
1729
1891
  continue;
1730
- const relPath = row.relative_path ? String(row.relative_path) : undefined;
1892
+ const relPath = row.relative_path
1893
+ ? String(row.relative_path)
1894
+ : undefined;
1731
1895
  const symbolFqn = row.symbol_fqn ? String(row.symbol_fqn) : undefined;
1732
1896
  const key = `symbol_registry:${symbolName}:${symbolFqn || ""}:${relPath || ""}`;
1733
1897
  if (!relatedSymbolsMap.has(key)) {
@@ -1757,8 +1921,7 @@ export class SlotDB {
1757
1921
  if (!project) {
1758
1922
  throw new Error(`project_id '${projectId}' is not registered`);
1759
1923
  }
1760
- const aliases = this.listProjects(scopeUserId, scopeAgentId)
1761
- .find((row) => row.project.project_id === projectId)?.aliases || [];
1924
+ const aliases = this.listProjects(scopeUserId, scopeAgentId).find((row) => row.project.project_id === projectId)?.aliases || [];
1762
1925
  const registration = this.getProjectRegistrationState(scopeUserId, scopeAgentId, projectId);
1763
1926
  const trackerStmt = this.db.prepare(`SELECT * FROM project_tracker_mappings
1764
1927
  WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
@@ -1769,12 +1932,20 @@ export class SlotDB {
1769
1932
  scope_user_id: String(row.scope_user_id),
1770
1933
  scope_agent_id: String(row.scope_agent_id),
1771
1934
  tracker_type: String(row.tracker_type),
1772
- tracker_space_key: row.tracker_space_key ? String(row.tracker_space_key) : null,
1773
- tracker_project_id: row.tracker_project_id ? String(row.tracker_project_id) : null,
1774
- default_epic_key: row.default_epic_key ? String(row.default_epic_key) : null,
1935
+ tracker_space_key: row.tracker_space_key
1936
+ ? String(row.tracker_space_key)
1937
+ : null,
1938
+ tracker_project_id: row.tracker_project_id
1939
+ ? String(row.tracker_project_id)
1940
+ : null,
1941
+ default_epic_key: row.default_epic_key
1942
+ ? String(row.default_epic_key)
1943
+ : null,
1775
1944
  board_key: row.board_key ? String(row.board_key) : null,
1776
1945
  active_version: row.active_version ? String(row.active_version) : null,
1777
- external_project_url: row.external_project_url ? String(row.external_project_url) : null,
1946
+ external_project_url: row.external_project_url
1947
+ ? String(row.external_project_url)
1948
+ : null,
1778
1949
  created_at: String(row.created_at),
1779
1950
  updated_at: String(row.updated_at),
1780
1951
  }));
@@ -1807,7 +1978,9 @@ export class SlotDB {
1807
1978
  const recentTasks = taskStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
1808
1979
  task_id: String(row.task_id),
1809
1980
  task_title: String(row.task_title),
1810
- tracker_issue_key: row.tracker_issue_key ? String(row.tracker_issue_key) : null,
1981
+ tracker_issue_key: row.tracker_issue_key
1982
+ ? String(row.tracker_issue_key)
1983
+ : null,
1811
1984
  task_status: row.task_status ? String(row.task_status) : null,
1812
1985
  }));
1813
1986
  const runStmt = this.db.prepare(`SELECT run_id, trigger_type, state, started_at, finished_at
@@ -1841,7 +2014,8 @@ export class SlotDB {
1841
2014
  const onlyAliases = new Set(this.normalizeStringArray(input.only_aliases).map((a) => this.normalizeProjectAlias(a)));
1842
2015
  const projects = this.listProjects(scopeUserId, scopeAgentId);
1843
2016
  const selected = projects.filter((row) => {
1844
- if (onlyProjectIds.size > 0 && !onlyProjectIds.has(row.project.project_id))
2017
+ if (onlyProjectIds.size > 0 &&
2018
+ !onlyProjectIds.has(row.project.project_id))
1845
2019
  return false;
1846
2020
  if (onlyAliases.size > 0) {
1847
2021
  const aliases = row.aliases.map((a) => this.normalizeProjectAlias(a.project_alias));
@@ -1859,7 +2033,9 @@ export class SlotDB {
1859
2033
  const project = row.project;
1860
2034
  const warnings = [];
1861
2035
  const actions = [];
1862
- const existingAliases = new Set(row.aliases.map((a) => this.normalizeProjectAlias(a.project_alias)).filter(Boolean));
2036
+ const existingAliases = new Set(row.aliases
2037
+ .map((a) => this.normalizeProjectAlias(a.project_alias))
2038
+ .filter(Boolean));
1863
2039
  const inferredAliases = this.inferBackfillAliases(project, existingAliases, source);
1864
2040
  const inferredMappings = this.inferBackfillTrackerMappings(scopeUserId, scopeAgentId, project.project_id, project, source);
1865
2041
  if (mode === "apply") {
@@ -1873,10 +2049,10 @@ export class SlotDB {
1873
2049
  }
1874
2050
  for (const mapping of inferredMappings) {
1875
2051
  const existing = this.getProjectTrackerMapping(scopeUserId, scopeAgentId, project.project_id, mapping.tracker_type);
1876
- if (existing
1877
- && existing.tracker_space_key === mapping.tracker_space_key
1878
- && existing.default_epic_key === mapping.default_epic_key
1879
- && existing.tracker_project_id === mapping.tracker_project_id) {
2052
+ if (existing &&
2053
+ existing.tracker_space_key === mapping.tracker_space_key &&
2054
+ existing.default_epic_key === mapping.default_epic_key &&
2055
+ existing.tracker_project_id === mapping.tracker_project_id) {
1880
2056
  continue;
1881
2057
  }
1882
2058
  this.setProjectTrackerMapping(scopeUserId, scopeAgentId, {
@@ -1893,14 +2069,15 @@ export class SlotDB {
1893
2069
  const primaryAlias = this.pickPrimaryAlias(existingAliases, project);
1894
2070
  const completeness = this.computeRegistrationCompleteness(project, primaryAlias);
1895
2071
  const missingFields = this.computeMissingRegistrationFields(project, primaryAlias);
1896
- const hasTracker = inferredMappings.length > 0 || Boolean(this.getProjectTrackerMapping(scopeUserId, scopeAgentId, project.project_id, "jira"));
2072
+ const hasTracker = inferredMappings.length > 0 ||
2073
+ Boolean(this.getProjectTrackerMapping(scopeUserId, scopeAgentId, project.project_id, "jira"));
1897
2074
  const status = hasTracker && completeness >= 90 ? "validated" : "registered";
1898
2075
  const validationStatus = missingFields.length === 0 ? "ok" : "warn";
1899
2076
  const existingRegistration = this.getProjectRegistrationState(scopeUserId, scopeAgentId, project.project_id);
1900
- const shouldUpdateRegistration = !existingRegistration
1901
- || input.force_registration_state === true
1902
- || existingRegistration.registration_status === "draft"
1903
- || existingRegistration.validation_status !== validationStatus;
2077
+ const shouldUpdateRegistration = !existingRegistration ||
2078
+ input.force_registration_state === true ||
2079
+ existingRegistration.registration_status === "draft" ||
2080
+ existingRegistration.validation_status !== validationStatus;
1904
2081
  if (shouldUpdateRegistration) {
1905
2082
  if (mode === "apply") {
1906
2083
  this.upsertProjectRegistrationState(scopeUserId, scopeAgentId, {
@@ -2036,38 +2213,54 @@ export class SlotDB {
2036
2213
  stmt.run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
2037
2214
  }
2038
2215
  upsertChunkRegistry(scopeUserId, scopeAgentId, input) {
2039
- const existing = this.db.prepare(`SELECT chunk_id FROM chunk_registry WHERE scope_user_id = ? AND scope_agent_id = ? AND chunk_id = ?`).get(scopeUserId, scopeAgentId, input.chunk_id);
2216
+ const existing = this.db
2217
+ .prepare(`SELECT chunk_id FROM chunk_registry WHERE scope_user_id = ? AND scope_agent_id = ? AND chunk_id = ?`)
2218
+ .get(scopeUserId, scopeAgentId, input.chunk_id);
2040
2219
  if (existing) {
2041
- this.db.prepare(`UPDATE chunk_registry
2220
+ this.db
2221
+ .prepare(`UPDATE chunk_registry
2042
2222
  SET project_id = ?, file_id = ?, relative_path = ?, chunk_kind = ?, symbol_id = ?, task_id = ?, checksum = ?, qdrant_point_id = ?, index_state = ?, active = ?, tombstone_at = ?, indexed_at = ?
2043
- WHERE scope_user_id = ? AND scope_agent_id = ? AND chunk_id = ?`).run(input.project_id, input.file_id, input.relative_path, input.chunk_kind, input.symbol_id, input.task_id || null, input.checksum, input.qdrant_point_id || null, input.index_state, input.active, input.tombstone_at, input.indexed_at, scopeUserId, scopeAgentId, input.chunk_id);
2223
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND chunk_id = ?`)
2224
+ .run(input.project_id, input.file_id, input.relative_path, input.chunk_kind, input.symbol_id, input.task_id || null, input.checksum, input.qdrant_point_id || null, input.index_state, input.active, input.tombstone_at, input.indexed_at, scopeUserId, scopeAgentId, input.chunk_id);
2044
2225
  return;
2045
2226
  }
2046
- this.db.prepare(`INSERT INTO chunk_registry (
2227
+ this.db
2228
+ .prepare(`INSERT INTO chunk_registry (
2047
2229
  chunk_id, scope_user_id, scope_agent_id, project_id, file_id, relative_path, chunk_kind, symbol_id, task_id, checksum, qdrant_point_id, index_state, active, tombstone_at, indexed_at
2048
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.chunk_id, scopeUserId, scopeAgentId, input.project_id, input.file_id, input.relative_path, input.chunk_kind, input.symbol_id, input.task_id || null, input.checksum, input.qdrant_point_id || null, input.index_state, input.active, input.tombstone_at, input.indexed_at);
2230
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
2231
+ .run(input.chunk_id, scopeUserId, scopeAgentId, input.project_id, input.file_id, input.relative_path, input.chunk_kind, input.symbol_id, input.task_id || null, input.checksum, input.qdrant_point_id || null, input.index_state, input.active, input.tombstone_at, input.indexed_at);
2049
2232
  }
2050
2233
  upsertSymbolRegistry(scopeUserId, scopeAgentId, input) {
2051
- const existing = this.db.prepare(`SELECT symbol_id FROM symbol_registry WHERE scope_user_id = ? AND scope_agent_id = ? AND symbol_id = ?`).get(scopeUserId, scopeAgentId, input.symbol_id);
2234
+ const existing = this.db
2235
+ .prepare(`SELECT symbol_id FROM symbol_registry WHERE scope_user_id = ? AND scope_agent_id = ? AND symbol_id = ?`)
2236
+ .get(scopeUserId, scopeAgentId, input.symbol_id);
2052
2237
  if (existing) {
2053
- this.db.prepare(`UPDATE symbol_registry
2238
+ this.db
2239
+ .prepare(`UPDATE symbol_registry
2054
2240
  SET project_id = ?, relative_path = ?, module = ?, language = ?, symbol_name = ?, symbol_fqn = ?, symbol_kind = ?, signature_hash = ?, index_state = ?, active = ?, tombstone_at = ?, indexed_at = ?
2055
- WHERE scope_user_id = ? AND scope_agent_id = ? AND symbol_id = ?`).run(input.project_id, input.relative_path, input.module, input.language, input.symbol_name, input.symbol_fqn, input.symbol_kind, input.signature_hash || null, input.index_state, input.active, input.tombstone_at, input.indexed_at, scopeUserId, scopeAgentId, input.symbol_id);
2241
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND symbol_id = ?`)
2242
+ .run(input.project_id, input.relative_path, input.module, input.language, input.symbol_name, input.symbol_fqn, input.symbol_kind, input.signature_hash || null, input.index_state, input.active, input.tombstone_at, input.indexed_at, scopeUserId, scopeAgentId, input.symbol_id);
2056
2243
  return;
2057
2244
  }
2058
- this.db.prepare(`INSERT INTO symbol_registry (
2245
+ this.db
2246
+ .prepare(`INSERT INTO symbol_registry (
2059
2247
  symbol_id, scope_user_id, scope_agent_id, project_id, relative_path, module, language, symbol_name, symbol_fqn, symbol_kind, signature_hash, index_state, active, tombstone_at, indexed_at
2060
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.symbol_id, scopeUserId, scopeAgentId, input.project_id, input.relative_path, input.module, input.language, input.symbol_name, input.symbol_fqn, input.symbol_kind, input.signature_hash || null, input.index_state, input.active, input.tombstone_at, input.indexed_at);
2248
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
2249
+ .run(input.symbol_id, scopeUserId, scopeAgentId, input.project_id, input.relative_path, input.module, input.language, input.symbol_name, input.symbol_fqn, input.symbol_kind, input.signature_hash || null, input.index_state, input.active, input.tombstone_at, input.indexed_at);
2061
2250
  }
2062
2251
  markProjectChunksByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
2063
- this.db.prepare(`UPDATE chunk_registry
2252
+ this.db
2253
+ .prepare(`UPDATE chunk_registry
2064
2254
  SET index_state = 'stale', active = 0, tombstone_at = ?, indexed_at = ?
2065
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND relative_path = ?`).run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
2255
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND relative_path = ?`)
2256
+ .run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
2066
2257
  }
2067
2258
  markProjectSymbolsByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
2068
- this.db.prepare(`UPDATE symbol_registry
2259
+ this.db
2260
+ .prepare(`UPDATE symbol_registry
2069
2261
  SET index_state = 'stale', active = 0, tombstone_at = ?, indexed_at = ?
2070
- WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND relative_path = ?`).run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
2262
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND relative_path = ?`)
2263
+ .run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
2071
2264
  }
2072
2265
  markProjectFileDeletedForEvent(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
2073
2266
  this.markFileIndexStateDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt);
@@ -2117,9 +2310,7 @@ export class SlotDB {
2117
2310
  normalizeStringArray(input) {
2118
2311
  if (!Array.isArray(input))
2119
2312
  return [];
2120
- return this.uniqueSorted(input
2121
- .map((item) => String(item || "").trim())
2122
- .filter(Boolean));
2313
+ return this.uniqueSorted(input.map((item) => String(item || "").trim()).filter(Boolean));
2123
2314
  }
2124
2315
  uniqueSorted(values) {
2125
2316
  return Array.from(new Set(values.map((v) => String(v || "").trim()).filter(Boolean))).sort((a, b) => a.localeCompare(b));
@@ -2128,7 +2319,10 @@ export class SlotDB {
2128
2319
  const candidates = new Set();
2129
2320
  if (source === "repo_root" || source === "mixed") {
2130
2321
  if (project.repo_root) {
2131
- const parts = project.repo_root.replace(/\\/g, "/").split("/").filter(Boolean);
2322
+ const parts = project.repo_root
2323
+ .replace(/\\/g, "/")
2324
+ .split("/")
2325
+ .filter(Boolean);
2132
2326
  const leaf = parts[parts.length - 1] || "";
2133
2327
  const normalized = this.normalizeProjectAlias(leaf);
2134
2328
  if (normalized)
@@ -2138,7 +2332,7 @@ export class SlotDB {
2138
2332
  if (source === "repo_remote" || source === "mixed") {
2139
2333
  if (project.repo_remote_primary) {
2140
2334
  const remote = String(project.repo_remote_primary);
2141
- const m = remote.match(/[:\/]([^/]+?)\.git$/i) || remote.match(/[:\/]([^/]+?)$/i);
2335
+ const m = remote.match(/[:/]([^/]+?)\.git$/i) || remote.match(/[:/]([^/]+?)$/i);
2142
2336
  const repoName = m?.[1] || "";
2143
2337
  const normalized = this.normalizeProjectAlias(repoName);
2144
2338
  if (normalized)
@@ -2168,7 +2362,9 @@ export class SlotDB {
2168
2362
  WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND tracker_issue_key IS NOT NULL AND tracker_issue_key != ''
2169
2363
  ORDER BY updated_at DESC LIMIT 200`);
2170
2364
  const rows = stmt.all(scopeUserId, scopeAgentId, projectId);
2171
- const keys = rows.map((r) => String(r.tracker_issue_key || "").trim()).filter(Boolean);
2365
+ const keys = rows
2366
+ .map((r) => String(r.tracker_issue_key || "").trim())
2367
+ .filter(Boolean);
2172
2368
  const jiraLike = keys
2173
2369
  .map((key) => key.match(/^([A-Z][A-Z0-9_]+)-\d+$/)?.[1] || null)
2174
2370
  .filter((x) => Boolean(x));
@@ -2199,7 +2395,9 @@ export class SlotDB {
2199
2395
  return Array.from(dedup.values()).sort((a, b) => b.confidence - a.confidence);
2200
2396
  }
2201
2397
  pickPrimaryAlias(existingAliases, project) {
2202
- const aliases = Array.from(existingAliases).filter(Boolean).sort((a, b) => a.localeCompare(b));
2398
+ const aliases = Array.from(existingAliases)
2399
+ .filter(Boolean)
2400
+ .sort((a, b) => a.localeCompare(b));
2203
2401
  if (aliases.length > 0)
2204
2402
  return aliases[0];
2205
2403
  const fromName = this.normalizeProjectAlias(project.project_name);
@@ -2223,6 +2421,23 @@ export class SlotDB {
2223
2421
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
2224
2422
  insertStmt.run(input.migration_id, scopeUserId, scopeAgentId, input.schema_from, input.schema_to, input.applied_at, input.status, input.notes || null);
2225
2423
  }
2424
+ recordMigrationState(scopeUserId, scopeAgentId, input) {
2425
+ this.upsertMigrationState(scopeUserId, scopeAgentId, input);
2426
+ }
2427
+ getMigrationState(scopeUserId, scopeAgentId, migrationId) {
2428
+ const stmt = this.db.prepare(`SELECT migration_id, schema_from, schema_to, applied_at, status, notes
2429
+ FROM migration_state
2430
+ WHERE scope_user_id = ? AND scope_agent_id = ? AND migration_id = ?`);
2431
+ const row = stmt.get(scopeUserId, scopeAgentId, migrationId);
2432
+ return row || null;
2433
+ }
2434
+ getMigrationStates(scopeUserId, scopeAgentId) {
2435
+ const stmt = this.db.prepare(`SELECT migration_id, schema_from, schema_to, applied_at, status, notes
2436
+ FROM migration_state
2437
+ WHERE scope_user_id = ? AND scope_agent_id = ?
2438
+ ORDER BY applied_at DESC`);
2439
+ return stmt.all(scopeUserId, scopeAgentId);
2440
+ }
2226
2441
  rowToTaskRecord(row) {
2227
2442
  return {
2228
2443
  task_id: row.task_id,
@@ -2255,16 +2470,25 @@ export class SlotDB {
2255
2470
  }
2256
2471
  computeRegistrationCompleteness(project, alias) {
2257
2472
  const requiredTotal = 3;
2258
- const requiredPresent = [project.project_id, alias, project.project_name].filter(Boolean).length;
2473
+ const requiredPresent = [
2474
+ project.project_id,
2475
+ alias,
2476
+ project.project_name,
2477
+ ].filter(Boolean).length;
2259
2478
  const optionalTotal = 3;
2260
- const optionalPresent = [project.repo_root, project.repo_remote_primary, project.active_version].filter(Boolean).length;
2261
- return Math.round((requiredPresent / requiredTotal) * 80 + (optionalPresent / optionalTotal) * 20);
2479
+ const optionalPresent = [
2480
+ project.repo_root,
2481
+ project.repo_remote_primary,
2482
+ project.active_version,
2483
+ ].filter(Boolean).length;
2484
+ return Math.round((requiredPresent / requiredTotal) * 80 +
2485
+ (optionalPresent / optionalTotal) * 20);
2262
2486
  }
2263
2487
  upsertProjectAlias(scopeUserId, scopeAgentId, projectId, projectAlias, isPrimary, now, allowAliasUpdate) {
2264
2488
  const existingAlias = this.getProjectAlias(scopeUserId, scopeAgentId, projectAlias);
2265
2489
  if (existingAlias) {
2266
2490
  if (existingAlias.project_id !== projectId && !allowAliasUpdate) {
2267
- throw new Error(`project_alias \"${projectAlias}\" is already mapped to another project_id`);
2491
+ throw new Error(`project_alias "${projectAlias}" is already mapped to another project_id`);
2268
2492
  }
2269
2493
  const stmt = this.db.prepare(`UPDATE project_aliases SET project_id = ?, is_primary = ?, updated_at = ? WHERE id = ?`);
2270
2494
  stmt.run(projectId, isPrimary ? 1 : 0, now, existingAlias.id);
@@ -2329,7 +2553,7 @@ export class SlotDB {
2329
2553
  AND expires_at IS NOT NULL AND expires_at < ?`);
2330
2554
  stmt.run(scopeUserId, scopeAgentId, now);
2331
2555
  // Auto-expire slots based on category TTL (auto_capture source)
2332
- const categories = ['project', 'environment', 'custom'];
2556
+ const categories = ["project", "environment", "custom"];
2333
2557
  for (const cat of categories) {
2334
2558
  const ttlDays = getSlotTTL(cat);
2335
2559
  const cutoff = new Date(Date.now() - ttlDays * 24 * 60 * 60 * 1000).toISOString();
@@ -2342,7 +2566,7 @@ export class SlotDB {
2342
2566
  }
2343
2567
  // Safety cleanup: volatile project status slots should expire even if source was manual/tool.
2344
2568
  // This prevents stale "current phase/task" slots from persisting forever.
2345
- const projectCutoff = new Date(Date.now() - getSlotTTL('project') * 24 * 60 * 60 * 1000).toISOString();
2569
+ const projectCutoff = new Date(Date.now() - getSlotTTL("project") * 24 * 60 * 60 * 1000).toISOString();
2346
2570
  const volatileProjectStmt = this.db.prepare(`DELETE FROM slots
2347
2571
  WHERE scope_user_id = ? AND scope_agent_id = ?
2348
2572
  AND category = 'project'