@locusai/sdk 0.5.0 → 0.5.1
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.
|
@@ -12,6 +12,6 @@ export declare class CodebaseIndexerService {
|
|
|
12
12
|
private deps;
|
|
13
13
|
private indexer;
|
|
14
14
|
constructor(deps: CodebaseIndexerServiceDeps);
|
|
15
|
-
reindex(): Promise<void>;
|
|
15
|
+
reindex(force?: boolean): Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
//# sourceMappingURL=codebase-indexer-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codebase-indexer-service.d.ts","sourceRoot":"","sources":["../../src/agent/codebase-indexer-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,sBAAsB;IAGrB,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,OAAO,CAAkB;gBAEb,IAAI,EAAE,0BAA0B;IAI9C,OAAO,
|
|
1
|
+
{"version":3,"file":"codebase-indexer-service.d.ts","sourceRoot":"","sources":["../../src/agent/codebase-indexer-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,sBAAsB;IAGrB,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,OAAO,CAAkB;gBAEb,IAAI,EAAE,0BAA0B;IAI9C,OAAO,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CA2C5C"}
|
package/dist/agent/worker.js
CHANGED
|
@@ -847,6 +847,7 @@ class ArtifactSyncer {
|
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
// src/core/indexer.ts
|
|
850
|
+
var import_node_crypto2 = require("node:crypto");
|
|
850
851
|
var import_node_fs3 = require("node:fs");
|
|
851
852
|
var import_node_path5 = require("node:path");
|
|
852
853
|
var import_globby = require("globby");
|
|
@@ -854,16 +855,64 @@ var import_globby = require("globby");
|
|
|
854
855
|
class CodebaseIndexer {
|
|
855
856
|
projectPath;
|
|
856
857
|
indexPath;
|
|
858
|
+
fullReindexRatioThreshold = 0.2;
|
|
857
859
|
constructor(projectPath) {
|
|
858
860
|
this.projectPath = projectPath;
|
|
859
861
|
this.indexPath = import_node_path5.join(projectPath, ".locus", "codebase-index.json");
|
|
860
862
|
}
|
|
861
|
-
async index(onProgress, treeSummarizer) {
|
|
863
|
+
async index(onProgress, treeSummarizer, force = false) {
|
|
862
864
|
if (!treeSummarizer) {
|
|
863
865
|
throw new Error("A treeSummarizer is required for this indexing method.");
|
|
864
866
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
+
onProgress?.("Generating file tree...");
|
|
868
|
+
const currentFiles = await this.getFileTree();
|
|
869
|
+
const treeString = currentFiles.join(`
|
|
870
|
+
`);
|
|
871
|
+
const newTreeHash = this.hashTree(treeString);
|
|
872
|
+
const existingIndex = this.loadIndex();
|
|
873
|
+
if (!force && existingIndex?.treeHash === newTreeHash) {
|
|
874
|
+
onProgress?.("No file changes detected, skipping reindex");
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const currentHashes = this.computeFileHashes(currentFiles);
|
|
878
|
+
const existingHashes = existingIndex?.fileHashes;
|
|
879
|
+
const canIncremental = !force && existingIndex && existingHashes;
|
|
880
|
+
if (canIncremental) {
|
|
881
|
+
onProgress?.("Performing incremental update");
|
|
882
|
+
const { added, deleted, modified } = this.diffFiles(currentHashes, existingHashes);
|
|
883
|
+
const changedFiles = [...added, ...modified];
|
|
884
|
+
const totalChanges = changedFiles.length + deleted.length;
|
|
885
|
+
const existingFileCount = Object.keys(existingHashes).length;
|
|
886
|
+
onProgress?.(`File changes detected: ${changedFiles.length} changed, ${added.length} added, ${deleted.length} deleted`);
|
|
887
|
+
if (existingFileCount > 0) {
|
|
888
|
+
const changeRatio = totalChanges / existingFileCount;
|
|
889
|
+
if (changeRatio <= this.fullReindexRatioThreshold && changedFiles.length > 0) {
|
|
890
|
+
onProgress?.(`Reindexing ${changedFiles.length} changed files and merging with existing index`);
|
|
891
|
+
const incrementalIndex = await treeSummarizer(changedFiles.join(`
|
|
892
|
+
`));
|
|
893
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
894
|
+
this.removeFilesFromIndex(updatedIndex, [...deleted, ...modified]);
|
|
895
|
+
return this.mergeIndex(updatedIndex, incrementalIndex, currentHashes, newTreeHash);
|
|
896
|
+
}
|
|
897
|
+
if (changedFiles.length === 0 && deleted.length > 0) {
|
|
898
|
+
onProgress?.(`Removing ${deleted.length} deleted files from index`);
|
|
899
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
900
|
+
this.removeFilesFromIndex(updatedIndex, deleted);
|
|
901
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
902
|
+
}
|
|
903
|
+
if (changedFiles.length === 0 && deleted.length === 0) {
|
|
904
|
+
onProgress?.("No actual file changes, updating hashes only");
|
|
905
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
906
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
907
|
+
}
|
|
908
|
+
onProgress?.(`Too many changes (${(changeRatio * 100).toFixed(1)}%), performing full reindex`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
onProgress?.("AI is analyzing codebase structure...");
|
|
912
|
+
const index = await treeSummarizer(treeString);
|
|
913
|
+
return this.applyIndexMetadata(index, currentHashes, newTreeHash);
|
|
914
|
+
}
|
|
915
|
+
async getFileTree() {
|
|
867
916
|
const gitmodulesPath = import_node_path5.join(this.projectPath, ".gitmodules");
|
|
868
917
|
const submoduleIgnores = [];
|
|
869
918
|
if (import_node_fs3.existsSync(gitmodulesPath)) {
|
|
@@ -881,7 +930,7 @@ class CodebaseIndexer {
|
|
|
881
930
|
}
|
|
882
931
|
} catch {}
|
|
883
932
|
}
|
|
884
|
-
|
|
933
|
+
return import_globby.globby(["**/*"], {
|
|
885
934
|
cwd: this.projectPath,
|
|
886
935
|
gitignore: true,
|
|
887
936
|
ignore: [
|
|
@@ -922,13 +971,6 @@ class CodebaseIndexer {
|
|
|
922
971
|
"**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}"
|
|
923
972
|
]
|
|
924
973
|
});
|
|
925
|
-
const treeString = files.join(`
|
|
926
|
-
`);
|
|
927
|
-
if (onProgress)
|
|
928
|
-
onProgress("AI is analyzing codebase structure...");
|
|
929
|
-
const index = await treeSummarizer(treeString);
|
|
930
|
-
index.lastIndexed = new Date().toISOString();
|
|
931
|
-
return index;
|
|
932
974
|
}
|
|
933
975
|
loadIndex() {
|
|
934
976
|
if (import_node_fs3.existsSync(this.indexPath)) {
|
|
@@ -947,6 +989,79 @@ class CodebaseIndexer {
|
|
|
947
989
|
}
|
|
948
990
|
import_node_fs3.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
949
991
|
}
|
|
992
|
+
cloneIndex(index) {
|
|
993
|
+
return JSON.parse(JSON.stringify(index));
|
|
994
|
+
}
|
|
995
|
+
applyIndexMetadata(index, fileHashes, treeHash) {
|
|
996
|
+
index.lastIndexed = new Date().toISOString();
|
|
997
|
+
index.treeHash = treeHash;
|
|
998
|
+
index.fileHashes = fileHashes;
|
|
999
|
+
return index;
|
|
1000
|
+
}
|
|
1001
|
+
hashTree(tree) {
|
|
1002
|
+
return import_node_crypto2.createHash("sha256").update(tree).digest("hex");
|
|
1003
|
+
}
|
|
1004
|
+
hashFile(filePath) {
|
|
1005
|
+
try {
|
|
1006
|
+
const content = import_node_fs3.readFileSync(import_node_path5.join(this.projectPath, filePath), "utf-8");
|
|
1007
|
+
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1008
|
+
} catch {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
computeFileHashes(files) {
|
|
1013
|
+
const hashes = {};
|
|
1014
|
+
for (const file of files) {
|
|
1015
|
+
const hash = this.hashFile(file);
|
|
1016
|
+
if (hash !== null) {
|
|
1017
|
+
hashes[file] = hash;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return hashes;
|
|
1021
|
+
}
|
|
1022
|
+
diffFiles(currentHashes, existingHashes) {
|
|
1023
|
+
const currentFiles = Object.keys(currentHashes);
|
|
1024
|
+
const existingFiles = Object.keys(existingHashes);
|
|
1025
|
+
const existingSet = new Set(existingFiles);
|
|
1026
|
+
const currentSet = new Set(currentFiles);
|
|
1027
|
+
const added = currentFiles.filter((f) => !existingSet.has(f));
|
|
1028
|
+
const deleted = existingFiles.filter((f) => !currentSet.has(f));
|
|
1029
|
+
const modified = currentFiles.filter((f) => existingSet.has(f) && currentHashes[f] !== existingHashes[f]);
|
|
1030
|
+
return { added, deleted, modified };
|
|
1031
|
+
}
|
|
1032
|
+
removeFilesFromIndex(index, files) {
|
|
1033
|
+
const fileSet = new Set(files);
|
|
1034
|
+
for (const file of files) {
|
|
1035
|
+
delete index.responsibilities[file];
|
|
1036
|
+
}
|
|
1037
|
+
for (const [symbol, paths] of Object.entries(index.symbols)) {
|
|
1038
|
+
index.symbols[symbol] = paths.filter((p) => !fileSet.has(p));
|
|
1039
|
+
if (index.symbols[symbol].length === 0) {
|
|
1040
|
+
delete index.symbols[symbol];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
mergeIndex(existing, incremental, newHashes, newTreeHash) {
|
|
1045
|
+
const mergedSymbols = { ...existing.symbols };
|
|
1046
|
+
for (const [symbol, paths] of Object.entries(incremental.symbols)) {
|
|
1047
|
+
if (mergedSymbols[symbol]) {
|
|
1048
|
+
mergedSymbols[symbol] = [
|
|
1049
|
+
...new Set([...mergedSymbols[symbol], ...paths])
|
|
1050
|
+
];
|
|
1051
|
+
} else {
|
|
1052
|
+
mergedSymbols[symbol] = paths;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const merged = {
|
|
1056
|
+
symbols: mergedSymbols,
|
|
1057
|
+
responsibilities: {
|
|
1058
|
+
...existing.responsibilities,
|
|
1059
|
+
...incremental.responsibilities
|
|
1060
|
+
},
|
|
1061
|
+
lastIndexed: ""
|
|
1062
|
+
};
|
|
1063
|
+
return this.applyIndexMetadata(merged, newHashes, newTreeHash);
|
|
1064
|
+
}
|
|
950
1065
|
}
|
|
951
1066
|
|
|
952
1067
|
// src/agent/codebase-indexer-service.ts
|
|
@@ -957,9 +1072,8 @@ class CodebaseIndexerService {
|
|
|
957
1072
|
this.deps = deps;
|
|
958
1073
|
this.indexer = new CodebaseIndexer(deps.projectPath);
|
|
959
1074
|
}
|
|
960
|
-
async reindex() {
|
|
1075
|
+
async reindex(force = false) {
|
|
961
1076
|
try {
|
|
962
|
-
this.deps.log("Reindexing codebase...", "info");
|
|
963
1077
|
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
964
1078
|
const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
|
|
965
1079
|
1. Key symbols (classes, functions, types) and their locations
|
|
@@ -980,7 +1094,11 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
980
1094
|
return JSON.parse(jsonMatch[0]);
|
|
981
1095
|
}
|
|
982
1096
|
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
983
|
-
});
|
|
1097
|
+
}, force);
|
|
1098
|
+
if (index === null) {
|
|
1099
|
+
this.deps.log("No changes detected, skipping reindex", "info");
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
984
1102
|
this.indexer.saveIndex(index);
|
|
985
1103
|
this.deps.log("Codebase reindexed successfully", "success");
|
|
986
1104
|
} catch (error) {
|
package/dist/core/indexer.d.ts
CHANGED
|
@@ -2,17 +2,30 @@ export interface CodebaseIndex {
|
|
|
2
2
|
symbols: Record<string, string[]>;
|
|
3
3
|
responsibilities: Record<string, string>;
|
|
4
4
|
lastIndexed: string;
|
|
5
|
+
treeHash?: string;
|
|
6
|
+
fileHashes?: Record<string, string>;
|
|
5
7
|
}
|
|
6
8
|
export declare class CodebaseIndexer {
|
|
7
9
|
private projectPath;
|
|
8
10
|
private indexPath;
|
|
11
|
+
private fullReindexRatioThreshold;
|
|
9
12
|
constructor(projectPath: string);
|
|
10
13
|
/**
|
|
11
14
|
* Generates a codebase index by providing the entire file tree to an AI summarizer.
|
|
12
|
-
*
|
|
15
|
+
* Supports incremental updates - only analyzes changed files when possible.
|
|
16
|
+
* Returns null if no changes detected (unless force=true).
|
|
13
17
|
*/
|
|
14
|
-
index(onProgress?: (message: string) => void, treeSummarizer?: (tree: string) => Promise<CodebaseIndex
|
|
18
|
+
index(onProgress?: (message: string) => void, treeSummarizer?: (tree: string) => Promise<CodebaseIndex>, force?: boolean): Promise<CodebaseIndex | null>;
|
|
19
|
+
private getFileTree;
|
|
15
20
|
loadIndex(): CodebaseIndex | null;
|
|
16
21
|
saveIndex(index: CodebaseIndex): void;
|
|
22
|
+
private cloneIndex;
|
|
23
|
+
private applyIndexMetadata;
|
|
24
|
+
private hashTree;
|
|
25
|
+
private hashFile;
|
|
26
|
+
private computeFileHashes;
|
|
27
|
+
private diffFiles;
|
|
28
|
+
private removeFilesFromIndex;
|
|
29
|
+
private mergeIndex;
|
|
17
30
|
}
|
|
18
31
|
//# sourceMappingURL=indexer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,yBAAyB,CAAe;gBAEpC,WAAW,EAAE,MAAM;IAK/B;;;;OAIG;IACG,KAAK,CACT,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,EACzD,KAAK,UAAQ,GACZ,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;YAyGlB,WAAW;IAgEzB,SAAS,IAAI,aAAa,GAAG,IAAI;IAWjC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAQrC,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,UAAU;CA8BnB"}
|
package/dist/index-node.js
CHANGED
|
@@ -847,6 +847,7 @@ class ArtifactSyncer {
|
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
// src/core/indexer.ts
|
|
850
|
+
var import_node_crypto2 = require("node:crypto");
|
|
850
851
|
var import_node_fs3 = require("node:fs");
|
|
851
852
|
var import_node_path5 = require("node:path");
|
|
852
853
|
var import_globby = require("globby");
|
|
@@ -854,16 +855,64 @@ var import_globby = require("globby");
|
|
|
854
855
|
class CodebaseIndexer {
|
|
855
856
|
projectPath;
|
|
856
857
|
indexPath;
|
|
858
|
+
fullReindexRatioThreshold = 0.2;
|
|
857
859
|
constructor(projectPath) {
|
|
858
860
|
this.projectPath = projectPath;
|
|
859
861
|
this.indexPath = import_node_path5.join(projectPath, ".locus", "codebase-index.json");
|
|
860
862
|
}
|
|
861
|
-
async index(onProgress, treeSummarizer) {
|
|
863
|
+
async index(onProgress, treeSummarizer, force = false) {
|
|
862
864
|
if (!treeSummarizer) {
|
|
863
865
|
throw new Error("A treeSummarizer is required for this indexing method.");
|
|
864
866
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
+
onProgress?.("Generating file tree...");
|
|
868
|
+
const currentFiles = await this.getFileTree();
|
|
869
|
+
const treeString = currentFiles.join(`
|
|
870
|
+
`);
|
|
871
|
+
const newTreeHash = this.hashTree(treeString);
|
|
872
|
+
const existingIndex = this.loadIndex();
|
|
873
|
+
if (!force && existingIndex?.treeHash === newTreeHash) {
|
|
874
|
+
onProgress?.("No file changes detected, skipping reindex");
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const currentHashes = this.computeFileHashes(currentFiles);
|
|
878
|
+
const existingHashes = existingIndex?.fileHashes;
|
|
879
|
+
const canIncremental = !force && existingIndex && existingHashes;
|
|
880
|
+
if (canIncremental) {
|
|
881
|
+
onProgress?.("Performing incremental update");
|
|
882
|
+
const { added, deleted, modified } = this.diffFiles(currentHashes, existingHashes);
|
|
883
|
+
const changedFiles = [...added, ...modified];
|
|
884
|
+
const totalChanges = changedFiles.length + deleted.length;
|
|
885
|
+
const existingFileCount = Object.keys(existingHashes).length;
|
|
886
|
+
onProgress?.(`File changes detected: ${changedFiles.length} changed, ${added.length} added, ${deleted.length} deleted`);
|
|
887
|
+
if (existingFileCount > 0) {
|
|
888
|
+
const changeRatio = totalChanges / existingFileCount;
|
|
889
|
+
if (changeRatio <= this.fullReindexRatioThreshold && changedFiles.length > 0) {
|
|
890
|
+
onProgress?.(`Reindexing ${changedFiles.length} changed files and merging with existing index`);
|
|
891
|
+
const incrementalIndex = await treeSummarizer(changedFiles.join(`
|
|
892
|
+
`));
|
|
893
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
894
|
+
this.removeFilesFromIndex(updatedIndex, [...deleted, ...modified]);
|
|
895
|
+
return this.mergeIndex(updatedIndex, incrementalIndex, currentHashes, newTreeHash);
|
|
896
|
+
}
|
|
897
|
+
if (changedFiles.length === 0 && deleted.length > 0) {
|
|
898
|
+
onProgress?.(`Removing ${deleted.length} deleted files from index`);
|
|
899
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
900
|
+
this.removeFilesFromIndex(updatedIndex, deleted);
|
|
901
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
902
|
+
}
|
|
903
|
+
if (changedFiles.length === 0 && deleted.length === 0) {
|
|
904
|
+
onProgress?.("No actual file changes, updating hashes only");
|
|
905
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
906
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
907
|
+
}
|
|
908
|
+
onProgress?.(`Too many changes (${(changeRatio * 100).toFixed(1)}%), performing full reindex`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
onProgress?.("AI is analyzing codebase structure...");
|
|
912
|
+
const index = await treeSummarizer(treeString);
|
|
913
|
+
return this.applyIndexMetadata(index, currentHashes, newTreeHash);
|
|
914
|
+
}
|
|
915
|
+
async getFileTree() {
|
|
867
916
|
const gitmodulesPath = import_node_path5.join(this.projectPath, ".gitmodules");
|
|
868
917
|
const submoduleIgnores = [];
|
|
869
918
|
if (import_node_fs3.existsSync(gitmodulesPath)) {
|
|
@@ -881,7 +930,7 @@ class CodebaseIndexer {
|
|
|
881
930
|
}
|
|
882
931
|
} catch {}
|
|
883
932
|
}
|
|
884
|
-
|
|
933
|
+
return import_globby.globby(["**/*"], {
|
|
885
934
|
cwd: this.projectPath,
|
|
886
935
|
gitignore: true,
|
|
887
936
|
ignore: [
|
|
@@ -922,13 +971,6 @@ class CodebaseIndexer {
|
|
|
922
971
|
"**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}"
|
|
923
972
|
]
|
|
924
973
|
});
|
|
925
|
-
const treeString = files.join(`
|
|
926
|
-
`);
|
|
927
|
-
if (onProgress)
|
|
928
|
-
onProgress("AI is analyzing codebase structure...");
|
|
929
|
-
const index = await treeSummarizer(treeString);
|
|
930
|
-
index.lastIndexed = new Date().toISOString();
|
|
931
|
-
return index;
|
|
932
974
|
}
|
|
933
975
|
loadIndex() {
|
|
934
976
|
if (import_node_fs3.existsSync(this.indexPath)) {
|
|
@@ -947,6 +989,79 @@ class CodebaseIndexer {
|
|
|
947
989
|
}
|
|
948
990
|
import_node_fs3.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
949
991
|
}
|
|
992
|
+
cloneIndex(index) {
|
|
993
|
+
return JSON.parse(JSON.stringify(index));
|
|
994
|
+
}
|
|
995
|
+
applyIndexMetadata(index, fileHashes, treeHash) {
|
|
996
|
+
index.lastIndexed = new Date().toISOString();
|
|
997
|
+
index.treeHash = treeHash;
|
|
998
|
+
index.fileHashes = fileHashes;
|
|
999
|
+
return index;
|
|
1000
|
+
}
|
|
1001
|
+
hashTree(tree) {
|
|
1002
|
+
return import_node_crypto2.createHash("sha256").update(tree).digest("hex");
|
|
1003
|
+
}
|
|
1004
|
+
hashFile(filePath) {
|
|
1005
|
+
try {
|
|
1006
|
+
const content = import_node_fs3.readFileSync(import_node_path5.join(this.projectPath, filePath), "utf-8");
|
|
1007
|
+
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1008
|
+
} catch {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
computeFileHashes(files) {
|
|
1013
|
+
const hashes = {};
|
|
1014
|
+
for (const file of files) {
|
|
1015
|
+
const hash = this.hashFile(file);
|
|
1016
|
+
if (hash !== null) {
|
|
1017
|
+
hashes[file] = hash;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return hashes;
|
|
1021
|
+
}
|
|
1022
|
+
diffFiles(currentHashes, existingHashes) {
|
|
1023
|
+
const currentFiles = Object.keys(currentHashes);
|
|
1024
|
+
const existingFiles = Object.keys(existingHashes);
|
|
1025
|
+
const existingSet = new Set(existingFiles);
|
|
1026
|
+
const currentSet = new Set(currentFiles);
|
|
1027
|
+
const added = currentFiles.filter((f) => !existingSet.has(f));
|
|
1028
|
+
const deleted = existingFiles.filter((f) => !currentSet.has(f));
|
|
1029
|
+
const modified = currentFiles.filter((f) => existingSet.has(f) && currentHashes[f] !== existingHashes[f]);
|
|
1030
|
+
return { added, deleted, modified };
|
|
1031
|
+
}
|
|
1032
|
+
removeFilesFromIndex(index, files) {
|
|
1033
|
+
const fileSet = new Set(files);
|
|
1034
|
+
for (const file of files) {
|
|
1035
|
+
delete index.responsibilities[file];
|
|
1036
|
+
}
|
|
1037
|
+
for (const [symbol, paths] of Object.entries(index.symbols)) {
|
|
1038
|
+
index.symbols[symbol] = paths.filter((p) => !fileSet.has(p));
|
|
1039
|
+
if (index.symbols[symbol].length === 0) {
|
|
1040
|
+
delete index.symbols[symbol];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
mergeIndex(existing, incremental, newHashes, newTreeHash) {
|
|
1045
|
+
const mergedSymbols = { ...existing.symbols };
|
|
1046
|
+
for (const [symbol, paths] of Object.entries(incremental.symbols)) {
|
|
1047
|
+
if (mergedSymbols[symbol]) {
|
|
1048
|
+
mergedSymbols[symbol] = [
|
|
1049
|
+
...new Set([...mergedSymbols[symbol], ...paths])
|
|
1050
|
+
];
|
|
1051
|
+
} else {
|
|
1052
|
+
mergedSymbols[symbol] = paths;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const merged = {
|
|
1056
|
+
symbols: mergedSymbols,
|
|
1057
|
+
responsibilities: {
|
|
1058
|
+
...existing.responsibilities,
|
|
1059
|
+
...incremental.responsibilities
|
|
1060
|
+
},
|
|
1061
|
+
lastIndexed: ""
|
|
1062
|
+
};
|
|
1063
|
+
return this.applyIndexMetadata(merged, newHashes, newTreeHash);
|
|
1064
|
+
}
|
|
950
1065
|
}
|
|
951
1066
|
|
|
952
1067
|
// src/agent/codebase-indexer-service.ts
|
|
@@ -957,9 +1072,8 @@ class CodebaseIndexerService {
|
|
|
957
1072
|
this.deps = deps;
|
|
958
1073
|
this.indexer = new CodebaseIndexer(deps.projectPath);
|
|
959
1074
|
}
|
|
960
|
-
async reindex() {
|
|
1075
|
+
async reindex(force = false) {
|
|
961
1076
|
try {
|
|
962
|
-
this.deps.log("Reindexing codebase...", "info");
|
|
963
1077
|
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
964
1078
|
const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
|
|
965
1079
|
1. Key symbols (classes, functions, types) and their locations
|
|
@@ -980,7 +1094,11 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
980
1094
|
return JSON.parse(jsonMatch[0]);
|
|
981
1095
|
}
|
|
982
1096
|
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
983
|
-
});
|
|
1097
|
+
}, force);
|
|
1098
|
+
if (index === null) {
|
|
1099
|
+
this.deps.log("No changes detected, skipping reindex", "info");
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
984
1102
|
this.indexer.saveIndex(index);
|
|
985
1103
|
this.deps.log("Codebase reindexed successfully", "success");
|
|
986
1104
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"clean": "rm -rf node_modules"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@locusai/shared": "^0.5.
|
|
33
|
+
"@locusai/shared": "^0.5.1",
|
|
34
34
|
"axios": "^1.13.2",
|
|
35
35
|
"events": "^3.3.0",
|
|
36
36
|
"globby": "^14.0.2"
|