@mrc2204/agent-smart-memo 5.1.0 → 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 +1 -1
- package/dist/core/ingest/contracts.d.ts.map +1 -1
- package/dist/core/ingest/ingest-pipeline.js +1 -1
- package/dist/core/ingest/ingest-pipeline.js.map +1 -1
- package/dist/core/ingest/semantic-block-extractor.d.ts.map +1 -1
- package/dist/core/ingest/semantic-block-extractor.js +36 -0
- package/dist/core/ingest/semantic-block-extractor.js.map +1 -1
- package/dist/core/usecases/default-memory-usecase-port.d.ts +35 -0
- package/dist/core/usecases/default-memory-usecase-port.d.ts.map +1 -1
- package/dist/core/usecases/default-memory-usecase-port.js +1578 -19
- 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 +227 -1
- package/dist/db/slot-db.d.ts.map +1 -1
- package/dist/db/slot-db.js +700 -13
- 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 +476 -1
- 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
|
@@ -13,6 +13,7 @@ import { mkdirSync, existsSync } from "node:fs";
|
|
|
13
13
|
import { buildChunkArtifacts } from "../core/ingest/ingest-pipeline.js";
|
|
14
14
|
import { extractSemanticBlocks } from "../core/ingest/semantic-block-extractor.js";
|
|
15
15
|
import { buildSymbolId } from "../core/ingest/ids.js";
|
|
16
|
+
import { populateUniversalCodeGraphForFile } from "../core/graph/code-graph-populator.js";
|
|
16
17
|
import { GraphDB } from "./graph-db.js";
|
|
17
18
|
import { getSlotTTL } from "../shared/memory-config.js";
|
|
18
19
|
import { resolveLegacyStateDirInput, resolveSlotDbDir } from "../shared/slotdb-path.js";
|
|
@@ -632,6 +633,278 @@ export class SlotDB {
|
|
|
632
633
|
updated_at: row.updated_at,
|
|
633
634
|
};
|
|
634
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
|
+
}
|
|
635
908
|
reindexProjectByDiff(scopeUserId, scopeAgentId, input) {
|
|
636
909
|
const now = new Date().toISOString();
|
|
637
910
|
const runId = randomUUID();
|
|
@@ -725,7 +998,7 @@ export class SlotDB {
|
|
|
725
998
|
});
|
|
726
999
|
}
|
|
727
1000
|
for (const block of blocks) {
|
|
728
|
-
if (!block.symbol_name || !["function", "class", "method"].includes(block.kind))
|
|
1001
|
+
if (!block.symbol_name || !["function", "class", "method", "tool"].includes(block.kind))
|
|
729
1002
|
continue;
|
|
730
1003
|
const symbolFqn = block.semantic_path || `${block.kind}:${block.symbol_name}`;
|
|
731
1004
|
this.upsertSymbolRegistry(scopeUserId, scopeAgentId, {
|
|
@@ -744,6 +1017,14 @@ export class SlotDB {
|
|
|
744
1017
|
indexed_at: nowIso,
|
|
745
1018
|
});
|
|
746
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
|
+
});
|
|
747
1028
|
}
|
|
748
1029
|
}
|
|
749
1030
|
for (const relativePath of deleted) {
|
|
@@ -758,7 +1039,14 @@ export class SlotDB {
|
|
|
758
1039
|
last_checksum_snapshot: checksumSnapshotRecord,
|
|
759
1040
|
updated_at: nowIso,
|
|
760
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);
|
|
761
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);
|
|
762
1050
|
return {
|
|
763
1051
|
run_id: runId,
|
|
764
1052
|
project_id: input.project_id,
|
|
@@ -932,6 +1220,65 @@ export class SlotDB {
|
|
|
932
1220
|
if (!project) {
|
|
933
1221
|
throw new Error(`project_id '${projectId}' is not registered`);
|
|
934
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
|
+
}
|
|
935
1282
|
const limit = Math.min(Math.max(Number(input.limit || 10), 1), 50);
|
|
936
1283
|
const queryLc = query.toLowerCase();
|
|
937
1284
|
const queryTokens = Array.from(new Set(queryLc.split(/[^a-z0-9._/-]+/i).map((t) => t.trim()).filter(Boolean)));
|
|
@@ -946,17 +1293,65 @@ export class SlotDB {
|
|
|
946
1293
|
}
|
|
947
1294
|
return matched / queryTokens.length;
|
|
948
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('_');
|
|
949
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
|
+
};
|
|
950
1321
|
let lineageContext = null;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
+
}
|
|
960
1355
|
}
|
|
961
1356
|
const lexicalPathPrefix = this.normalizeStringArray(input.path_prefix).map((p) => this.normalizeRelativePath(p)).filter(Boolean);
|
|
962
1357
|
const lexicalModules = new Set(this.normalizeStringArray(input.module).map((s) => s.toLowerCase()));
|
|
@@ -974,6 +1369,19 @@ export class SlotDB {
|
|
|
974
1369
|
}
|
|
975
1370
|
}
|
|
976
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();
|
|
977
1385
|
const fileStmt = this.db.prepare(`SELECT * FROM file_index_state
|
|
978
1386
|
WHERE scope_user_id = ? AND scope_agent_id = ? AND project_id = ? AND active = 1`);
|
|
979
1387
|
const fileRows = fileStmt.all(scopeUserId, scopeAgentId, projectId);
|
|
@@ -998,10 +1406,28 @@ export class SlotDB {
|
|
|
998
1406
|
score += tokenScore(text) * 0.25;
|
|
999
1407
|
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
1000
1408
|
score += 0.35;
|
|
1001
|
-
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/")) {
|
|
1002
1416
|
score += 0.05;
|
|
1417
|
+
}
|
|
1418
|
+
if (looksIdentifierQuery) {
|
|
1419
|
+
score -= 0.12;
|
|
1420
|
+
}
|
|
1003
1421
|
if (score <= 0.08)
|
|
1004
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
|
+
});
|
|
1005
1431
|
results.push({
|
|
1006
1432
|
source: "file_index_state",
|
|
1007
1433
|
id: String(row.file_id),
|
|
@@ -1023,6 +1449,7 @@ export class SlotDB {
|
|
|
1023
1449
|
const symbolName = String(row.symbol_name || "");
|
|
1024
1450
|
const symbolKind = String(row.symbol_kind || "");
|
|
1025
1451
|
const symbolFqn = String(row.symbol_fqn || "");
|
|
1452
|
+
symbolRowsById.set(String(row.symbol_id), row);
|
|
1026
1453
|
if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
|
|
1027
1454
|
continue;
|
|
1028
1455
|
if (lexicalModules.size > 0 && !moduleName)
|
|
@@ -1038,12 +1465,37 @@ export class SlotDB {
|
|
|
1038
1465
|
if (text.includes(queryLc))
|
|
1039
1466
|
score += 0.62;
|
|
1040
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
|
+
}
|
|
1041
1478
|
if (lineageContext && lineageContext.touched_symbols.includes(symbolName))
|
|
1042
1479
|
score += 0.3;
|
|
1043
1480
|
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
1044
1481
|
score += 0.12;
|
|
1482
|
+
if (looksCodeIntent && (relativePath.startsWith('src/') || relativePath.startsWith('tests/')))
|
|
1483
|
+
score += 0.08;
|
|
1484
|
+
if (looksIdentifierQuery)
|
|
1485
|
+
score += 0.18;
|
|
1045
1486
|
if (score <= 0.08)
|
|
1046
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
|
+
});
|
|
1047
1499
|
results.push({
|
|
1048
1500
|
source: "symbol_registry",
|
|
1049
1501
|
id: String(row.symbol_id),
|
|
@@ -1064,24 +1516,53 @@ export class SlotDB {
|
|
|
1064
1516
|
const relativePath = String(row.relative_path || "");
|
|
1065
1517
|
const chunkKind = String(row.chunk_kind || "");
|
|
1066
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 || '') : '';
|
|
1067
1522
|
if (lexicalPathPrefix.length > 0 && !lexicalPathPrefix.some((prefix) => relativePath.startsWith(prefix)))
|
|
1068
1523
|
continue;
|
|
1069
|
-
const text = `${relativePath} ${chunkKind} ${symbolId || ""}`.toLowerCase();
|
|
1524
|
+
const text = `${relativePath} ${chunkKind} ${symbolId || ""} ${symbolName} ${symbolFqn}`.toLowerCase();
|
|
1070
1525
|
let score = 0;
|
|
1071
1526
|
if (text.includes(queryLc))
|
|
1072
1527
|
score += 0.6;
|
|
1073
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
|
+
}
|
|
1074
1537
|
if (lineageContext && lineageContext.touched_files.includes(relativePath))
|
|
1075
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;
|
|
1076
1547
|
if (score <= 0.08)
|
|
1077
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
|
+
});
|
|
1078
1558
|
results.push({
|
|
1079
1559
|
source: "chunk_registry",
|
|
1080
1560
|
id: String(row.chunk_id),
|
|
1081
1561
|
score,
|
|
1082
1562
|
project_id: projectId,
|
|
1083
1563
|
relative_path: relativePath,
|
|
1084
|
-
|
|
1564
|
+
symbol_name: symbolName || undefined,
|
|
1565
|
+
snippet: symbolName ? `chunk ${chunkKind} for symbol ${symbolName} in ${relativePath}` : `chunk ${chunkKind} in ${relativePath}`,
|
|
1085
1566
|
});
|
|
1086
1567
|
}
|
|
1087
1568
|
const taskStmt = this.db.prepare(`SELECT * FROM task_registry
|
|
@@ -1123,6 +1604,13 @@ export class SlotDB {
|
|
|
1123
1604
|
}
|
|
1124
1605
|
if (score <= 0.08)
|
|
1125
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
|
+
});
|
|
1126
1614
|
results.push({
|
|
1127
1615
|
source: "task_registry",
|
|
1128
1616
|
id: task.task_id,
|
|
@@ -1141,9 +1629,208 @@ export class SlotDB {
|
|
|
1141
1629
|
return {
|
|
1142
1630
|
query,
|
|
1143
1631
|
project_id: projectId,
|
|
1632
|
+
project_lifecycle_status: project.lifecycle_status,
|
|
1633
|
+
searchable: true,
|
|
1144
1634
|
count: ranked.length,
|
|
1145
1635
|
task_lineage_context: lineageContext,
|
|
1636
|
+
task_context_resolution: taskContextResolution,
|
|
1146
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,
|
|
1147
1834
|
};
|
|
1148
1835
|
}
|
|
1149
1836
|
runLegacyCompatibilityBackfill(scopeUserId, scopeAgentId, input = {}) {
|