@mrc2204/agent-smart-memo 5.0.2 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -375
- package/bin/asm.mjs +365 -0
- package/bin/opencode-mcp-server.mjs +320 -0
- package/dist/core/contracts/adapter-contracts.d.ts +1 -1
- package/dist/core/contracts/adapter-contracts.d.ts.map +1 -1
- package/dist/core/contracts/change-overlay-contracts.d.ts +69 -0
- package/dist/core/contracts/change-overlay-contracts.d.ts.map +1 -0
- package/dist/core/contracts/change-overlay-contracts.js +2 -0
- package/dist/core/contracts/change-overlay-contracts.js.map +1 -0
- package/dist/core/contracts/feature-pack-contracts.d.ts +37 -0
- package/dist/core/contracts/feature-pack-contracts.d.ts.map +1 -0
- package/dist/core/contracts/feature-pack-contracts.js +8 -0
- package/dist/core/contracts/feature-pack-contracts.js.map +1 -0
- package/dist/core/contracts/project-query-contracts.d.ts +84 -0
- package/dist/core/contracts/project-query-contracts.d.ts.map +1 -0
- package/dist/core/contracts/project-query-contracts.js +2 -0
- package/dist/core/contracts/project-query-contracts.js.map +1 -0
- package/dist/core/graph/code-graph-model.d.ts +9 -0
- package/dist/core/graph/code-graph-model.d.ts.map +1 -0
- package/dist/core/graph/code-graph-model.js +70 -0
- package/dist/core/graph/code-graph-model.js.map +1 -0
- package/dist/core/graph/code-graph-populator.d.ts +20 -0
- package/dist/core/graph/code-graph-populator.d.ts.map +1 -0
- package/dist/core/graph/code-graph-populator.js +760 -0
- package/dist/core/graph/code-graph-populator.js.map +1 -0
- package/dist/core/graph/contracts.d.ts +29 -0
- package/dist/core/graph/contracts.d.ts.map +1 -0
- package/dist/core/graph/contracts.js +47 -0
- package/dist/core/graph/contracts.js.map +1 -0
- package/dist/core/ingest/contracts.d.ts +44 -0
- package/dist/core/ingest/contracts.d.ts.map +1 -0
- package/dist/core/ingest/contracts.js +2 -0
- package/dist/core/ingest/contracts.js.map +1 -0
- package/dist/core/ingest/ids.d.ts +5 -0
- package/dist/core/ingest/ids.d.ts.map +1 -0
- package/dist/core/ingest/ids.js +17 -0
- package/dist/core/ingest/ids.js.map +1 -0
- package/dist/core/ingest/ingest-pipeline.d.ts +4 -0
- package/dist/core/ingest/ingest-pipeline.d.ts.map +1 -0
- package/dist/core/ingest/ingest-pipeline.js +105 -0
- package/dist/core/ingest/ingest-pipeline.js.map +1 -0
- package/dist/core/ingest/semantic-block-extractor.d.ts +9 -0
- package/dist/core/ingest/semantic-block-extractor.d.ts.map +1 -0
- package/dist/core/ingest/semantic-block-extractor.js +171 -0
- package/dist/core/ingest/semantic-block-extractor.js.map +1 -0
- package/dist/core/usecases/default-memory-usecase-port.d.ts +38 -0
- package/dist/core/usecases/default-memory-usecase-port.d.ts.map +1 -1
- package/dist/core/usecases/default-memory-usecase-port.js +1686 -12
- package/dist/core/usecases/default-memory-usecase-port.js.map +1 -1
- package/dist/db/graph-db.d.ts +24 -0
- package/dist/db/graph-db.d.ts.map +1 -1
- package/dist/db/graph-db.js +81 -2
- package/dist/db/graph-db.js.map +1 -1
- package/dist/db/slot-db.d.ts +235 -2
- package/dist/db/slot-db.d.ts.map +1 -1
- package/dist/db/slot-db.js +840 -18
- package/dist/db/slot-db.js.map +1 -1
- package/dist/index.d.ts +7 -247
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -119
- package/dist/index.js.map +1 -1
- package/dist/shared/asm-config.d.ts +82 -0
- package/dist/shared/asm-config.d.ts.map +1 -0
- package/dist/shared/asm-config.js +254 -0
- package/dist/shared/asm-config.js.map +1 -0
- package/dist/shared/slotdb-path.d.ts +4 -3
- package/dist/shared/slotdb-path.d.ts.map +1 -1
- package/dist/shared/slotdb-path.js +15 -6
- package/dist/shared/slotdb-path.js.map +1 -1
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +131 -0
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +543 -0
- package/dist/tools/project-tools.js.map +1 -1
- package/openclaw.plugin.json +5 -164
- package/package.json +61 -26
- package/scripts/init-openclaw.mjs +727 -0
package/dist/db/slot-db.js
CHANGED
|
@@ -10,6 +10,10 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { mkdirSync, existsSync } from "node:fs";
|
|
13
|
+
import { buildChunkArtifacts } from "../core/ingest/ingest-pipeline.js";
|
|
14
|
+
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";
|
|
13
17
|
import { GraphDB } from "./graph-db.js";
|
|
14
18
|
import { getSlotTTL } from "../shared/memory-config.js";
|
|
15
19
|
import { resolveLegacyStateDirInput, resolveSlotDbDir } from "../shared/slotdb-path.js";
|
|
@@ -629,6 +633,278 @@ export class SlotDB {
|
|
|
629
633
|
updated_at: row.updated_at,
|
|
630
634
|
};
|
|
631
635
|
}
|
|
636
|
+
deindexProject(scopeUserId, scopeAgentId, input) {
|
|
637
|
+
const projectId = String(input.project_id || "").trim();
|
|
638
|
+
if (!projectId)
|
|
639
|
+
throw new Error("project_id is required");
|
|
640
|
+
const project = this.getProjectById(scopeUserId, scopeAgentId, projectId);
|
|
641
|
+
if (!project) {
|
|
642
|
+
throw new Error(`project_id '${projectId}' is not registered`);
|
|
643
|
+
}
|
|
644
|
+
const now = new Date().toISOString();
|
|
645
|
+
const reason = input.reason == null ? null : String(input.reason).trim() || null;
|
|
646
|
+
const fileCountStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM file_index_state
|
|
647
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
648
|
+
const chunkCountStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM chunk_registry
|
|
649
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
650
|
+
const symbolCountStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM symbol_registry
|
|
651
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
652
|
+
const files = Number(fileCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
|
|
653
|
+
const chunks = Number(chunkCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
|
|
654
|
+
const symbols = Number(symbolCountStmt.get(scopeUserId, scopeAgentId, projectId)?.cnt || 0);
|
|
655
|
+
this.db.prepare(`UPDATE file_index_state
|
|
656
|
+
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
|
|
659
|
+
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
|
|
662
|
+
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
|
|
665
|
+
SET lifecycle_status = 'deindexed', updated_at = ?
|
|
666
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`).run(now, scopeUserId, scopeAgentId, projectId);
|
|
667
|
+
this.insertIndexRun(scopeUserId, scopeAgentId, {
|
|
668
|
+
run_id: randomUUID(),
|
|
669
|
+
project_id: projectId,
|
|
670
|
+
index_profile: "default",
|
|
671
|
+
trigger_type: "manual",
|
|
672
|
+
state: "indexed",
|
|
673
|
+
started_at: now,
|
|
674
|
+
finished_at: now,
|
|
675
|
+
error_message: reason ? `deindex:${reason}` : "deindex",
|
|
676
|
+
});
|
|
677
|
+
return {
|
|
678
|
+
project_id: projectId,
|
|
679
|
+
lifecycle_status: "deindexed",
|
|
680
|
+
deindexed_at: now,
|
|
681
|
+
reason,
|
|
682
|
+
affected: {
|
|
683
|
+
files,
|
|
684
|
+
chunks,
|
|
685
|
+
symbols,
|
|
686
|
+
},
|
|
687
|
+
searchable: false,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
detachProject(scopeUserId, scopeAgentId, input) {
|
|
691
|
+
const projectId = String(input.project_id || "").trim();
|
|
692
|
+
if (!projectId)
|
|
693
|
+
throw new Error("project_id is required");
|
|
694
|
+
const project = this.getProjectById(scopeUserId, scopeAgentId, projectId);
|
|
695
|
+
if (!project) {
|
|
696
|
+
throw new Error(`project_id '${projectId}' is not registered`);
|
|
697
|
+
}
|
|
698
|
+
const reason = input.reason == null ? null : String(input.reason).trim() || null;
|
|
699
|
+
if (project.lifecycle_status !== "deindexed") {
|
|
700
|
+
this.deindexProject(scopeUserId, scopeAgentId, {
|
|
701
|
+
project_id: projectId,
|
|
702
|
+
reason: reason || "detach_precondition_deindex",
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
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
|
|
715
|
+
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);
|
|
717
|
+
return {
|
|
718
|
+
project_id: projectId,
|
|
719
|
+
lifecycle_status: "detached",
|
|
720
|
+
detached_at: now,
|
|
721
|
+
reason,
|
|
722
|
+
detached_fields: {
|
|
723
|
+
repo_root: project.repo_root != null,
|
|
724
|
+
repo_remote_primary: project.repo_remote_primary != null,
|
|
725
|
+
active_version: project.active_version != null,
|
|
726
|
+
aliases_removed: aliasesRemoved,
|
|
727
|
+
tracker_mappings_removed: trackerMappingsRemoved,
|
|
728
|
+
},
|
|
729
|
+
searchable: false,
|
|
730
|
+
next_actions: {
|
|
731
|
+
reattach_via_register_or_update: true,
|
|
732
|
+
reversible_by_re_register: true,
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
unregisterProject(scopeUserId, scopeAgentId, input) {
|
|
737
|
+
const projectId = String(input.project_id || "").trim();
|
|
738
|
+
if (!projectId)
|
|
739
|
+
throw new Error("project_id is required");
|
|
740
|
+
const mode = input.mode || "safe";
|
|
741
|
+
if (mode !== "safe") {
|
|
742
|
+
throw new Error("project.unregister currently supports mode='safe' only");
|
|
743
|
+
}
|
|
744
|
+
if (input.confirm !== true) {
|
|
745
|
+
throw new Error("project.unregister requires explicit confirm=true");
|
|
746
|
+
}
|
|
747
|
+
const project = this.getProjectById(scopeUserId, scopeAgentId, projectId);
|
|
748
|
+
if (!project) {
|
|
749
|
+
throw new Error(`project_id '${projectId}' is not registered`);
|
|
750
|
+
}
|
|
751
|
+
const reason = input.reason == null ? null : String(input.reason).trim() || null;
|
|
752
|
+
let deindexedFirst = false;
|
|
753
|
+
if (project.lifecycle_status !== "deindexed") {
|
|
754
|
+
this.deindexProject(scopeUserId, scopeAgentId, {
|
|
755
|
+
project_id: projectId,
|
|
756
|
+
reason: reason || "unregister_precondition_deindex",
|
|
757
|
+
});
|
|
758
|
+
deindexedFirst = true;
|
|
759
|
+
}
|
|
760
|
+
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
|
|
770
|
+
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);
|
|
772
|
+
this.upsertProjectRegistrationState(scopeUserId, scopeAgentId, {
|
|
773
|
+
project_id: projectId,
|
|
774
|
+
registration_status: "draft",
|
|
775
|
+
validation_status: "warn",
|
|
776
|
+
validation_notes: reason ? `unregistered:${reason}` : "unregistered",
|
|
777
|
+
completeness_score: 0,
|
|
778
|
+
missing_required_fields: ["project_alias", "repo_root"],
|
|
779
|
+
last_validated_at: now,
|
|
780
|
+
});
|
|
781
|
+
return {
|
|
782
|
+
project_id: projectId,
|
|
783
|
+
lifecycle_status: "disabled",
|
|
784
|
+
unregistered_at: now,
|
|
785
|
+
mode,
|
|
786
|
+
reason,
|
|
787
|
+
detached_fields: {
|
|
788
|
+
aliases_removed: aliasesRemoved,
|
|
789
|
+
tracker_mappings_removed: trackerMappingsRemoved,
|
|
790
|
+
},
|
|
791
|
+
registration_state: {
|
|
792
|
+
registration_status: "draft",
|
|
793
|
+
validation_status: "warn",
|
|
794
|
+
},
|
|
795
|
+
searchable: false,
|
|
796
|
+
audit: {
|
|
797
|
+
deindexed_first: deindexedFirst,
|
|
798
|
+
confirm_required: true,
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
purgePreviewProject(scopeUserId, scopeAgentId, input) {
|
|
803
|
+
const projectId = String(input.project_id || "").trim();
|
|
804
|
+
if (!projectId)
|
|
805
|
+
throw new Error("project_id is required");
|
|
806
|
+
const project = this.getProjectById(scopeUserId, scopeAgentId, projectId);
|
|
807
|
+
if (!project) {
|
|
808
|
+
throw new Error(`project_id '${projectId}' is not registered`);
|
|
809
|
+
}
|
|
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);
|
|
812
|
+
const canPurge = project.lifecycle_status === "disabled";
|
|
813
|
+
const reason = canPurge
|
|
814
|
+
? "safe to purge: lifecycle_status is disabled and explicit confirm is still required"
|
|
815
|
+
: `purge blocked: lifecycle_status must be disabled (current=${project.lifecycle_status})`;
|
|
816
|
+
return {
|
|
817
|
+
project_id: projectId,
|
|
818
|
+
current_lifecycle_status: project.lifecycle_status,
|
|
819
|
+
purge_guard: {
|
|
820
|
+
destructive: true,
|
|
821
|
+
allowed: canPurge,
|
|
822
|
+
reason,
|
|
823
|
+
requires_lifecycle_status: "disabled",
|
|
824
|
+
requires_confirm: true,
|
|
825
|
+
},
|
|
826
|
+
affected: {
|
|
827
|
+
project_row: 1,
|
|
828
|
+
aliases: countBy("project_aliases"),
|
|
829
|
+
tracker_mappings: countBy("project_tracker_mappings"),
|
|
830
|
+
registration_state: countBy("project_registration_state"),
|
|
831
|
+
index_runs: countBy("index_runs"),
|
|
832
|
+
watch_state: countBy("project_index_watch_state"),
|
|
833
|
+
file_index_state: countBy("file_index_state"),
|
|
834
|
+
chunk_registry: countBy("chunk_registry"),
|
|
835
|
+
symbol_registry: countBy("symbol_registry"),
|
|
836
|
+
task_registry: countBy("task_registry"),
|
|
837
|
+
},
|
|
838
|
+
previewed_at: new Date().toISOString(),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
purgeProject(scopeUserId, scopeAgentId, input) {
|
|
842
|
+
const projectId = String(input.project_id || "").trim();
|
|
843
|
+
if (!projectId)
|
|
844
|
+
throw new Error("project_id is required");
|
|
845
|
+
if (input.confirm !== true) {
|
|
846
|
+
throw new Error("project.purge requires explicit confirm=true");
|
|
847
|
+
}
|
|
848
|
+
const preview = this.purgePreviewProject(scopeUserId, scopeAgentId, { project_id: projectId });
|
|
849
|
+
if (!preview.purge_guard.allowed) {
|
|
850
|
+
throw new Error(preview.purge_guard.reason);
|
|
851
|
+
}
|
|
852
|
+
const now = new Date().toISOString();
|
|
853
|
+
const reason = input.reason == null ? null : String(input.reason).trim() || null;
|
|
854
|
+
const deleted = {
|
|
855
|
+
project_row: 1,
|
|
856
|
+
aliases: preview.affected.aliases,
|
|
857
|
+
tracker_mappings: preview.affected.tracker_mappings,
|
|
858
|
+
registration_state: preview.affected.registration_state,
|
|
859
|
+
index_runs: preview.affected.index_runs,
|
|
860
|
+
watch_state: preview.affected.watch_state,
|
|
861
|
+
file_index_state: preview.affected.file_index_state,
|
|
862
|
+
chunk_registry: preview.affected.chunk_registry,
|
|
863
|
+
symbol_registry: preview.affected.symbol_registry,
|
|
864
|
+
task_registry: preview.affected.task_registry,
|
|
865
|
+
};
|
|
866
|
+
this.db.exec("BEGIN");
|
|
867
|
+
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);
|
|
888
|
+
this.db.exec("COMMIT");
|
|
889
|
+
}
|
|
890
|
+
catch (error) {
|
|
891
|
+
this.db.exec("ROLLBACK");
|
|
892
|
+
throw error;
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
project_id: projectId,
|
|
896
|
+
lifecycle_status: "purged",
|
|
897
|
+
purged_at: now,
|
|
898
|
+
reason,
|
|
899
|
+
deleted,
|
|
900
|
+
searchable: false,
|
|
901
|
+
recoverable: false,
|
|
902
|
+
audit: {
|
|
903
|
+
confirm_required: true,
|
|
904
|
+
allowed_from_lifecycle_status: "disabled",
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
}
|
|
632
908
|
reindexProjectByDiff(scopeUserId, scopeAgentId, input) {
|
|
633
909
|
const now = new Date().toISOString();
|
|
634
910
|
const runId = randomUUID();
|
|
@@ -662,9 +938,12 @@ export class SlotDB {
|
|
|
662
938
|
unchanged.push(relativePath);
|
|
663
939
|
}
|
|
664
940
|
const deleted = [];
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
941
|
+
const treatAsFullSnapshot = input.full_snapshot === true || triggerType === "bootstrap";
|
|
942
|
+
if (treatAsFullSnapshot) {
|
|
943
|
+
for (const prevPath of Object.keys(previousSnapshot)) {
|
|
944
|
+
if (!currentSnapshot.has(prevPath))
|
|
945
|
+
deleted.push(prevPath);
|
|
946
|
+
}
|
|
668
947
|
}
|
|
669
948
|
this.insertIndexRun(scopeUserId, scopeAgentId, {
|
|
670
949
|
run_id: runId,
|
|
@@ -680,12 +959,14 @@ export class SlotDB {
|
|
|
680
959
|
const nowIso = new Date().toISOString();
|
|
681
960
|
for (const relativePath of changed) {
|
|
682
961
|
const item = (input.paths || []).find((p) => this.normalizeRelativePath(p.relative_path) === relativePath);
|
|
962
|
+
const fileId = this.makeScopedId(input.project_id, relativePath);
|
|
963
|
+
const language = item?.language || null;
|
|
683
964
|
this.upsertFileIndexState(scopeUserId, scopeAgentId, {
|
|
684
|
-
file_id:
|
|
965
|
+
file_id: fileId,
|
|
685
966
|
project_id: input.project_id,
|
|
686
967
|
relative_path: relativePath,
|
|
687
968
|
module: item?.module || null,
|
|
688
|
-
language
|
|
969
|
+
language,
|
|
689
970
|
checksum: currentSnapshot.get(relativePath) || "__missing__",
|
|
690
971
|
last_commit_sha: sourceRev,
|
|
691
972
|
index_state: "indexed",
|
|
@@ -693,9 +974,63 @@ export class SlotDB {
|
|
|
693
974
|
tombstone_at: null,
|
|
694
975
|
indexed_at: nowIso,
|
|
695
976
|
});
|
|
977
|
+
this.markProjectChunksByFileDeleted(scopeUserId, scopeAgentId, input.project_id, relativePath, nowIso);
|
|
978
|
+
this.markProjectSymbolsByFileDeleted(scopeUserId, scopeAgentId, input.project_id, relativePath, nowIso);
|
|
979
|
+
const content = String(item?.content || "");
|
|
980
|
+
if (content.trim()) {
|
|
981
|
+
const blocks = extractSemanticBlocks({ relativePath, content });
|
|
982
|
+
const chunks = buildChunkArtifacts(input.project_id, fileId, relativePath, blocks);
|
|
983
|
+
for (const chunk of chunks) {
|
|
984
|
+
this.upsertChunkRegistry(scopeUserId, scopeAgentId, {
|
|
985
|
+
chunk_id: chunk.chunk_id,
|
|
986
|
+
project_id: input.project_id,
|
|
987
|
+
file_id: chunk.file_id,
|
|
988
|
+
relative_path: chunk.relative_path,
|
|
989
|
+
chunk_kind: chunk.chunk_kind,
|
|
990
|
+
symbol_id: chunk.symbol_id,
|
|
991
|
+
task_id: null,
|
|
992
|
+
checksum: chunk.checksum,
|
|
993
|
+
qdrant_point_id: null,
|
|
994
|
+
index_state: "indexed",
|
|
995
|
+
active: 1,
|
|
996
|
+
tombstone_at: null,
|
|
997
|
+
indexed_at: nowIso,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
for (const block of blocks) {
|
|
1001
|
+
if (!block.symbol_name || !["function", "class", "method", "tool"].includes(block.kind))
|
|
1002
|
+
continue;
|
|
1003
|
+
const symbolFqn = block.semantic_path || `${block.kind}:${block.symbol_name}`;
|
|
1004
|
+
this.upsertSymbolRegistry(scopeUserId, scopeAgentId, {
|
|
1005
|
+
symbol_id: buildSymbolId(input.project_id, relativePath, symbolFqn),
|
|
1006
|
+
project_id: input.project_id,
|
|
1007
|
+
relative_path: relativePath,
|
|
1008
|
+
module: item?.module || null,
|
|
1009
|
+
language: language || "text",
|
|
1010
|
+
symbol_name: block.symbol_name,
|
|
1011
|
+
symbol_fqn: symbolFqn,
|
|
1012
|
+
symbol_kind: block.kind,
|
|
1013
|
+
signature_hash: null,
|
|
1014
|
+
index_state: "indexed",
|
|
1015
|
+
active: 1,
|
|
1016
|
+
tombstone_at: null,
|
|
1017
|
+
indexed_at: nowIso,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
populateUniversalCodeGraphForFile(this.graph, scopeUserId, scopeAgentId, {
|
|
1021
|
+
projectId: input.project_id,
|
|
1022
|
+
relativePath,
|
|
1023
|
+
module: item?.module || null,
|
|
1024
|
+
language: language || "text",
|
|
1025
|
+
content,
|
|
1026
|
+
blocks,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
696
1029
|
}
|
|
697
1030
|
for (const relativePath of deleted) {
|
|
698
1031
|
this.markFileIndexStateDeleted(scopeUserId, scopeAgentId, input.project_id, relativePath, nowIso);
|
|
1032
|
+
this.markProjectChunksByFileDeleted(scopeUserId, scopeAgentId, input.project_id, relativePath, nowIso);
|
|
1033
|
+
this.markProjectSymbolsByFileDeleted(scopeUserId, scopeAgentId, input.project_id, relativePath, nowIso);
|
|
699
1034
|
}
|
|
700
1035
|
const checksumSnapshotRecord = Object.fromEntries(currentSnapshot.entries());
|
|
701
1036
|
this.upsertProjectIndexWatchState(scopeUserId, scopeAgentId, {
|
|
@@ -704,7 +1039,14 @@ export class SlotDB {
|
|
|
704
1039
|
last_checksum_snapshot: checksumSnapshotRecord,
|
|
705
1040
|
updated_at: nowIso,
|
|
706
1041
|
});
|
|
1042
|
+
this.db.prepare(`UPDATE projects
|
|
1043
|
+
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);
|
|
707
1045
|
this.finishIndexRun(scopeUserId, scopeAgentId, runId, "indexed", null, nowIso);
|
|
1046
|
+
this.db.prepare(`UPDATE projects
|
|
1047
|
+
SET lifecycle_status = 'active', updated_at = ?
|
|
1048
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
|
|
1049
|
+
AND lifecycle_status = 'deindexed'`).run(nowIso, scopeUserId, scopeAgentId, input.project_id);
|
|
708
1050
|
return {
|
|
709
1051
|
run_id: runId,
|
|
710
1052
|
project_id: input.project_id,
|
|
@@ -878,19 +1220,138 @@ export class SlotDB {
|
|
|
878
1220
|
if (!project) {
|
|
879
1221
|
throw new Error(`project_id '${projectId}' is not registered`);
|
|
880
1222
|
}
|
|
1223
|
+
const isSearchDisabled = ["deindexed", "detached", "disabled", "purged"].includes(project.lifecycle_status);
|
|
1224
|
+
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);
|
|
1231
|
+
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() } : {}),
|
|
1235
|
+
};
|
|
1236
|
+
const reasonByLifecycle = {
|
|
1237
|
+
deindexed: "project is deindexed; retrieval is disabled until reindex",
|
|
1238
|
+
detached: "project is detached; retrieval is disabled until project is re-attached and reindexed",
|
|
1239
|
+
disabled: "project is unregistered/disabled; retrieval is disabled until re-registration",
|
|
1240
|
+
purged: "project is purged; retrieval is disabled",
|
|
1241
|
+
};
|
|
1242
|
+
return {
|
|
1243
|
+
query,
|
|
1244
|
+
project_id: projectId,
|
|
1245
|
+
project_lifecycle_status: project.lifecycle_status,
|
|
1246
|
+
searchable: false,
|
|
1247
|
+
tombstone_summary: { files, chunks, symbols },
|
|
1248
|
+
count: 0,
|
|
1249
|
+
task_lineage_context: null,
|
|
1250
|
+
task_context_resolution: {
|
|
1251
|
+
status: Object.keys(taskContextSelector).length > 0 ? "selector_not_resolved" : "not_requested",
|
|
1252
|
+
...(Object.keys(taskContextSelector).length > 0
|
|
1253
|
+
? { reason: reasonByLifecycle[project.lifecycle_status] || "project lifecycle disables retrieval" }
|
|
1254
|
+
: {}),
|
|
1255
|
+
selector: taskContextSelector,
|
|
1256
|
+
recoverable: project.lifecycle_status !== "purged",
|
|
1257
|
+
},
|
|
1258
|
+
results: [],
|
|
1259
|
+
debug: input.debug
|
|
1260
|
+
? {
|
|
1261
|
+
query_intent: {
|
|
1262
|
+
looks_code_intent: false,
|
|
1263
|
+
looks_identifier_query: false,
|
|
1264
|
+
query_tokens: [],
|
|
1265
|
+
},
|
|
1266
|
+
candidate_counts: {
|
|
1267
|
+
file_index_state: 0,
|
|
1268
|
+
symbol_registry: 0,
|
|
1269
|
+
chunk_registry: 0,
|
|
1270
|
+
task_registry: 0,
|
|
1271
|
+
},
|
|
1272
|
+
top_candidates: {
|
|
1273
|
+
file_index_state: [],
|
|
1274
|
+
symbol_registry: [],
|
|
1275
|
+
chunk_registry: [],
|
|
1276
|
+
task_registry: [],
|
|
1277
|
+
},
|
|
1278
|
+
}
|
|
1279
|
+
: undefined,
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
881
1282
|
const limit = Math.min(Math.max(Number(input.limit || 10), 1), 50);
|
|
882
1283
|
const queryLc = query.toLowerCase();
|
|
1284
|
+
const queryTokens = Array.from(new Set(queryLc.split(/[^a-z0-9._/-]+/i).map((t) => t.trim()).filter(Boolean)));
|
|
1285
|
+
const tokenScore = (text) => {
|
|
1286
|
+
if (!queryTokens.length)
|
|
1287
|
+
return 0;
|
|
1288
|
+
const hay = text.toLowerCase();
|
|
1289
|
+
let matched = 0;
|
|
1290
|
+
for (const token of queryTokens) {
|
|
1291
|
+
if (hay.includes(token))
|
|
1292
|
+
matched += 1;
|
|
1293
|
+
}
|
|
1294
|
+
return matched / queryTokens.length;
|
|
1295
|
+
};
|
|
1296
|
+
const exactMatchScore = (candidate) => {
|
|
1297
|
+
const value = String(candidate || '').trim().toLowerCase();
|
|
1298
|
+
if (!value)
|
|
1299
|
+
return 0;
|
|
1300
|
+
if (value === queryLc)
|
|
1301
|
+
return 1;
|
|
1302
|
+
if (value.endsWith(`.${queryLc}`))
|
|
1303
|
+
return 0.92;
|
|
1304
|
+
if (value.includes(`/${queryLc}`) || value.includes(`:${queryLc}`) || value.includes(`#${queryLc}`))
|
|
1305
|
+
return 0.78;
|
|
1306
|
+
return 0;
|
|
1307
|
+
};
|
|
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('_');
|
|
1310
|
+
const looksIdentifierQuery = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(query) ||
|
|
1311
|
+
/^[a-zA-Z_][a-zA-Z0-9_.#:-]*$/.test(query) ||
|
|
1312
|
+
query.includes('::') ||
|
|
1313
|
+
query.includes('.') ||
|
|
1314
|
+
query.includes('_');
|
|
883
1315
|
const taskContextInput = input.task_context;
|
|
1316
|
+
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() } : {}),
|
|
1320
|
+
};
|
|
884
1321
|
let lineageContext = null;
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1322
|
+
let taskContextResolution = {
|
|
1323
|
+
status: "not_requested",
|
|
1324
|
+
selector: taskContextSelector,
|
|
1325
|
+
recoverable: false,
|
|
1326
|
+
};
|
|
1327
|
+
if (Object.keys(taskContextSelector).length > 0) {
|
|
1328
|
+
try {
|
|
1329
|
+
lineageContext = this.getTaskLineageContext(scopeUserId, scopeAgentId, {
|
|
1330
|
+
project_id: projectId,
|
|
1331
|
+
task_id: taskContextInput?.task_id,
|
|
1332
|
+
tracker_issue_key: taskContextInput?.tracker_issue_key,
|
|
1333
|
+
task_title: taskContextInput?.task_title,
|
|
1334
|
+
include_parent_chain: taskContextInput?.include_parent_chain,
|
|
1335
|
+
include_related: taskContextInput?.include_related,
|
|
1336
|
+
});
|
|
1337
|
+
taskContextResolution = {
|
|
1338
|
+
status: "resolved",
|
|
1339
|
+
selector: taskContextSelector,
|
|
1340
|
+
recoverable: false,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
catch (error) {
|
|
1344
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1345
|
+
if (!message.includes("task lineage focus not found for provided selector")) {
|
|
1346
|
+
throw error;
|
|
1347
|
+
}
|
|
1348
|
+
taskContextResolution = {
|
|
1349
|
+
status: "selector_not_resolved",
|
|
1350
|
+
reason: "task lineage focus not found for provided selector",
|
|
1351
|
+
selector: taskContextSelector,
|
|
1352
|
+
recoverable: true,
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
894
1355
|
}
|
|
895
1356
|
const lexicalPathPrefix = this.normalizeStringArray(input.path_prefix).map((p) => this.normalizeRelativePath(p)).filter(Boolean);
|
|
896
1357
|
const lexicalModules = new Set(this.normalizeStringArray(input.module).map((s) => s.toLowerCase()));
|
|
@@ -908,6 +1369,19 @@ export class SlotDB {
|
|
|
908
1369
|
}
|
|
909
1370
|
}
|
|
910
1371
|
const results = [];
|
|
1372
|
+
const debugEnabled = input.debug === true;
|
|
1373
|
+
const debugBuckets = {
|
|
1374
|
+
file_index_state: [],
|
|
1375
|
+
symbol_registry: [],
|
|
1376
|
+
chunk_registry: [],
|
|
1377
|
+
task_registry: [],
|
|
1378
|
+
};
|
|
1379
|
+
const pushDebug = (bucket, entry) => {
|
|
1380
|
+
if (!debugEnabled)
|
|
1381
|
+
return;
|
|
1382
|
+
debugBuckets[bucket].push(entry);
|
|
1383
|
+
};
|
|
1384
|
+
const symbolRowsById = new Map();
|
|
911
1385
|
const fileStmt = this.db.prepare(`SELECT * FROM file_index_state
|
|
912
1386
|
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
913
1387
|
const fileRows = fileStmt.all(scopeUserId, scopeAgentId, projectId);
|
|
@@ -929,12 +1403,31 @@ export class SlotDB {
|
|
|
929
1403
|
let score = 0;
|
|
930
1404
|
if (text.includes(queryLc))
|
|
931
1405
|
score += 0.55;
|
|
1406
|
+
score += tokenScore(text) * 0.25;
|
|
932
1407
|
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
933
1408
|
score += 0.35;
|
|
934
|
-
if (
|
|
1409
|
+
if (looksCodeIntent) {
|
|
1410
|
+
if (relativePath.includes('/docs/') || relativePath.startsWith('docs/') || relativePath.includes('README'))
|
|
1411
|
+
score -= 0.18;
|
|
1412
|
+
if (relativePath.startsWith('src/') || relativePath.startsWith('tests/'))
|
|
1413
|
+
score += 0.14;
|
|
1414
|
+
}
|
|
1415
|
+
else if (relativePath.includes("README") || relativePath.includes("docs/")) {
|
|
935
1416
|
score += 0.05;
|
|
936
|
-
|
|
1417
|
+
}
|
|
1418
|
+
if (looksIdentifierQuery) {
|
|
1419
|
+
score -= 0.12;
|
|
1420
|
+
}
|
|
1421
|
+
if (score <= 0.08)
|
|
937
1422
|
continue;
|
|
1423
|
+
pushDebug('file_index_state', {
|
|
1424
|
+
relative_path: relativePath,
|
|
1425
|
+
score: Number(score.toFixed(4)),
|
|
1426
|
+
module: moduleName,
|
|
1427
|
+
language,
|
|
1428
|
+
text_exact: text.includes(queryLc),
|
|
1429
|
+
token_score: Number(tokenScore(text).toFixed(4)),
|
|
1430
|
+
});
|
|
938
1431
|
results.push({
|
|
939
1432
|
source: "file_index_state",
|
|
940
1433
|
id: String(row.file_id),
|
|
@@ -956,6 +1449,7 @@ export class SlotDB {
|
|
|
956
1449
|
const symbolName = String(row.symbol_name || "");
|
|
957
1450
|
const symbolKind = String(row.symbol_kind || "");
|
|
958
1451
|
const symbolFqn = String(row.symbol_fqn || "");
|
|
1452
|
+
symbolRowsById.set(String(row.symbol_id), row);
|
|
959
1453
|
if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
|
|
960
1454
|
continue;
|
|
961
1455
|
if (lexicalModules.size > 0 && !moduleName)
|
|
@@ -970,12 +1464,38 @@ export class SlotDB {
|
|
|
970
1464
|
let score = 0;
|
|
971
1465
|
if (text.includes(queryLc))
|
|
972
1466
|
score += 0.62;
|
|
1467
|
+
score += tokenScore(text) * 0.35;
|
|
1468
|
+
score += exactMatchScore(symbolName) * 1.35;
|
|
1469
|
+
score += exactMatchScore(symbolFqn) * 1.1;
|
|
1470
|
+
if (looksIdentifierQuery) {
|
|
1471
|
+
if (symbolName.toLowerCase() === queryLc)
|
|
1472
|
+
score += 1.8;
|
|
1473
|
+
else if (symbolFqn.toLowerCase() === queryLc)
|
|
1474
|
+
score += 1.5;
|
|
1475
|
+
else if (symbolFqn.toLowerCase().endsWith(`.${queryLc}`))
|
|
1476
|
+
score += 1.1;
|
|
1477
|
+
}
|
|
973
1478
|
if (lineageContext && lineageContext.touched_symbols.includes(symbolName))
|
|
974
1479
|
score += 0.3;
|
|
975
1480
|
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
976
1481
|
score += 0.12;
|
|
977
|
-
if (
|
|
1482
|
+
if (looksCodeIntent && (relativePath.startsWith('src/') || relativePath.startsWith('tests/')))
|
|
1483
|
+
score += 0.08;
|
|
1484
|
+
if (looksIdentifierQuery)
|
|
1485
|
+
score += 0.18;
|
|
1486
|
+
if (score <= 0.08)
|
|
978
1487
|
continue;
|
|
1488
|
+
pushDebug('symbol_registry', {
|
|
1489
|
+
relative_path: relativePath,
|
|
1490
|
+
symbol_name: symbolName,
|
|
1491
|
+
symbol_fqn: symbolFqn,
|
|
1492
|
+
symbol_kind: symbolKind,
|
|
1493
|
+
score: Number(score.toFixed(4)),
|
|
1494
|
+
exact_symbol: symbolName.toLowerCase() === queryLc,
|
|
1495
|
+
exact_fqn: symbolFqn.toLowerCase() === queryLc,
|
|
1496
|
+
suffix_fqn: symbolFqn.toLowerCase().endsWith(`.${queryLc}`),
|
|
1497
|
+
token_score: Number(tokenScore(text).toFixed(4)),
|
|
1498
|
+
});
|
|
979
1499
|
results.push({
|
|
980
1500
|
source: "symbol_registry",
|
|
981
1501
|
id: String(row.symbol_id),
|
|
@@ -989,6 +1509,62 @@ export class SlotDB {
|
|
|
989
1509
|
snippet: `symbol ${symbolName} (${symbolKind}) in ${relativePath}`,
|
|
990
1510
|
});
|
|
991
1511
|
}
|
|
1512
|
+
const chunkStmt = this.db.prepare(`SELECT * FROM chunk_registry
|
|
1513
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
1514
|
+
const chunkRows = chunkStmt.all(scopeUserId, scopeAgentId, projectId);
|
|
1515
|
+
for (const row of chunkRows) {
|
|
1516
|
+
const relativePath = String(row.relative_path || "");
|
|
1517
|
+
const chunkKind = String(row.chunk_kind || "");
|
|
1518
|
+
const symbolId = row.symbol_id ? String(row.symbol_id) : null;
|
|
1519
|
+
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)))
|
|
1523
|
+
continue;
|
|
1524
|
+
const text = `${relativePath} ${chunkKind} ${symbolId || ""} ${symbolName} ${symbolFqn}`.toLowerCase();
|
|
1525
|
+
let score = 0;
|
|
1526
|
+
if (text.includes(queryLc))
|
|
1527
|
+
score += 0.6;
|
|
1528
|
+
score += tokenScore(text) * 0.4;
|
|
1529
|
+
score += exactMatchScore(symbolName) * 0.9;
|
|
1530
|
+
score += exactMatchScore(symbolFqn) * 0.7;
|
|
1531
|
+
if (looksIdentifierQuery) {
|
|
1532
|
+
if (symbolName.toLowerCase() === queryLc)
|
|
1533
|
+
score += 1.0;
|
|
1534
|
+
else if (symbolFqn.toLowerCase() === queryLc)
|
|
1535
|
+
score += 0.85;
|
|
1536
|
+
}
|
|
1537
|
+
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
1538
|
+
score += 0.15;
|
|
1539
|
+
if (looksCodeIntent) {
|
|
1540
|
+
if (relativePath.includes('/docs/') || relativePath.startsWith('docs/') || relativePath.includes('README'))
|
|
1541
|
+
score -= 0.14;
|
|
1542
|
+
if (relativePath.startsWith('src/') || relativePath.startsWith('tests/'))
|
|
1543
|
+
score += 0.1;
|
|
1544
|
+
}
|
|
1545
|
+
if (looksIdentifierQuery)
|
|
1546
|
+
score += 0.12;
|
|
1547
|
+
if (score <= 0.08)
|
|
1548
|
+
continue;
|
|
1549
|
+
pushDebug('chunk_registry', {
|
|
1550
|
+
relative_path: relativePath,
|
|
1551
|
+
chunk_kind: chunkKind,
|
|
1552
|
+
symbol_name: symbolName || null,
|
|
1553
|
+
symbol_fqn: symbolFqn || null,
|
|
1554
|
+
score: Number(score.toFixed(4)),
|
|
1555
|
+
exact_symbol: symbolName ? symbolName.toLowerCase() === queryLc : false,
|
|
1556
|
+
token_score: Number(tokenScore(text).toFixed(4)),
|
|
1557
|
+
});
|
|
1558
|
+
results.push({
|
|
1559
|
+
source: "chunk_registry",
|
|
1560
|
+
id: String(row.chunk_id),
|
|
1561
|
+
score,
|
|
1562
|
+
project_id: projectId,
|
|
1563
|
+
relative_path: relativePath,
|
|
1564
|
+
symbol_name: symbolName || undefined,
|
|
1565
|
+
snippet: symbolName ? `chunk ${chunkKind} for symbol ${symbolName} in ${relativePath}` : `chunk ${chunkKind} in ${relativePath}`,
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
992
1568
|
const taskStmt = this.db.prepare(`SELECT * FROM task_registry
|
|
993
1569
|
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?`);
|
|
994
1570
|
const taskRows = taskStmt.all(scopeUserId, scopeAgentId, projectId);
|
|
@@ -1013,6 +1589,7 @@ export class SlotDB {
|
|
|
1013
1589
|
let score = 0;
|
|
1014
1590
|
if (text.includes(queryLc))
|
|
1015
1591
|
score += 0.58;
|
|
1592
|
+
score += tokenScore(text) * 0.25;
|
|
1016
1593
|
if (lexicalTaskIds.has(task.task_id))
|
|
1017
1594
|
score += 0.28;
|
|
1018
1595
|
if (taskIssueKey && lexicalIssueKeys.has(taskIssueKey))
|
|
@@ -1025,8 +1602,15 @@ export class SlotDB {
|
|
|
1025
1602
|
if (lineageContext.related_tasks.some((t) => t.task_id === task.task_id))
|
|
1026
1603
|
score += 0.2;
|
|
1027
1604
|
}
|
|
1028
|
-
if (score <= 0)
|
|
1605
|
+
if (score <= 0.08)
|
|
1029
1606
|
continue;
|
|
1607
|
+
pushDebug('task_registry', {
|
|
1608
|
+
task_id: task.task_id,
|
|
1609
|
+
tracker_issue_key: task.tracker_issue_key,
|
|
1610
|
+
task_title: task.task_title,
|
|
1611
|
+
score: Number(score.toFixed(4)),
|
|
1612
|
+
token_score: Number(tokenScore(text).toFixed(4)),
|
|
1613
|
+
});
|
|
1030
1614
|
results.push({
|
|
1031
1615
|
source: "task_registry",
|
|
1032
1616
|
id: task.task_id,
|
|
@@ -1045,9 +1629,208 @@ export class SlotDB {
|
|
|
1045
1629
|
return {
|
|
1046
1630
|
query,
|
|
1047
1631
|
project_id: projectId,
|
|
1632
|
+
project_lifecycle_status: project.lifecycle_status,
|
|
1633
|
+
searchable: true,
|
|
1048
1634
|
count: ranked.length,
|
|
1049
1635
|
task_lineage_context: lineageContext,
|
|
1636
|
+
task_context_resolution: taskContextResolution,
|
|
1050
1637
|
results: ranked,
|
|
1638
|
+
debug: debugEnabled
|
|
1639
|
+
? {
|
|
1640
|
+
query_intent: {
|
|
1641
|
+
looks_code_intent: looksCodeIntent,
|
|
1642
|
+
looks_identifier_query: looksIdentifierQuery,
|
|
1643
|
+
query_tokens: queryTokens,
|
|
1644
|
+
},
|
|
1645
|
+
candidate_counts: {
|
|
1646
|
+
file_index_state: debugBuckets.file_index_state.length,
|
|
1647
|
+
symbol_registry: debugBuckets.symbol_registry.length,
|
|
1648
|
+
chunk_registry: debugBuckets.chunk_registry.length,
|
|
1649
|
+
task_registry: debugBuckets.task_registry.length,
|
|
1650
|
+
},
|
|
1651
|
+
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),
|
|
1656
|
+
},
|
|
1657
|
+
}
|
|
1658
|
+
: undefined,
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
queryProjectChangeOverlay(scopeUserId, scopeAgentId, input) {
|
|
1662
|
+
let lineage = null;
|
|
1663
|
+
const taskIdSelector = String(input.task_id || "").trim();
|
|
1664
|
+
const trackerSelector = String(input.tracker_issue_key || "").trim();
|
|
1665
|
+
const taskTitleSelector = String(input.task_title || "").trim();
|
|
1666
|
+
const selector = {
|
|
1667
|
+
...(taskIdSelector ? { task_id: taskIdSelector } : {}),
|
|
1668
|
+
...(trackerSelector ? { tracker_issue_key: trackerSelector } : {}),
|
|
1669
|
+
...(taskTitleSelector ? { task_title: taskTitleSelector } : {}),
|
|
1670
|
+
};
|
|
1671
|
+
try {
|
|
1672
|
+
lineage = this.getTaskLineageContext(scopeUserId, scopeAgentId, {
|
|
1673
|
+
project_id: input.project_id,
|
|
1674
|
+
task_id: input.task_id,
|
|
1675
|
+
tracker_issue_key: input.tracker_issue_key,
|
|
1676
|
+
task_title: input.task_title,
|
|
1677
|
+
include_related: input.include_related,
|
|
1678
|
+
include_parent_chain: input.include_parent_chain,
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
catch (error) {
|
|
1682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1683
|
+
if (!message.includes("task lineage focus not found for provided selector")) {
|
|
1684
|
+
throw error;
|
|
1685
|
+
}
|
|
1686
|
+
const unresolvedTaskId = taskIdSelector || `unresolved:${trackerSelector || taskTitleSelector || "selector"}`;
|
|
1687
|
+
const unresolvedTitle = taskTitleSelector || "Unresolved task lineage selector";
|
|
1688
|
+
return {
|
|
1689
|
+
status: "selector_not_resolved",
|
|
1690
|
+
reason: "task lineage focus not found for provided selector",
|
|
1691
|
+
selector,
|
|
1692
|
+
recoverable: true,
|
|
1693
|
+
project_id: input.project_id,
|
|
1694
|
+
focus: {
|
|
1695
|
+
task_id: unresolvedTaskId,
|
|
1696
|
+
task_title: unresolvedTitle,
|
|
1697
|
+
tracker_issue_key: trackerSelector || null,
|
|
1698
|
+
},
|
|
1699
|
+
changed_files: [],
|
|
1700
|
+
related_symbols: [],
|
|
1701
|
+
commit_refs: [],
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
const changedFiles = this.uniqueSorted((lineage.touched_files || []).map((p) => this.normalizeRelativePath(p)).filter(Boolean));
|
|
1705
|
+
const relatedSymbolsMap = new Map();
|
|
1706
|
+
for (const symbolName of lineage.touched_symbols || []) {
|
|
1707
|
+
const normalized = String(symbolName || "").trim();
|
|
1708
|
+
if (!normalized)
|
|
1709
|
+
continue;
|
|
1710
|
+
const key = `task_registry:${normalized}`;
|
|
1711
|
+
if (!relatedSymbolsMap.has(key)) {
|
|
1712
|
+
relatedSymbolsMap.set(key, {
|
|
1713
|
+
symbol_name: normalized,
|
|
1714
|
+
source: "task_registry",
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (changedFiles.length > 0) {
|
|
1719
|
+
const placeholders = changedFiles.map(() => "?").join(",");
|
|
1720
|
+
const stmt = this.db.prepare(`SELECT symbol_name, symbol_kind, symbol_fqn, relative_path
|
|
1721
|
+
FROM symbol_registry
|
|
1722
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1
|
|
1723
|
+
AND relative_path IN (${placeholders})
|
|
1724
|
+
ORDER BY indexed_at DESC, symbol_name ASC`);
|
|
1725
|
+
const rows = stmt.all(scopeUserId, scopeAgentId, input.project_id, ...changedFiles);
|
|
1726
|
+
for (const row of rows) {
|
|
1727
|
+
const symbolName = String(row.symbol_name || "").trim();
|
|
1728
|
+
if (!symbolName)
|
|
1729
|
+
continue;
|
|
1730
|
+
const relPath = row.relative_path ? String(row.relative_path) : undefined;
|
|
1731
|
+
const symbolFqn = row.symbol_fqn ? String(row.symbol_fqn) : undefined;
|
|
1732
|
+
const key = `symbol_registry:${symbolName}:${symbolFqn || ""}:${relPath || ""}`;
|
|
1733
|
+
if (!relatedSymbolsMap.has(key)) {
|
|
1734
|
+
relatedSymbolsMap.set(key, {
|
|
1735
|
+
symbol_name: symbolName,
|
|
1736
|
+
symbol_kind: row.symbol_kind ? String(row.symbol_kind) : undefined,
|
|
1737
|
+
symbol_fqn: symbolFqn,
|
|
1738
|
+
relative_path: relPath,
|
|
1739
|
+
source: "symbol_registry",
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return {
|
|
1745
|
+
status: "ok",
|
|
1746
|
+
selector,
|
|
1747
|
+
recoverable: false,
|
|
1748
|
+
project_id: input.project_id,
|
|
1749
|
+
focus: lineage.focus,
|
|
1750
|
+
changed_files: changedFiles,
|
|
1751
|
+
related_symbols: Array.from(relatedSymbolsMap.values()),
|
|
1752
|
+
commit_refs: this.uniqueSorted(lineage.commit_refs || []),
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
getProjectFeaturePackProjectOnboardingIndexingSnapshot(scopeUserId, scopeAgentId, projectId) {
|
|
1756
|
+
const project = this.getProjectById(scopeUserId, scopeAgentId, projectId);
|
|
1757
|
+
if (!project) {
|
|
1758
|
+
throw new Error(`project_id '${projectId}' is not registered`);
|
|
1759
|
+
}
|
|
1760
|
+
const aliases = this.listProjects(scopeUserId, scopeAgentId)
|
|
1761
|
+
.find((row) => row.project.project_id === projectId)?.aliases || [];
|
|
1762
|
+
const registration = this.getProjectRegistrationState(scopeUserId, scopeAgentId, projectId);
|
|
1763
|
+
const trackerStmt = this.db.prepare(`SELECT * FROM project_tracker_mappings
|
|
1764
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
|
|
1765
|
+
ORDER BY updated_at DESC`);
|
|
1766
|
+
const trackerMappings = trackerStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
|
|
1767
|
+
id: String(row.id),
|
|
1768
|
+
project_id: String(row.project_id),
|
|
1769
|
+
scope_user_id: String(row.scope_user_id),
|
|
1770
|
+
scope_agent_id: String(row.scope_agent_id),
|
|
1771
|
+
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,
|
|
1775
|
+
board_key: row.board_key ? String(row.board_key) : null,
|
|
1776
|
+
active_version: row.active_version ? String(row.active_version) : null,
|
|
1777
|
+
external_project_url: row.external_project_url ? String(row.external_project_url) : null,
|
|
1778
|
+
created_at: String(row.created_at),
|
|
1779
|
+
updated_at: String(row.updated_at),
|
|
1780
|
+
}));
|
|
1781
|
+
const fileStmt = this.db.prepare(`SELECT relative_path, module, language
|
|
1782
|
+
FROM file_index_state
|
|
1783
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1
|
|
1784
|
+
ORDER BY indexed_at DESC, relative_path ASC
|
|
1785
|
+
LIMIT 12`);
|
|
1786
|
+
const recentFiles = fileStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
|
|
1787
|
+
relative_path: String(row.relative_path),
|
|
1788
|
+
module: row.module ? String(row.module) : null,
|
|
1789
|
+
language: row.language ? String(row.language) : null,
|
|
1790
|
+
}));
|
|
1791
|
+
const symbolStmt = this.db.prepare(`SELECT symbol_name, symbol_kind, symbol_fqn, relative_path
|
|
1792
|
+
FROM symbol_registry
|
|
1793
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1
|
|
1794
|
+
ORDER BY indexed_at DESC, symbol_name ASC
|
|
1795
|
+
LIMIT 16`);
|
|
1796
|
+
const recentSymbols = symbolStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
|
|
1797
|
+
symbol_name: String(row.symbol_name),
|
|
1798
|
+
symbol_kind: String(row.symbol_kind),
|
|
1799
|
+
symbol_fqn: String(row.symbol_fqn),
|
|
1800
|
+
relative_path: String(row.relative_path),
|
|
1801
|
+
}));
|
|
1802
|
+
const taskStmt = this.db.prepare(`SELECT task_id, task_title, tracker_issue_key, task_status
|
|
1803
|
+
FROM task_registry
|
|
1804
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
|
|
1805
|
+
ORDER BY updated_at DESC
|
|
1806
|
+
LIMIT 12`);
|
|
1807
|
+
const recentTasks = taskStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
|
|
1808
|
+
task_id: String(row.task_id),
|
|
1809
|
+
task_title: String(row.task_title),
|
|
1810
|
+
tracker_issue_key: row.tracker_issue_key ? String(row.tracker_issue_key) : null,
|
|
1811
|
+
task_status: row.task_status ? String(row.task_status) : null,
|
|
1812
|
+
}));
|
|
1813
|
+
const runStmt = this.db.prepare(`SELECT run_id, trigger_type, state, started_at, finished_at
|
|
1814
|
+
FROM index_runs
|
|
1815
|
+
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ?
|
|
1816
|
+
ORDER BY started_at DESC
|
|
1817
|
+
LIMIT 8`);
|
|
1818
|
+
const recentIndexRuns = runStmt.all(scopeUserId, scopeAgentId, projectId).map((row) => ({
|
|
1819
|
+
run_id: String(row.run_id),
|
|
1820
|
+
trigger_type: String(row.trigger_type),
|
|
1821
|
+
state: String(row.state),
|
|
1822
|
+
started_at: String(row.started_at),
|
|
1823
|
+
finished_at: row.finished_at ? String(row.finished_at) : null,
|
|
1824
|
+
}));
|
|
1825
|
+
return {
|
|
1826
|
+
project,
|
|
1827
|
+
aliases,
|
|
1828
|
+
registration,
|
|
1829
|
+
tracker_mappings: trackerMappings,
|
|
1830
|
+
recent_files: recentFiles,
|
|
1831
|
+
recent_symbols: recentSymbols,
|
|
1832
|
+
recent_tasks: recentTasks,
|
|
1833
|
+
recent_index_runs: recentIndexRuns,
|
|
1051
1834
|
};
|
|
1052
1835
|
}
|
|
1053
1836
|
runLegacyCompatibilityBackfill(scopeUserId, scopeAgentId, input = {}) {
|
|
@@ -1252,6 +2035,45 @@ export class SlotDB {
|
|
|
1252
2035
|
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND relative_path = ?`);
|
|
1253
2036
|
stmt.run(tombstoneAt, tombstoneAt, scopeUserId, scopeAgentId, projectId, relativePath);
|
|
1254
2037
|
}
|
|
2038
|
+
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);
|
|
2040
|
+
if (existing) {
|
|
2041
|
+
this.db.prepare(`UPDATE chunk_registry
|
|
2042
|
+
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);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
this.db.prepare(`INSERT INTO chunk_registry (
|
|
2047
|
+
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);
|
|
2049
|
+
}
|
|
2050
|
+
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);
|
|
2052
|
+
if (existing) {
|
|
2053
|
+
this.db.prepare(`UPDATE symbol_registry
|
|
2054
|
+
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);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
this.db.prepare(`INSERT INTO symbol_registry (
|
|
2059
|
+
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);
|
|
2061
|
+
}
|
|
2062
|
+
markProjectChunksByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
|
|
2063
|
+
this.db.prepare(`UPDATE chunk_registry
|
|
2064
|
+
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);
|
|
2066
|
+
}
|
|
2067
|
+
markProjectSymbolsByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
|
|
2068
|
+
this.db.prepare(`UPDATE symbol_registry
|
|
2069
|
+
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);
|
|
2071
|
+
}
|
|
2072
|
+
markProjectFileDeletedForEvent(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt) {
|
|
2073
|
+
this.markFileIndexStateDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt);
|
|
2074
|
+
this.markProjectChunksByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt);
|
|
2075
|
+
this.markProjectSymbolsByFileDeleted(scopeUserId, scopeAgentId, projectId, relativePath, tombstoneAt);
|
|
2076
|
+
}
|
|
1255
2077
|
upsertProjectIndexWatchState(scopeUserId, scopeAgentId, input) {
|
|
1256
2078
|
const existing = this.getProjectIndexWatchState(scopeUserId, scopeAgentId, input.project_id);
|
|
1257
2079
|
const checksumJson = JSON.stringify(input.last_checksum_snapshot || {});
|