@mrc2204/agent-smart-memo 5.1.15 → 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.
- package/dist/cli/platform-installers.d.ts.map +1 -1
- package/dist/cli/platform-installers.js +259 -56
- package/dist/cli/platform-installers.js.map +1 -1
- package/dist/core/precedence/recall-precedence.d.ts +20 -0
- package/dist/core/precedence/recall-precedence.d.ts.map +1 -0
- package/dist/core/precedence/recall-precedence.js +36 -0
- package/dist/core/precedence/recall-precedence.js.map +1 -0
- package/dist/core/promotion/promotion-lifecycle.d.ts +20 -0
- package/dist/core/promotion/promotion-lifecycle.d.ts.map +1 -0
- package/dist/core/promotion/promotion-lifecycle.js +46 -0
- package/dist/core/promotion/promotion-lifecycle.js.map +1 -0
- package/dist/core/retrieval-policy.d.ts +26 -0
- package/dist/core/retrieval-policy.d.ts.map +1 -0
- package/dist/core/retrieval-policy.js +44 -0
- package/dist/core/retrieval-policy.js.map +1 -0
- package/dist/core/usecases/semantic-memory-usecase.d.ts +4 -3
- package/dist/core/usecases/semantic-memory-usecase.d.ts.map +1 -1
- package/dist/core/usecases/semantic-memory-usecase.js +54 -16
- package/dist/core/usecases/semantic-memory-usecase.js.map +1 -1
- package/dist/db/slot-db.d.ts +25 -1
- package/dist/db/slot-db.d.ts.map +1 -1
- package/dist/db/slot-db.js +391 -167
- package/dist/db/slot-db.js.map +1 -1
- package/dist/hooks/auto-capture.d.ts +4 -4
- package/dist/hooks/auto-capture.d.ts.map +1 -1
- package/dist/hooks/auto-capture.js +89 -26
- package/dist/hooks/auto-capture.js.map +1 -1
- package/dist/hooks/auto-recall.d.ts +3 -3
- package/dist/hooks/auto-recall.d.ts.map +1 -1
- package/dist/hooks/auto-recall.js +91 -49
- package/dist/hooks/auto-recall.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/services/qdrant.d.ts +3 -2
- package/dist/services/qdrant.d.ts.map +1 -1
- package/dist/services/qdrant.js +24 -7
- package/dist/services/qdrant.js.map +1 -1
- package/dist/shared/memory-config.d.ts +7 -0
- package/dist/shared/memory-config.d.ts.map +1 -1
- package/dist/shared/memory-config.js +67 -16
- package/dist/shared/memory-config.js.map +1 -1
- package/dist/types.d.ts +13 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -62
- package/bin/asm.mjs +0 -450
- package/bin/opencode-mcp-server.mjs +0 -318
- package/scripts/init-openclaw.mjs +0 -721
package/dist/db/slot-db.js
CHANGED
|
@@ -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 {
|
|
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({
|
|
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
|
|
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 &&
|
|
456
|
-
|
|
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
|
|
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
|
|
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`)
|
|
658
|
-
|
|
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`)
|
|
661
|
-
|
|
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`)
|
|
664
|
-
|
|
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 = ?`)
|
|
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
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this.db
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
this.db
|
|
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 = ?`)
|
|
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
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
this.db
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
this.db
|
|
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 = ?`)
|
|
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
|
|
811
|
-
|
|
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, {
|
|
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
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
this.db
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.db
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
this.db
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
this.db
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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 ||
|
|
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
|
|
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'`)
|
|
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
|
|
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'`)
|
|
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)
|
|
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 = [
|
|
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
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
const
|
|
1230
|
-
|
|
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
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
|
1338
|
+
status: Object.keys(taskContextSelector).length > 0
|
|
1339
|
+
? "selector_not_resolved"
|
|
1340
|
+
: "not_requested",
|
|
1252
1341
|
...(Object.keys(taskContextSelector).length > 0
|
|
1253
|
-
? {
|
|
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
|
|
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 ||
|
|
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}`) ||
|
|
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 = [
|
|
1309
|
-
|
|
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
|
|
1318
|
-
|
|
1319
|
-
|
|
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)
|
|
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 [
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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(
|
|
1541
|
+
if (relativePath.includes("/docs/") ||
|
|
1542
|
+
relativePath.startsWith("docs/") ||
|
|
1543
|
+
relativePath.includes("README"))
|
|
1411
1544
|
score -= 0.18;
|
|
1412
|
-
if (relativePath.startsWith(
|
|
1545
|
+
if (relativePath.startsWith("src/") ||
|
|
1546
|
+
relativePath.startsWith("tests/"))
|
|
1413
1547
|
score += 0.14;
|
|
1414
1548
|
}
|
|
1415
|
-
else if (relativePath.includes("README") ||
|
|
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(
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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(
|
|
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 &&
|
|
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(
|
|
1682
|
+
if (relativePath.includes("/docs/") ||
|
|
1683
|
+
relativePath.startsWith("docs/") ||
|
|
1684
|
+
relativePath.includes("README"))
|
|
1541
1685
|
score -= 0.14;
|
|
1542
|
-
if (relativePath.startsWith(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
]
|
|
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(
|
|
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
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
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 ||
|
|
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 || [])
|
|
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
|
|
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
|
|
1773
|
-
|
|
1774
|
-
|
|
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
|
|
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
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
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 ||
|
|
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
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
|
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
|
|
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 = ?`)
|
|
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
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
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
|
|
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
|
|
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 = ?`)
|
|
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
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
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
|
|
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 = ?`)
|
|
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
|
|
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 = ?`)
|
|
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
|
|
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(/[
|
|
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
|
|
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)
|
|
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 = [
|
|
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 = [
|
|
2261
|
-
|
|
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
|
|
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 = [
|
|
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(
|
|
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'
|