@wrongstack/tools 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_win32-resolve.d.ts +1 -6
- package/dist/audit.js +5 -5
- package/dist/bash.js +2 -2
- package/dist/builtin.js +30 -30
- package/dist/{chunk-7LQCT3LT.js → chunk-32XPUSY3.js} +3 -3
- package/dist/{chunk-N3K2SFEI.js → chunk-3JVSOBHU.js} +1408 -200
- package/dist/{chunk-7N5U2RAU.js → chunk-3LS3FYEF.js} +2 -2
- package/dist/{chunk-FBUP524S.js → chunk-4YKVHCQC.js} +13 -2
- package/dist/{chunk-KE7A76VC.js → chunk-5U5ULQX2.js} +4 -14
- package/dist/{chunk-NU2IZ72J.js → chunk-6LCKF5KH.js} +8 -6
- package/dist/{chunk-GDDXX27P.js → chunk-6ZLRRC74.js} +2 -2
- package/dist/{chunk-IYH4OHBP.js → chunk-77MBK46T.js} +3 -3
- package/dist/{chunk-DK4J2WMH.js → chunk-AMURQAJX.js} +2 -2
- package/dist/{chunk-ZTCJCYLN.js → chunk-CVNCSMVU.js} +49 -6
- package/dist/{chunk-MJSJWXBF.js → chunk-DX5KZEMK.js} +14 -2
- package/dist/{chunk-CGXTR34H.js → chunk-E2ZD4ERD.js} +2 -2
- package/dist/{chunk-DRRY6SCM.js → chunk-EMA2XENR.js} +2 -2
- package/dist/{chunk-3D5DJWOE.js → chunk-FC3X7GGJ.js} +11 -3
- package/dist/{chunk-T3SMFF5Q.js → chunk-GU7QJ37F.js} +2 -2
- package/dist/{chunk-4WPBS45A.js → chunk-IRUEHGCR.js} +3 -3
- package/dist/{chunk-WEEVSWQB.js → chunk-KIQTABBJ.js} +3 -3
- package/dist/{chunk-6H4ERJP7.js → chunk-KYVOTHG7.js} +2 -2
- package/dist/{chunk-O6BY43ZB.js → chunk-MIV2RU5B.js} +3 -3
- package/dist/{chunk-5KS24LTS.js → chunk-NOCU555K.js} +2 -2
- package/dist/{chunk-6GOZTS6F.js → chunk-OUEH4I4X.js} +3 -3
- package/dist/{chunk-YEISRT4S.js → chunk-PNLDTRC5.js} +1429 -242
- package/dist/{chunk-GKSG5CPH.js → chunk-QU2VRCMM.js} +2 -2
- package/dist/{chunk-VVZPQPTY.js → chunk-REWOZIA4.js} +5 -3
- package/dist/{chunk-MGPR7VG3.js → chunk-T7YVY2BV.js} +2 -2
- package/dist/{chunk-IAYMTTR2.js → chunk-TYRVYBHW.js} +27 -23
- package/dist/{chunk-LQSOU63Z.js → chunk-VKAJMHVG.js} +2 -2
- package/dist/{chunk-E7JE3UEX.js → chunk-VO6M2FVG.js} +2 -2
- package/dist/{chunk-NJX2RXKJ.js → chunk-YDTRKEYY.js} +11 -3
- package/dist/{chunk-MK2JK35J.js → chunk-YECHJJJF.js} +3 -3
- package/dist/{chunk-QA6IASYX.js → chunk-Z34CUZCZ.js} +3 -3
- package/dist/{chunk-UCYQTZ7F.js → chunk-ZKC5UNGD.js} +56 -2
- package/dist/codebase-index/atlas-brief.d.ts +90 -0
- package/dist/codebase-index/atlas-export.d.ts +38 -0
- package/dist/codebase-index/atlas-projection.d.ts +114 -0
- package/dist/codebase-index/atlas-types.d.ts +90 -0
- package/dist/codebase-index/background-indexer.d.ts +15 -4
- package/dist/codebase-index/codebase-context-tool.d.ts +54 -0
- package/dist/codebase-index/concept-enrichment.d.ts +154 -0
- package/dist/codebase-index/context-retrieval.d.ts +91 -0
- package/dist/codebase-index/embedding-pass.d.ts +95 -0
- package/dist/codebase-index/graph-adjacency-cache.d.ts +36 -0
- package/dist/codebase-index/graph-rank-pass.d.ts +46 -0
- package/dist/codebase-index/graph-rank.d.ts +198 -0
- package/dist/codebase-index/index-service.d.ts +15 -1
- package/dist/codebase-index/index.d.ts +34 -19
- package/dist/codebase-index/index.js +128 -11
- package/dist/codebase-index/parser-worker-script.js +4 -4
- package/dist/codebase-index/perf-metrics.d.ts +19 -0
- package/dist/codebase-index/project-server-protocol.d.ts +2 -0
- package/dist/codebase-index/project-server-query-cache.d.ts +4 -0
- package/dist/codebase-index/project-server.js +55 -8
- package/dist/codebase-index/repo-map-fallback.d.ts +18 -0
- package/dist/codebase-index/repo-map-render.d.ts +42 -0
- package/dist/codebase-index/repo-map-types.d.ts +21 -0
- package/dist/codebase-index/repo-map.d.ts +28 -16
- package/dist/codebase-index/schema.d.ts +20 -0
- package/dist/codebase-index/worker-protocol.d.ts +47 -1
- package/dist/codebase-index/worker.js +12 -6
- package/dist/codebase-index/writer-concepts.d.ts +84 -0
- package/dist/codebase-index/writer-graph-decorate.d.ts +30 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +22 -0
- package/dist/codebase-index/writer-rank.d.ts +78 -0
- package/dist/codebase-index/writer-schema.d.ts +51 -0
- package/dist/codebase-index/writer-vectors.d.ts +53 -0
- package/dist/codebase-index/writer.d.ts +59 -0
- package/dist/edit.js +8 -8
- package/dist/exec.js +2 -2
- package/dist/fetch.js +1 -1
- package/dist/format.js +5 -5
- package/dist/{go-parser-D7ELWQ7F.js → go-parser-EMKTAAD3.js} +5 -3
- package/dist/index.d.ts +48 -48
- package/dist/index.js +57 -33
- package/dist/install.js +5 -5
- package/dist/languages/index.js +3 -3
- package/dist/{legacy-bridge-YXANP5QB.js → legacy-bridge-WVQ4BHDI.js} +5 -5
- package/dist/lint.js +5 -5
- package/dist/outdated.js +2 -2
- package/dist/pack.js +31 -31
- package/dist/parser-dispatch-UQAB4E6V.js +14 -0
- package/dist/patch.js +8 -8
- package/dist/plan.js +2 -2
- package/dist/{py-parser-QC6MR4GB.js → py-parser-DDG53RXI.js} +4 -4
- package/dist/read.js +8 -8
- package/dist/replace.js +8 -8
- package/dist/task.js +2 -2
- package/dist/test.js +5 -5
- package/dist/todo.js +1 -1
- package/dist/tool-tier.js +32 -32
- package/dist/typecheck.js +5 -5
- package/dist/win32.js +1 -1
- package/dist/write.js +8 -8
- package/package.json +5 -5
- package/dist/parser-dispatch-BDVCJXTV.js +0 -14
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
} from "./chunk-XRNN44QY.js";
|
|
4
4
|
import {
|
|
5
5
|
spawnStream
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EMA2XENR.js";
|
|
7
7
|
import {
|
|
8
|
+
codebaseContext,
|
|
8
9
|
codebaseIndexStats,
|
|
10
|
+
codebaseVectorSearch,
|
|
9
11
|
enqueueReindex,
|
|
10
12
|
getIndexState,
|
|
11
13
|
incomingCallsService,
|
|
@@ -13,34 +15,31 @@ import {
|
|
|
13
15
|
outgoingCallsService,
|
|
14
16
|
runStartupIndex,
|
|
15
17
|
searchCodebaseIndex
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CVNCSMVU.js";
|
|
17
19
|
import {
|
|
18
20
|
IndexTimeoutError,
|
|
19
21
|
SCHEMA_VERSION,
|
|
20
22
|
codebaseIndexDirOverride,
|
|
21
23
|
indexCircuitBreaker,
|
|
22
24
|
indexStorePool,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
isConceptRelation,
|
|
26
|
+
posixIndexPath,
|
|
27
|
+
resolveIndexDir,
|
|
28
|
+
xxhash64String
|
|
29
|
+
} from "./chunk-3JVSOBHU.js";
|
|
25
30
|
import {
|
|
26
31
|
EXT_TO_LANG,
|
|
27
32
|
detectLang,
|
|
28
33
|
isIndexablePath
|
|
29
34
|
} from "./chunk-YOHO4DVT.js";
|
|
35
|
+
import {
|
|
36
|
+
loadGitignoreMatcher
|
|
37
|
+
} from "./chunk-2CTZW6JQ.js";
|
|
30
38
|
import {
|
|
31
39
|
normalizeCommandOutput,
|
|
32
40
|
safeResolveProjectPath
|
|
33
41
|
} from "./chunk-ZUFHAHTE.js";
|
|
34
42
|
|
|
35
|
-
// src/codebase-index/codebase-ast-replace-tool.ts
|
|
36
|
-
import * as path2 from "node:path";
|
|
37
|
-
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
38
|
-
|
|
39
|
-
// src/codebase-index/ast-symbol-mutator.ts
|
|
40
|
-
import * as fs from "node:fs/promises";
|
|
41
|
-
import * as path from "node:path";
|
|
42
|
-
import { atomicWrite } from "@wrongstack/core/utils";
|
|
43
|
-
|
|
44
43
|
// src/codebase-index/ast-invariant-engine.ts
|
|
45
44
|
var tsModule = null;
|
|
46
45
|
async function loadTs() {
|
|
@@ -467,6 +466,9 @@ var PolyglotInvariantEngine = class {
|
|
|
467
466
|
var polyglotInvariantEngine = new PolyglotInvariantEngine();
|
|
468
467
|
|
|
469
468
|
// src/codebase-index/ast-symbol-mutator.ts
|
|
469
|
+
import * as fs from "node:fs/promises";
|
|
470
|
+
import * as path from "node:path";
|
|
471
|
+
import { atomicWrite } from "@wrongstack/core/utils";
|
|
470
472
|
var tsModule2 = null;
|
|
471
473
|
async function loadTs2() {
|
|
472
474
|
if (!tsModule2) {
|
|
@@ -639,6 +641,8 @@ Pass allowBreakingChanges: true if this breaking change is intended.`
|
|
|
639
641
|
}
|
|
640
642
|
|
|
641
643
|
// src/codebase-index/codebase-ast-replace-tool.ts
|
|
644
|
+
import * as path2 from "node:path";
|
|
645
|
+
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
642
646
|
var codebaseAstReplaceTool = {
|
|
643
647
|
name: "codebase-ast-replace",
|
|
644
648
|
category: "Edit",
|
|
@@ -705,88 +709,9 @@ var codebaseAstReplaceTool = {
|
|
|
705
709
|
}
|
|
706
710
|
};
|
|
707
711
|
|
|
708
|
-
// src/codebase-index/codebase-invariant-check-tool.ts
|
|
709
|
-
import * as fs2 from "node:fs/promises";
|
|
710
|
-
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
711
|
-
var codebaseInvariantCheckTool = {
|
|
712
|
-
name: "codebase-invariant-check",
|
|
713
|
-
category: "Inspect",
|
|
714
|
-
icon: "index",
|
|
715
|
-
permission: "auto",
|
|
716
|
-
subjectKey: "file",
|
|
717
|
-
mutating: false,
|
|
718
|
-
capabilities: ["fs.read"],
|
|
719
|
-
description: "Mathematically verify AST backward-compatibility invariants between original code and candidate mutations across TypeScript, Python, Go, Rust, Java, etc. Detects missing exports (INV-001), mandatory parameter additions (INV-002), interface breaking expansions (INV-003), and return type changes (INV-004) with zero hallucinations.",
|
|
720
|
-
usageHint: "PRE-FLIGHT AST INVARIANT AUDIT:\n\n- Provide `file` and `modifiedCode` (or both `originalCode` and `modifiedCode`).\n- Returns `valid: true` if changes are strictly backward compatible.\n- Returns structured `violations[]` with exact rule IDs, offending symbol names, and fix instructions.",
|
|
721
|
-
inputSchema: {
|
|
722
|
-
type: "object",
|
|
723
|
-
properties: {
|
|
724
|
-
file: {
|
|
725
|
-
type: "string",
|
|
726
|
-
description: "Path to existing source file to compare against candidate modifications."
|
|
727
|
-
},
|
|
728
|
-
originalCode: {
|
|
729
|
-
type: "string",
|
|
730
|
-
description: "Original source code string (if not reading from an existing file)."
|
|
731
|
-
},
|
|
732
|
-
modifiedCode: {
|
|
733
|
-
type: "string",
|
|
734
|
-
description: "The candidate modified code string to validate."
|
|
735
|
-
},
|
|
736
|
-
lang: {
|
|
737
|
-
type: "string",
|
|
738
|
-
description: "Language hint (ts, py, go, rs, etc.). Inferred from file path if omitted."
|
|
739
|
-
}
|
|
740
|
-
},
|
|
741
|
-
required: ["modifiedCode"],
|
|
742
|
-
additionalProperties: false
|
|
743
|
-
},
|
|
744
|
-
async execute(input, ctx) {
|
|
745
|
-
try {
|
|
746
|
-
let originalCode = input.originalCode;
|
|
747
|
-
let lang = input.lang;
|
|
748
|
-
if (!originalCode && input.file) {
|
|
749
|
-
const resolved = await safeResolveProjectPath(input.file, ctx);
|
|
750
|
-
originalCode = await fs2.readFile(resolved, "utf8");
|
|
751
|
-
if (!lang) {
|
|
752
|
-
lang = detectLang(resolved) ?? "ts";
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
if (originalCode === void 0) {
|
|
756
|
-
return {
|
|
757
|
-
valid: false,
|
|
758
|
-
violations: [],
|
|
759
|
-
summary: "Neither originalCode nor file was provided for invariant comparison.",
|
|
760
|
-
error: "Missing originalCode or file path."
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
const res = await polyglotInvariantEngine.evaluate({
|
|
764
|
-
originalCode,
|
|
765
|
-
modifiedCode: input.modifiedCode,
|
|
766
|
-
lang,
|
|
767
|
-
filePath: input.file
|
|
768
|
-
});
|
|
769
|
-
const summary = res.valid ? "AST Invariants PASSED. Mutation is 100% backward compatible." : `AST Invariants FAILED with ${res.violations.length} violation(s):
|
|
770
|
-
` + res.violations.map((v) => ` - [${v.ruleId}] ${v.symbolName}: ${v.message}`).join("\n");
|
|
771
|
-
return {
|
|
772
|
-
valid: res.valid,
|
|
773
|
-
violations: res.violations,
|
|
774
|
-
summary
|
|
775
|
-
};
|
|
776
|
-
} catch (err) {
|
|
777
|
-
return {
|
|
778
|
-
valid: false,
|
|
779
|
-
violations: [],
|
|
780
|
-
summary: `Error during invariant evaluation: ${toErrorMessage2(err)}`,
|
|
781
|
-
error: toErrorMessage2(err)
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
|
|
787
712
|
// src/codebase-index/codebase-impact-analysis-tool.ts
|
|
788
713
|
import * as path3 from "node:path";
|
|
789
|
-
import { toErrorMessage as
|
|
714
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
790
715
|
var TEST_FILE_REGEX = /(?:test|spec|mock|fixture|bench)[s]?[/\\._]/i;
|
|
791
716
|
var codebaseImpactAnalysisTool = {
|
|
792
717
|
name: "codebase-impact-analysis",
|
|
@@ -840,7 +765,7 @@ var codebaseImpactAnalysisTool = {
|
|
|
840
765
|
} catch (err) {
|
|
841
766
|
symbolFound = false;
|
|
842
767
|
indexUnavailable = true;
|
|
843
|
-
indexError =
|
|
768
|
+
indexError = toErrorMessage2(err);
|
|
844
769
|
}
|
|
845
770
|
if (indexUnavailable) {
|
|
846
771
|
const detail = indexError ? ` (${indexError})` : "";
|
|
@@ -943,14 +868,15 @@ var codebaseImpactAnalysisTool = {
|
|
|
943
868
|
callSites: [],
|
|
944
869
|
recommendedActionPlan: [],
|
|
945
870
|
symbolFound: false,
|
|
946
|
-
error:
|
|
871
|
+
error: toErrorMessage2(err)
|
|
947
872
|
};
|
|
948
873
|
}
|
|
949
874
|
}
|
|
950
875
|
};
|
|
951
876
|
|
|
952
877
|
// src/codebase-index/codebase-incoming-calls-tool.ts
|
|
953
|
-
import {
|
|
878
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
879
|
+
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
954
880
|
var codebaseIncomingCallsTool = {
|
|
955
881
|
name: "codebase-incoming-calls",
|
|
956
882
|
category: "Project",
|
|
@@ -987,6 +913,12 @@ var codebaseIncomingCallsTool = {
|
|
|
987
913
|
required: ["symbol"]
|
|
988
914
|
},
|
|
989
915
|
async execute(input, ctx) {
|
|
916
|
+
if (!input?.symbol || typeof input.symbol !== "string" || !input.symbol.trim()) {
|
|
917
|
+
throw new ToolValidationError({
|
|
918
|
+
message: "codebase-incoming-calls: symbol is required and cannot be empty",
|
|
919
|
+
field: "symbol"
|
|
920
|
+
});
|
|
921
|
+
}
|
|
990
922
|
const state = getIndexState();
|
|
991
923
|
if (state.lastError) {
|
|
992
924
|
const circuit = state.circuit;
|
|
@@ -1024,7 +956,7 @@ var codebaseIncomingCallsTool = {
|
|
|
1024
956
|
symbol: input.symbol,
|
|
1025
957
|
calls: [],
|
|
1026
958
|
total: 0,
|
|
1027
|
-
indexStatus: `Index query failed: ${
|
|
959
|
+
indexStatus: `Index query failed: ${toErrorMessage3(err)}. Fall back to grep for this lookup.`
|
|
1028
960
|
};
|
|
1029
961
|
}
|
|
1030
962
|
const { calls, symbolFound, ambiguous, totalMatches, stale } = serviced;
|
|
@@ -1082,11 +1014,11 @@ var codebaseIncomingCallsTool = {
|
|
|
1082
1014
|
};
|
|
1083
1015
|
|
|
1084
1016
|
// src/codebase-index/codebase-index-tool.ts
|
|
1085
|
-
import { ToolValidationError as
|
|
1017
|
+
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
1086
1018
|
|
|
1087
1019
|
// src/codebase-index/codebase-search-tool.ts
|
|
1088
|
-
import { ToolValidationError } from "@wrongstack/core/types";
|
|
1089
|
-
import { toErrorMessage as
|
|
1020
|
+
import { ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
1021
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
1090
1022
|
var INDEXABLE_LANG_IDS = [
|
|
1091
1023
|
...new Set(Object.values(EXT_TO_LANG))
|
|
1092
1024
|
].sort();
|
|
@@ -1163,13 +1095,13 @@ var codebaseSearchTool = {
|
|
|
1163
1095
|
},
|
|
1164
1096
|
async execute(input, ctx, execOpts) {
|
|
1165
1097
|
if (!input?.query || typeof input.query !== "string" || !input.query.trim()) {
|
|
1166
|
-
throw new
|
|
1098
|
+
throw new ToolValidationError2({
|
|
1167
1099
|
message: "codebase-search: query is required and cannot be empty",
|
|
1168
1100
|
field: "query"
|
|
1169
1101
|
});
|
|
1170
1102
|
}
|
|
1171
1103
|
if (input.lspKind !== void 0 && (!Number.isInteger(input.lspKind) || input.lspKind < 1 || input.lspKind > 26)) {
|
|
1172
|
-
throw new
|
|
1104
|
+
throw new ToolValidationError2({
|
|
1173
1105
|
message: "codebase-search: lspKind must be an integer between 1 and 26",
|
|
1174
1106
|
field: "lspKind"
|
|
1175
1107
|
});
|
|
@@ -1219,7 +1151,7 @@ var codebaseSearchTool = {
|
|
|
1219
1151
|
results: [],
|
|
1220
1152
|
total: 0,
|
|
1221
1153
|
query: input.query,
|
|
1222
|
-
indexStatus: `Index query failed: ${
|
|
1154
|
+
indexStatus: `Index query failed: ${toErrorMessage4(err)}. Fall back to grep/glob for this lookup.`
|
|
1223
1155
|
};
|
|
1224
1156
|
}
|
|
1225
1157
|
const { results, total, stale, indexSummary } = searched;
|
|
@@ -1287,7 +1219,7 @@ var codebaseIndexTool = {
|
|
|
1287
1219
|
signal?.throwIfAborted();
|
|
1288
1220
|
if (input.langs) {
|
|
1289
1221
|
if (!Array.isArray(input.langs) || input.langs.length === 0) {
|
|
1290
|
-
throw new
|
|
1222
|
+
throw new ToolValidationError3({
|
|
1291
1223
|
message: "codebase-index: langs cannot be an empty array. Pass at least one valid language or omit langs.",
|
|
1292
1224
|
field: "langs"
|
|
1293
1225
|
});
|
|
@@ -1296,7 +1228,7 @@ var codebaseIndexTool = {
|
|
|
1296
1228
|
(lang) => !INDEXABLE_LANG_IDS.includes(lang)
|
|
1297
1229
|
);
|
|
1298
1230
|
if (unknown.length > 0) {
|
|
1299
|
-
throw new
|
|
1231
|
+
throw new ToolValidationError3({
|
|
1300
1232
|
message: `codebase-index: unknown lang(s) ${unknown.map((l) => `"${l}"`).join(", ")}. Valid ids: ${INDEXABLE_LANG_IDS.join(", ")}.`,
|
|
1301
1233
|
field: "langs"
|
|
1302
1234
|
});
|
|
@@ -1345,7 +1277,87 @@ var codebaseIndexTool = {
|
|
|
1345
1277
|
}
|
|
1346
1278
|
};
|
|
1347
1279
|
|
|
1280
|
+
// src/codebase-index/codebase-invariant-check-tool.ts
|
|
1281
|
+
import * as fs2 from "node:fs/promises";
|
|
1282
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
1283
|
+
var codebaseInvariantCheckTool = {
|
|
1284
|
+
name: "codebase-invariant-check",
|
|
1285
|
+
category: "Inspect",
|
|
1286
|
+
icon: "index",
|
|
1287
|
+
permission: "auto",
|
|
1288
|
+
subjectKey: "file",
|
|
1289
|
+
mutating: false,
|
|
1290
|
+
capabilities: ["fs.read"],
|
|
1291
|
+
description: "Mathematically verify AST backward-compatibility invariants between original code and candidate mutations across TypeScript, Python, Go, Rust, Java, etc. Detects missing exports (INV-001), mandatory parameter additions (INV-002), interface breaking expansions (INV-003), and return type changes (INV-004) with zero hallucinations.",
|
|
1292
|
+
usageHint: "PRE-FLIGHT AST INVARIANT AUDIT:\n\n- Provide `file` and `modifiedCode` (or both `originalCode` and `modifiedCode`).\n- Returns `valid: true` if changes are strictly backward compatible.\n- Returns structured `violations[]` with exact rule IDs, offending symbol names, and fix instructions.",
|
|
1293
|
+
inputSchema: {
|
|
1294
|
+
type: "object",
|
|
1295
|
+
properties: {
|
|
1296
|
+
file: {
|
|
1297
|
+
type: "string",
|
|
1298
|
+
description: "Path to existing source file to compare against candidate modifications."
|
|
1299
|
+
},
|
|
1300
|
+
originalCode: {
|
|
1301
|
+
type: "string",
|
|
1302
|
+
description: "Original source code string (if not reading from an existing file)."
|
|
1303
|
+
},
|
|
1304
|
+
modifiedCode: {
|
|
1305
|
+
type: "string",
|
|
1306
|
+
description: "The candidate modified code string to validate."
|
|
1307
|
+
},
|
|
1308
|
+
lang: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
description: "Language hint (ts, py, go, rs, etc.). Inferred from file path if omitted."
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
required: ["modifiedCode"],
|
|
1314
|
+
additionalProperties: false
|
|
1315
|
+
},
|
|
1316
|
+
async execute(input, ctx) {
|
|
1317
|
+
try {
|
|
1318
|
+
let originalCode = input.originalCode;
|
|
1319
|
+
let lang = input.lang;
|
|
1320
|
+
if (!originalCode && input.file) {
|
|
1321
|
+
const resolved = await safeResolveProjectPath(input.file, ctx);
|
|
1322
|
+
originalCode = await fs2.readFile(resolved, "utf8");
|
|
1323
|
+
if (!lang) {
|
|
1324
|
+
lang = detectLang(resolved) ?? "ts";
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (originalCode === void 0) {
|
|
1328
|
+
return {
|
|
1329
|
+
valid: false,
|
|
1330
|
+
violations: [],
|
|
1331
|
+
summary: "Neither originalCode nor file was provided for invariant comparison.",
|
|
1332
|
+
error: "Missing originalCode or file path."
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
const res = await polyglotInvariantEngine.evaluate({
|
|
1336
|
+
originalCode,
|
|
1337
|
+
modifiedCode: input.modifiedCode,
|
|
1338
|
+
lang,
|
|
1339
|
+
filePath: input.file
|
|
1340
|
+
});
|
|
1341
|
+
const summary = res.valid ? "AST Invariants PASSED. Mutation is 100% backward compatible." : `AST Invariants FAILED with ${res.violations.length} violation(s):
|
|
1342
|
+
` + res.violations.map((v) => ` - [${v.ruleId}] ${v.symbolName}: ${v.message}`).join("\n");
|
|
1343
|
+
return {
|
|
1344
|
+
valid: res.valid,
|
|
1345
|
+
violations: res.violations,
|
|
1346
|
+
summary
|
|
1347
|
+
};
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
return {
|
|
1350
|
+
valid: false,
|
|
1351
|
+
violations: [],
|
|
1352
|
+
summary: `Error during invariant evaluation: ${toErrorMessage5(err)}`,
|
|
1353
|
+
error: toErrorMessage5(err)
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1348
1359
|
// src/codebase-index/codebase-outgoing-calls-tool.ts
|
|
1360
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
1349
1361
|
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
1350
1362
|
var codebaseOutgoingCallsTool = {
|
|
1351
1363
|
name: "codebase-outgoing-calls",
|
|
@@ -1383,6 +1395,12 @@ var codebaseOutgoingCallsTool = {
|
|
|
1383
1395
|
required: ["symbol"]
|
|
1384
1396
|
},
|
|
1385
1397
|
async execute(input, ctx) {
|
|
1398
|
+
if (!input?.symbol || typeof input.symbol !== "string" || !input.symbol.trim()) {
|
|
1399
|
+
throw new ToolValidationError4({
|
|
1400
|
+
message: "codebase-outgoing-calls: symbol is required and cannot be empty",
|
|
1401
|
+
field: "symbol"
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1386
1404
|
const state = getIndexState();
|
|
1387
1405
|
if (state.lastError) {
|
|
1388
1406
|
const circuit = state.circuit;
|
|
@@ -1481,9 +1499,16 @@ var codebaseOutgoingCallsTool = {
|
|
|
1481
1499
|
import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
|
|
1482
1500
|
|
|
1483
1501
|
// src/codebase-index/repo-map.ts
|
|
1484
|
-
import
|
|
1502
|
+
import { existsSync } from "node:fs";
|
|
1503
|
+
import * as path6 from "node:path";
|
|
1504
|
+
|
|
1505
|
+
// src/codebase-index/repo-map-fallback.ts
|
|
1506
|
+
import * as fs5 from "node:fs/promises";
|
|
1485
1507
|
import * as path5 from "node:path";
|
|
1486
1508
|
|
|
1509
|
+
// src/codebase-index/repo-map-render.ts
|
|
1510
|
+
import * as fs4 from "node:fs/promises";
|
|
1511
|
+
|
|
1487
1512
|
// src/codebase-index/skeleton-extractor.ts
|
|
1488
1513
|
import * as fs3 from "node:fs/promises";
|
|
1489
1514
|
import * as path4 from "node:path";
|
|
@@ -1870,40 +1895,102 @@ ${res.skeleton}`
|
|
|
1870
1895
|
};
|
|
1871
1896
|
}
|
|
1872
1897
|
|
|
1873
|
-
// src/codebase-index/repo-map.ts
|
|
1898
|
+
// src/codebase-index/repo-map-render.ts
|
|
1899
|
+
var TRUNCATION_NOTE = "\n // \u2026 signatures truncated to fit the token budget";
|
|
1900
|
+
function truncateAtLine(text, limit) {
|
|
1901
|
+
if (text.length <= limit) return text;
|
|
1902
|
+
const cut = text.slice(0, limit);
|
|
1903
|
+
const lastNewline = cut.lastIndexOf("\n");
|
|
1904
|
+
return lastNewline > 0 ? cut.slice(0, lastNewline) : cut;
|
|
1905
|
+
}
|
|
1906
|
+
var COMMENT_ONLY_LINE = /^\s*(?:\/\/|\/\*|\*\/|\*|#|--|<!--|-->)/;
|
|
1907
|
+
function stripCommentLines(body) {
|
|
1908
|
+
const kept = body.split("\n").filter((line) => !COMMENT_ONLY_LINE.test(line));
|
|
1909
|
+
const joined = kept.join("\n").trim();
|
|
1910
|
+
return joined.length > 0 ? joined : body;
|
|
1911
|
+
}
|
|
1912
|
+
async function renderSkeletonSections(candidates, charBudget, options = {}) {
|
|
1913
|
+
const sections = [];
|
|
1914
|
+
const files = [];
|
|
1915
|
+
const maxSectionChars = options.maxSectionChars ?? Number.POSITIVE_INFINITY;
|
|
1916
|
+
let chars = 0;
|
|
1917
|
+
for (const candidate of candidates) {
|
|
1918
|
+
if (chars >= charBudget) break;
|
|
1919
|
+
let content;
|
|
1920
|
+
try {
|
|
1921
|
+
content = await fs4.readFile(candidate.absolute, "utf8");
|
|
1922
|
+
} catch {
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (content.length === 0) continue;
|
|
1926
|
+
let skeleton;
|
|
1927
|
+
try {
|
|
1928
|
+
skeleton = await extractFileSkeleton({
|
|
1929
|
+
file: candidate.absolute,
|
|
1930
|
+
content,
|
|
1931
|
+
options: {
|
|
1932
|
+
exportsOnly: true,
|
|
1933
|
+
collapseImports: true,
|
|
1934
|
+
includeDocs: false,
|
|
1935
|
+
includeLineNumbers: true,
|
|
1936
|
+
...options.skeleton ?? {}
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
} catch {
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
const body = skeleton.skeleton.trim();
|
|
1943
|
+
if (!body) continue;
|
|
1944
|
+
const indented = (options.signaturesOnly === true ? stripCommentLines(body) : body).split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
1945
|
+
const header = `${candidate.relative} (${skeleton.lang}):
|
|
1946
|
+
`;
|
|
1947
|
+
const room = Math.min(maxSectionChars, charBudget - chars) - header.length - TRUNCATION_NOTE.length - 2;
|
|
1948
|
+
let section;
|
|
1949
|
+
if (indented.length <= room) {
|
|
1950
|
+
section = header + indented;
|
|
1951
|
+
} else {
|
|
1952
|
+
if (room <= 0) break;
|
|
1953
|
+
section = `${header}${truncateAtLine(indented, room)}${TRUNCATION_NOTE}`;
|
|
1954
|
+
}
|
|
1955
|
+
const sectionChars = section.length + 2;
|
|
1956
|
+
if (chars + sectionChars > charBudget && files.length > 0) break;
|
|
1957
|
+
sections.push(section);
|
|
1958
|
+
files.push(candidate.relative);
|
|
1959
|
+
chars += sectionChars;
|
|
1960
|
+
}
|
|
1961
|
+
return { sections, files, chars };
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/codebase-index/repo-map-fallback.ts
|
|
1874
1965
|
var ENTRY_POINT_REGEX = /(?:^|[/\\])(?:index|main|app|server|cli|lib|api|types|routes|mod)\.[a-zA-Z0-9]+$/i;
|
|
1875
1966
|
var TEST_OR_MOCK_REGEX = /(?:test|spec|mock|fixture|bench|example)[s]?[/\\._]/i;
|
|
1967
|
+
var ALWAYS_SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
|
|
1876
1968
|
function getEntryPointBonus(relPath) {
|
|
1877
|
-
if (TEST_OR_MOCK_REGEX.test(relPath))
|
|
1878
|
-
|
|
1879
|
-
}
|
|
1880
|
-
if (ENTRY_POINT_REGEX.test(relPath)) {
|
|
1881
|
-
return 25;
|
|
1882
|
-
}
|
|
1969
|
+
if (TEST_OR_MOCK_REGEX.test(relPath)) return -20;
|
|
1970
|
+
if (ENTRY_POINT_REGEX.test(relPath)) return 25;
|
|
1883
1971
|
const depth = relPath.split(/[/\\]/).length;
|
|
1884
1972
|
if (depth <= 2) return 10;
|
|
1885
1973
|
if (depth <= 3) return 5;
|
|
1886
1974
|
return 0;
|
|
1887
1975
|
}
|
|
1888
|
-
async function
|
|
1976
|
+
async function generateFallbackRepoMap(opts) {
|
|
1889
1977
|
const projectRoot = opts.projectRoot;
|
|
1890
|
-
const
|
|
1891
|
-
const charBudget = maxTokens * 4;
|
|
1978
|
+
const charBudget = (opts.maxTokens ?? 1200) * 4;
|
|
1892
1979
|
const focusSet = new Set((opts.focusFiles ?? []).map((f) => path5.resolve(projectRoot, f)));
|
|
1980
|
+
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
1893
1981
|
const allFiles = [];
|
|
1894
1982
|
async function scan(dir) {
|
|
1895
|
-
if (allFiles.length > 500) return;
|
|
1896
1983
|
let entries;
|
|
1897
1984
|
try {
|
|
1898
|
-
entries = await
|
|
1985
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
1899
1986
|
} catch {
|
|
1900
1987
|
return;
|
|
1901
1988
|
}
|
|
1902
1989
|
for (const entry of entries) {
|
|
1903
|
-
if (entry.name.startsWith(".") ||
|
|
1904
|
-
continue;
|
|
1905
|
-
}
|
|
1990
|
+
if (entry.name.startsWith(".") || ALWAYS_SKIP.has(entry.name)) continue;
|
|
1906
1991
|
const fullPath = path5.join(dir, entry.name);
|
|
1992
|
+
const rel = path5.relative(projectRoot, fullPath).replace(/\\/g, "/");
|
|
1993
|
+
if (isGitIgnored(rel, entry.isDirectory())) continue;
|
|
1907
1994
|
if (entry.isDirectory()) {
|
|
1908
1995
|
await scan(fullPath);
|
|
1909
1996
|
} else if (entry.isFile() && isIndexablePath(fullPath)) {
|
|
@@ -1921,58 +2008,141 @@ async function generateRepoMap(opts) {
|
|
|
1921
2008
|
rankedFiles: []
|
|
1922
2009
|
};
|
|
1923
2010
|
}
|
|
1924
|
-
const
|
|
1925
|
-
for (const file of allFiles) {
|
|
2011
|
+
const scored = allFiles.map((file) => {
|
|
1926
2012
|
const relPath = path5.relative(projectRoot, file).replace(/\\/g, "/");
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2013
|
+
const score = 10 + (focusSet.has(file) ? 100 : 0) + getEntryPointBonus(relPath);
|
|
2014
|
+
return { file, relPath, score };
|
|
2015
|
+
});
|
|
2016
|
+
scored.sort((a, b) => b.score - a.score || a.relPath.localeCompare(b.relPath));
|
|
2017
|
+
const { sections, files } = await renderSkeletonSections(
|
|
2018
|
+
scored.map((s) => ({ absolute: s.file, relative: s.relPath })),
|
|
2019
|
+
charBudget,
|
|
2020
|
+
{ skeleton: opts.options }
|
|
2021
|
+
);
|
|
2022
|
+
const map = sections.join("\n\n");
|
|
2023
|
+
return {
|
|
2024
|
+
map,
|
|
2025
|
+
filesCount: files.length,
|
|
2026
|
+
totalFilesScanned: allFiles.length,
|
|
2027
|
+
estimatedTokens: Math.ceil(map.length / 4),
|
|
2028
|
+
rankedFiles: files
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// src/codebase-index/repo-map.ts
|
|
2033
|
+
var RANKED_FETCH_LIMIT = 400;
|
|
2034
|
+
var INDEX_DB_FILE = "index.db";
|
|
2035
|
+
var MAX_CLUSTERS = 16;
|
|
2036
|
+
var MAX_HOTSPOTS = 12;
|
|
2037
|
+
var HEADER_BUDGET_SHARE = 0.4;
|
|
2038
|
+
var TARGET_BODY_FILES = 6;
|
|
2039
|
+
var MIN_SECTION_CHARS = 300;
|
|
2040
|
+
function buildClusters(rows, relativeOf, packageCounts) {
|
|
2041
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
2042
|
+
for (const row of rows) {
|
|
2043
|
+
const relative12 = relativeOf(row.file);
|
|
2044
|
+
const label = row.package || relative12.split("/")[0] || "(root)";
|
|
2045
|
+
const existing = byLabel.get(label);
|
|
2046
|
+
if (existing === void 0) {
|
|
2047
|
+
byLabel.set(label, {
|
|
2048
|
+
label,
|
|
2049
|
+
fileCount: packageCounts.get(row.package) ?? 0,
|
|
2050
|
+
rankedCount: 1,
|
|
2051
|
+
hub: relative12,
|
|
2052
|
+
hubRank: row.rank
|
|
1953
2053
|
});
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
existing.rankedCount += 1;
|
|
2057
|
+
if (row.rank > existing.hubRank) {
|
|
2058
|
+
existing.hub = relative12;
|
|
2059
|
+
existing.hubRank = row.rank;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
return [...byLabel.values()].sort(
|
|
2063
|
+
(a, b) => b.hubRank - a.hubRank || a.label.localeCompare(b.label)
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
function renderHeader(clusters, hotspots, relativeOf, budget) {
|
|
2067
|
+
const lines = ["// Repo map \u2014 ranked by graph centrality (1.00 = most central)."];
|
|
2068
|
+
if (clusters.length > 0) {
|
|
2069
|
+
lines.push("", "// Clusters (hub = most central file):");
|
|
2070
|
+
for (const cluster of clusters.slice(0, MAX_CLUSTERS)) {
|
|
2071
|
+
const size = cluster.fileCount > 0 ? `${cluster.fileCount} files` : "unlabelled";
|
|
2072
|
+
lines.push(`// ${cluster.label} (${size}) hub: ${cluster.hub}`);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (hotspots.length > 0) {
|
|
2076
|
+
lines.push("", "// Hotspots:");
|
|
2077
|
+
for (const row of hotspots.slice(0, MAX_HOTSPOTS)) {
|
|
2078
|
+
lines.push(
|
|
2079
|
+
`// ${row.rank.toFixed(2)} in=${row.inDeg} out=${row.outDeg} ${relativeOf(row.file)}`
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
while (lines.length > 1 && lines.join("\n").length + 2 > budget) lines.pop();
|
|
2084
|
+
return lines.join("\n");
|
|
2085
|
+
}
|
|
2086
|
+
async function generateRepoMap(opts) {
|
|
2087
|
+
const projectRoot = opts.projectRoot;
|
|
2088
|
+
const charBudget = (opts.maxTokens ?? 1200) * 4;
|
|
2089
|
+
let ranked = [];
|
|
2090
|
+
let packageCounts = /* @__PURE__ */ new Map();
|
|
2091
|
+
let totalRankedFiles = 0;
|
|
2092
|
+
let store;
|
|
2093
|
+
try {
|
|
2094
|
+
if (!existsSync(path6.join(resolveIndexDir(projectRoot, opts.indexDir), INDEX_DB_FILE))) {
|
|
2095
|
+
return generateFallbackRepoMap(opts);
|
|
1966
2096
|
}
|
|
2097
|
+
store = indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
|
|
2098
|
+
totalRankedFiles = store.getRankCounts().files;
|
|
2099
|
+
if (totalRankedFiles > 0) {
|
|
2100
|
+
ranked = store.getRankedFiles(RANKED_FETCH_LIMIT);
|
|
2101
|
+
packageCounts = store.getPackageFileCounts();
|
|
2102
|
+
}
|
|
2103
|
+
} catch {
|
|
2104
|
+
ranked = [];
|
|
2105
|
+
} finally {
|
|
2106
|
+
if (store !== void 0) indexStorePool.release(store);
|
|
1967
2107
|
}
|
|
1968
|
-
|
|
1969
|
-
const
|
|
2108
|
+
if (ranked.length === 0) return generateFallbackRepoMap(opts);
|
|
2109
|
+
const relativeOf = (file) => {
|
|
2110
|
+
const relative12 = path6.relative(projectRoot, file);
|
|
2111
|
+
if (!relative12 || relative12.startsWith("..")) return posixIndexPath(file);
|
|
2112
|
+
return posixIndexPath(relative12);
|
|
2113
|
+
};
|
|
2114
|
+
const clusters = buildClusters(ranked, relativeOf, packageCounts);
|
|
2115
|
+
const header = renderHeader(clusters, ranked, relativeOf, charBudget * HEADER_BUDGET_SHARE);
|
|
2116
|
+
const focus = (opts.focusFiles ?? []).map((f) => path6.resolve(projectRoot, f));
|
|
2117
|
+
const focusIndex = new Map(focus.map((file, i) => [file, i]));
|
|
2118
|
+
const candidates = ranked.map((row) => ({
|
|
2119
|
+
row,
|
|
2120
|
+
absolute: path6.resolve(projectRoot, row.file)
|
|
2121
|
+
})).sort((a, b) => {
|
|
2122
|
+
const fa = focusIndex.get(a.absolute) ?? Number.POSITIVE_INFINITY;
|
|
2123
|
+
const fb = focusIndex.get(b.absolute) ?? Number.POSITIVE_INFINITY;
|
|
2124
|
+
if (fa !== fb) return fa - fb;
|
|
2125
|
+
return b.row.rank - a.row.rank;
|
|
2126
|
+
}).map(({ row, absolute }) => ({ absolute, relative: relativeOf(row.file) }));
|
|
2127
|
+
const known = new Set(candidates.map((c) => c.absolute));
|
|
2128
|
+
const missingFocus = focus.filter((file) => !known.has(file)).map((file) => ({ absolute: file, relative: relativeOf(file) }));
|
|
2129
|
+
const bodyBudget = Math.max(0, charBudget - header.length - 2);
|
|
2130
|
+
const { sections, files } = await renderSkeletonSections(
|
|
2131
|
+
[...missingFocus, ...candidates],
|
|
2132
|
+
bodyBudget,
|
|
2133
|
+
{
|
|
2134
|
+
skeleton: opts.options,
|
|
2135
|
+
maxSectionChars: Math.max(MIN_SECTION_CHARS, bodyBudget / TARGET_BODY_FILES),
|
|
2136
|
+
signaturesOnly: true
|
|
2137
|
+
}
|
|
2138
|
+
);
|
|
2139
|
+
const map = [header, ...sections].join("\n\n");
|
|
1970
2140
|
return {
|
|
1971
2141
|
map,
|
|
1972
|
-
filesCount:
|
|
1973
|
-
totalFilesScanned:
|
|
1974
|
-
estimatedTokens,
|
|
1975
|
-
rankedFiles:
|
|
2142
|
+
filesCount: files.length,
|
|
2143
|
+
totalFilesScanned: totalRankedFiles,
|
|
2144
|
+
estimatedTokens: Math.ceil(map.length / 4),
|
|
2145
|
+
rankedFiles: files
|
|
1976
2146
|
};
|
|
1977
2147
|
}
|
|
1978
2148
|
|
|
@@ -1984,8 +2154,8 @@ var codebaseRepoMapTool = {
|
|
|
1984
2154
|
permission: "auto",
|
|
1985
2155
|
mutating: false,
|
|
1986
2156
|
capabilities: ["fs.read"],
|
|
1987
|
-
description: "Generate a
|
|
1988
|
-
usageHint: 'USE AT THE START OF COMPLEX OR REPOSITORY-WIDE TASKS:\n\n- Call with default parameters to get a global architecture map within ~1200 tokens.\n- Pass `focusFiles: ["src/auth.ts"]` to
|
|
2157
|
+
description: "Generate a centrality-ranked, token-budgeted Repository Map of the codebase (~1200 token default). Use it for orientation before a cross-file change. Ranking comes from the index's reference graph (PageRank over calls, imports, type references and inheritance), not from filenames, so the map opens with the package clusters and their hub files, then the repository-wide hotspots, then the signatures of the most central files. Use this at the beginning of tasks or when navigating unfamiliar repositories to get a bird-eye view of the architecture.",
|
|
2158
|
+
usageHint: 'USE AT THE START OF COMPLEX OR REPOSITORY-WIDE TASKS:\n\n- Call with default parameters to get a global architecture map within ~1200 tokens.\n- The `1.00 = most central` scores are relative to this repository only; never compare them across projects.\n- Pass `focusFiles: ["src/auth.ts"]` to put specific files at the head of the map alongside the central ones.\n- Use the returned line numbers (e.g. `/* L32-L45 */`) to navigate or partially read only the functions you need.\n- Falls back to a filename heuristic when the index has not been built yet; run `/codebase-reindex` to get the ranked map.',
|
|
1989
2159
|
inputSchema: {
|
|
1990
2160
|
type: "object",
|
|
1991
2161
|
properties: {
|
|
@@ -2007,7 +2177,10 @@ var codebaseRepoMapTool = {
|
|
|
2007
2177
|
const result = await generateRepoMap({
|
|
2008
2178
|
projectRoot,
|
|
2009
2179
|
maxTokens: input.maxTokens,
|
|
2010
|
-
focusFiles: input.focusFiles
|
|
2180
|
+
focusFiles: input.focusFiles,
|
|
2181
|
+
// Honour a caller-supplied index location so the map reads the same
|
|
2182
|
+
// index the other codebase-* tools do.
|
|
2183
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
2011
2184
|
});
|
|
2012
2185
|
return {
|
|
2013
2186
|
status: "ok",
|
|
@@ -2028,7 +2201,7 @@ var codebaseRepoMapTool = {
|
|
|
2028
2201
|
};
|
|
2029
2202
|
|
|
2030
2203
|
// src/codebase-index/codebase-skeleton-tool.ts
|
|
2031
|
-
import * as
|
|
2204
|
+
import * as fs6 from "node:fs/promises";
|
|
2032
2205
|
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
2033
2206
|
var codebaseSkeletonTool = {
|
|
2034
2207
|
name: "codebase-skeleton",
|
|
@@ -2073,7 +2246,7 @@ var codebaseSkeletonTool = {
|
|
|
2073
2246
|
const targetPath = await safeResolveProjectPath(input.path, ctx);
|
|
2074
2247
|
let stat3;
|
|
2075
2248
|
try {
|
|
2076
|
-
stat3 = await
|
|
2249
|
+
stat3 = await fs6.stat(targetPath);
|
|
2077
2250
|
} catch (err) {
|
|
2078
2251
|
throw new Error(
|
|
2079
2252
|
`codebase-skeleton: file or directory not found at "${input.path}": ${toErrorMessage8(err)}`
|
|
@@ -2109,7 +2282,7 @@ var codebaseSkeletonTool = {
|
|
|
2109
2282
|
files: dirResult.files
|
|
2110
2283
|
};
|
|
2111
2284
|
}
|
|
2112
|
-
const content = await
|
|
2285
|
+
const content = await fs6.readFile(targetPath, "utf8");
|
|
2113
2286
|
const fileResult = await extractFileSkeleton({
|
|
2114
2287
|
file: targetPath,
|
|
2115
2288
|
content,
|
|
@@ -2215,28 +2388,28 @@ var codebaseStatsTool = {
|
|
|
2215
2388
|
};
|
|
2216
2389
|
|
|
2217
2390
|
// src/codebase-index/codebase-targeted-test-tool.ts
|
|
2218
|
-
import * as
|
|
2219
|
-
import * as
|
|
2391
|
+
import * as fs7 from "node:fs/promises";
|
|
2392
|
+
import * as path7 from "node:path";
|
|
2220
2393
|
import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
|
|
2221
2394
|
var TEST_FILE_REGEX2 = /(?:test|spec|mock|fixture|bench)[s]?[/\\._]/i;
|
|
2222
2395
|
async function findConventionTestFiles(projectRoot, sourceRelPath) {
|
|
2223
|
-
const parsed =
|
|
2396
|
+
const parsed = path7.parse(sourceRelPath);
|
|
2224
2397
|
const candidates = [
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2398
|
+
path7.join(parsed.dir, `${parsed.name}.test${parsed.ext}`),
|
|
2399
|
+
path7.join(parsed.dir, `${parsed.name}.spec${parsed.ext}`),
|
|
2400
|
+
path7.join(parsed.dir, `${parsed.name}_test${parsed.ext}`),
|
|
2401
|
+
path7.join(parsed.dir, `test_${parsed.name}${parsed.ext}`),
|
|
2402
|
+
path7.join("tests", `${parsed.name}.test${parsed.ext}`),
|
|
2403
|
+
path7.join("tests", `${parsed.name}.spec${parsed.ext}`),
|
|
2404
|
+
path7.join("test", `${parsed.name}.test${parsed.ext}`),
|
|
2405
|
+
path7.join(parsed.dir, "__tests__", `${parsed.name}.test${parsed.ext}`),
|
|
2406
|
+
path7.join(parsed.dir, "__tests__", `${parsed.name}${parsed.ext}`)
|
|
2234
2407
|
];
|
|
2235
2408
|
const found = [];
|
|
2236
2409
|
for (const cand of candidates) {
|
|
2237
|
-
const absPath =
|
|
2410
|
+
const absPath = path7.resolve(projectRoot, cand);
|
|
2238
2411
|
try {
|
|
2239
|
-
const st = await
|
|
2412
|
+
const st = await fs7.stat(absPath);
|
|
2240
2413
|
if (st.isFile()) {
|
|
2241
2414
|
found.push(cand.replace(/\\/g, "/"));
|
|
2242
2415
|
}
|
|
@@ -2293,7 +2466,7 @@ var codebaseTargetedTestTool = {
|
|
|
2293
2466
|
transitive: true
|
|
2294
2467
|
});
|
|
2295
2468
|
for (const site of serviced.calls) {
|
|
2296
|
-
const relPath =
|
|
2469
|
+
const relPath = path7.relative(projectRoot, site.symbol.file).replace(/\\/g, "/");
|
|
2297
2470
|
if (TEST_FILE_REGEX2.test(relPath)) {
|
|
2298
2471
|
suitesSet.add(relPath);
|
|
2299
2472
|
}
|
|
@@ -2377,8 +2550,8 @@ ${streamRes.stderr}`.trim(),
|
|
|
2377
2550
|
};
|
|
2378
2551
|
|
|
2379
2552
|
// src/codebase-index/dead-code-scan.ts
|
|
2380
|
-
import * as
|
|
2381
|
-
import * as
|
|
2553
|
+
import * as fs8 from "node:fs";
|
|
2554
|
+
import * as path8 from "node:path";
|
|
2382
2555
|
var deadCodeScanTool = {
|
|
2383
2556
|
name: "dead-code-scan",
|
|
2384
2557
|
category: "Project",
|
|
@@ -2423,25 +2596,25 @@ var deadCodeScanTool = {
|
|
|
2423
2596
|
};
|
|
2424
2597
|
function tryReadJson(filePath) {
|
|
2425
2598
|
try {
|
|
2426
|
-
const raw =
|
|
2599
|
+
const raw = fs8.readFileSync(filePath, "utf8");
|
|
2427
2600
|
return JSON.parse(raw);
|
|
2428
2601
|
} catch {
|
|
2429
2602
|
return null;
|
|
2430
2603
|
}
|
|
2431
2604
|
}
|
|
2432
|
-
function resolveAgainst(base,
|
|
2433
|
-
if (
|
|
2434
|
-
return
|
|
2605
|
+
function resolveAgainst(base, relative12) {
|
|
2606
|
+
if (path8.isAbsolute(relative12)) return relative12;
|
|
2607
|
+
return path8.resolve(base, relative12);
|
|
2435
2608
|
}
|
|
2436
2609
|
function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
2437
2610
|
const entries = /* @__PURE__ */ new Set();
|
|
2438
2611
|
if (userEntryPoints) {
|
|
2439
2612
|
for (const ep of userEntryPoints) {
|
|
2440
2613
|
const resolved = resolveAgainst(projectRoot, ep);
|
|
2441
|
-
if (
|
|
2614
|
+
if (fs8.existsSync(resolved)) entries.add(resolved);
|
|
2442
2615
|
}
|
|
2443
2616
|
}
|
|
2444
|
-
const rootPkg = tryReadJson(
|
|
2617
|
+
const rootPkg = tryReadJson(path8.join(projectRoot, "package.json"));
|
|
2445
2618
|
if (rootPkg) {
|
|
2446
2619
|
addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
|
|
2447
2620
|
}
|
|
@@ -2455,45 +2628,45 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
|
2455
2628
|
workspaces = [];
|
|
2456
2629
|
}
|
|
2457
2630
|
for (const wsDir of workspaces) {
|
|
2458
|
-
const pkgJsonPath =
|
|
2631
|
+
const pkgJsonPath = path8.join(wsDir, "package.json");
|
|
2459
2632
|
const pkg = tryReadJson(pkgJsonPath);
|
|
2460
2633
|
if (pkg) {
|
|
2461
2634
|
addPkgJsonEntryPoints(wsDir, pkg, entries);
|
|
2462
|
-
const convention1 =
|
|
2463
|
-
if (
|
|
2464
|
-
const convention2 =
|
|
2465
|
-
if (
|
|
2466
|
-
const convention3 =
|
|
2467
|
-
if (
|
|
2635
|
+
const convention1 = path8.join(wsDir, "src", "index.ts");
|
|
2636
|
+
if (fs8.existsSync(convention1)) entries.add(convention1);
|
|
2637
|
+
const convention2 = path8.join(wsDir, "src", "main.ts");
|
|
2638
|
+
if (fs8.existsSync(convention2)) entries.add(convention2);
|
|
2639
|
+
const convention3 = path8.join(wsDir, "index.ts");
|
|
2640
|
+
if (fs8.existsSync(convention3)) entries.add(convention3);
|
|
2468
2641
|
}
|
|
2469
2642
|
}
|
|
2470
2643
|
if (!rootPkg || !workspaces.length) {
|
|
2471
2644
|
for (const name of ["src/index.ts", "src/main.ts", "index.ts"]) {
|
|
2472
|
-
const convention =
|
|
2473
|
-
if (
|
|
2645
|
+
const convention = path8.join(projectRoot, name);
|
|
2646
|
+
if (fs8.existsSync(convention)) entries.add(convention);
|
|
2474
2647
|
}
|
|
2475
2648
|
}
|
|
2476
2649
|
return [...entries];
|
|
2477
2650
|
}
|
|
2478
2651
|
var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
|
|
2479
|
-
var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${
|
|
2652
|
+
var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path8.sep}${d}${path8.sep}`);
|
|
2480
2653
|
function trySourceEquivalent(resolved) {
|
|
2481
|
-
resolved = resolved.replace(/[/\\]/g,
|
|
2654
|
+
resolved = resolved.replace(/[/\\]/g, path8.sep);
|
|
2482
2655
|
for (const marker of BUILD_OUTPUT_DIR_NAMES) {
|
|
2483
2656
|
const idx = resolved.indexOf(marker);
|
|
2484
2657
|
if (idx === -1) continue;
|
|
2485
|
-
const base = resolved.replace(marker, `${
|
|
2658
|
+
const base = resolved.replace(marker, `${path8.sep}src${path8.sep}`);
|
|
2486
2659
|
const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
2487
|
-
if (candidate !== base &&
|
|
2660
|
+
if (candidate !== base && fs8.existsSync(candidate)) {
|
|
2488
2661
|
return candidate;
|
|
2489
2662
|
}
|
|
2490
2663
|
const dtsStripped = base.replace(/\.d\.ts$/, "");
|
|
2491
2664
|
const candidateDts = dtsStripped + ".ts";
|
|
2492
|
-
if (candidateDts !== base && candidateDts !== candidate &&
|
|
2665
|
+
if (candidateDts !== base && candidateDts !== candidate && fs8.existsSync(candidateDts)) {
|
|
2493
2666
|
return candidateDts;
|
|
2494
2667
|
}
|
|
2495
2668
|
const candidateNoExt = base + ".ts";
|
|
2496
|
-
if (candidate !== candidateNoExt && candidateNoExt !== candidateDts &&
|
|
2669
|
+
if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs8.existsSync(candidateNoExt)) {
|
|
2497
2670
|
return candidateNoExt;
|
|
2498
2671
|
}
|
|
2499
2672
|
}
|
|
@@ -2501,9 +2674,9 @@ function trySourceEquivalent(resolved) {
|
|
|
2501
2674
|
}
|
|
2502
2675
|
function tryAddEntryPath(pkgDir, rawPath, entries) {
|
|
2503
2676
|
const resolved = resolveAgainst(pkgDir, rawPath);
|
|
2504
|
-
if (
|
|
2677
|
+
if (fs8.existsSync(resolved)) entries.add(resolved);
|
|
2505
2678
|
const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
2506
|
-
if (tsResolved !== resolved &&
|
|
2679
|
+
if (tsResolved !== resolved && fs8.existsSync(tsResolved)) {
|
|
2507
2680
|
entries.add(tsResolved);
|
|
2508
2681
|
}
|
|
2509
2682
|
const srcAlt = trySourceEquivalent(resolved);
|
|
@@ -2547,18 +2720,18 @@ function expandGlobPattern(entry, projectRoot) {
|
|
|
2547
2720
|
const dirs = [];
|
|
2548
2721
|
if (entry.includes("*")) {
|
|
2549
2722
|
const base = entry.replace(/\/\*+$/, "");
|
|
2550
|
-
const baseDir =
|
|
2723
|
+
const baseDir = path8.resolve(projectRoot, base);
|
|
2551
2724
|
try {
|
|
2552
|
-
const children =
|
|
2725
|
+
const children = fs8.readdirSync(baseDir, { withFileTypes: true });
|
|
2553
2726
|
for (const child of children) {
|
|
2554
2727
|
if (child.isDirectory()) {
|
|
2555
|
-
dirs.push(
|
|
2728
|
+
dirs.push(path8.join(baseDir, child.name));
|
|
2556
2729
|
}
|
|
2557
2730
|
}
|
|
2558
2731
|
} catch {
|
|
2559
2732
|
}
|
|
2560
2733
|
} else {
|
|
2561
|
-
dirs.push(
|
|
2734
|
+
dirs.push(path8.resolve(projectRoot, entry));
|
|
2562
2735
|
}
|
|
2563
2736
|
return dirs;
|
|
2564
2737
|
}
|
|
@@ -2575,10 +2748,10 @@ function extractWorkspaceGlobs(pkg, projectRoot) {
|
|
|
2575
2748
|
return dirs;
|
|
2576
2749
|
}
|
|
2577
2750
|
function extractPnpmWorkspaceDirs(projectRoot) {
|
|
2578
|
-
const yamlPath =
|
|
2579
|
-
if (!
|
|
2751
|
+
const yamlPath = path8.join(projectRoot, "pnpm-workspace.yaml");
|
|
2752
|
+
if (!fs8.existsSync(yamlPath)) return [];
|
|
2580
2753
|
try {
|
|
2581
|
-
const content =
|
|
2754
|
+
const content = fs8.readFileSync(yamlPath, "utf8");
|
|
2582
2755
|
const dirs = [];
|
|
2583
2756
|
let inPackages = false;
|
|
2584
2757
|
const lines = content.split("\n");
|
|
@@ -2612,8 +2785,8 @@ function extractPnpmWorkspaceDirs(projectRoot) {
|
|
|
2612
2785
|
}
|
|
2613
2786
|
function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
|
|
2614
2787
|
if (!moduleSpecifier.startsWith(".")) return [];
|
|
2615
|
-
const dir =
|
|
2616
|
-
const base =
|
|
2788
|
+
const dir = path8.dirname(importerPath);
|
|
2789
|
+
const base = path8.resolve(dir, moduleSpecifier);
|
|
2617
2790
|
const results = [];
|
|
2618
2791
|
const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
2619
2792
|
const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
|
|
@@ -2625,18 +2798,18 @@ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
|
|
|
2625
2798
|
if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
|
|
2626
2799
|
if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
|
|
2627
2800
|
if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
|
|
2628
|
-
if (indexedFiles.has(
|
|
2629
|
-
results.push(
|
|
2630
|
-
if (indexedFiles.has(
|
|
2631
|
-
results.push(
|
|
2632
|
-
if (indexedFiles.has(
|
|
2633
|
-
results.push(
|
|
2634
|
-
if (indexedFiles.has(
|
|
2635
|
-
results.push(
|
|
2636
|
-
if (indexedFiles.has(
|
|
2637
|
-
results.push(
|
|
2638
|
-
if (indexedFiles.has(
|
|
2639
|
-
results.push(
|
|
2801
|
+
if (indexedFiles.has(path8.join(candidate, "index.ts")))
|
|
2802
|
+
results.push(path8.join(candidate, "index.ts"));
|
|
2803
|
+
if (indexedFiles.has(path8.join(candidate, "index.tsx")))
|
|
2804
|
+
results.push(path8.join(candidate, "index.tsx"));
|
|
2805
|
+
if (indexedFiles.has(path8.join(candidate, "index.js")))
|
|
2806
|
+
results.push(path8.join(candidate, "index.js"));
|
|
2807
|
+
if (indexedFiles.has(path8.join(candidate, "index.jsx")))
|
|
2808
|
+
results.push(path8.join(candidate, "index.jsx"));
|
|
2809
|
+
if (indexedFiles.has(path8.join(candidate, "index.mjs")))
|
|
2810
|
+
results.push(path8.join(candidate, "index.mjs"));
|
|
2811
|
+
if (indexedFiles.has(path8.join(candidate, "index.cjs")))
|
|
2812
|
+
results.push(path8.join(candidate, "index.cjs"));
|
|
2640
2813
|
}
|
|
2641
2814
|
return [...new Set(results)];
|
|
2642
2815
|
}
|
|
@@ -2668,7 +2841,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
2668
2841
|
symbolById.set(s.id, s);
|
|
2669
2842
|
}
|
|
2670
2843
|
const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
|
|
2671
|
-
const entryFileSet = new Set(discoveredFiles.map((f) =>
|
|
2844
|
+
const entryFileSet = new Set(discoveredFiles.map((f) => path8.resolve(f)));
|
|
2672
2845
|
const indexedFiles = /* @__PURE__ */ new Set();
|
|
2673
2846
|
for (const s of allSymbols) indexedFiles.add(s.file);
|
|
2674
2847
|
for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
|
|
@@ -2694,7 +2867,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
2694
2867
|
if (scannedBarrels.has(epFile)) continue;
|
|
2695
2868
|
scannedBarrels.add(epFile);
|
|
2696
2869
|
try {
|
|
2697
|
-
const content =
|
|
2870
|
+
const content = fs8.readFileSync(epFile, "utf8");
|
|
2698
2871
|
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
2699
2872
|
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
2700
2873
|
for (; ; ) {
|
|
@@ -2784,7 +2957,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
2784
2957
|
const deadPackages = [];
|
|
2785
2958
|
const pkgEntries = findPackageEntries(projectRoot);
|
|
2786
2959
|
for (const [pkgName, pkgDir] of pkgEntries) {
|
|
2787
|
-
const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir +
|
|
2960
|
+
const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path8.sep));
|
|
2788
2961
|
if (pkgFiles.length === 0) continue;
|
|
2789
2962
|
const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
|
|
2790
2963
|
if (pkgUsed.length === 0) {
|
|
@@ -2820,7 +2993,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
2820
2993
|
}
|
|
2821
2994
|
function findPackageEntries(projectRoot) {
|
|
2822
2995
|
const pkgMap = /* @__PURE__ */ new Map();
|
|
2823
|
-
const rootPkg = tryReadJson(
|
|
2996
|
+
const rootPkg = tryReadJson(path8.join(projectRoot, "package.json"));
|
|
2824
2997
|
if (rootPkg && typeof rootPkg.name === "string") {
|
|
2825
2998
|
pkgMap.set(rootPkg.name, projectRoot);
|
|
2826
2999
|
}
|
|
@@ -2830,7 +3003,7 @@ function findPackageEntries(projectRoot) {
|
|
|
2830
3003
|
wsDirs = extractPnpmWorkspaceDirs(projectRoot);
|
|
2831
3004
|
}
|
|
2832
3005
|
for (const wsDir of wsDirs) {
|
|
2833
|
-
const wsPkg = tryReadJson(
|
|
3006
|
+
const wsPkg = tryReadJson(path8.join(wsDir, "package.json"));
|
|
2834
3007
|
if (wsPkg && typeof wsPkg.name === "string") {
|
|
2835
3008
|
pkgMap.set(wsPkg.name, wsDir);
|
|
2836
3009
|
}
|
|
@@ -2839,16 +3012,997 @@ function findPackageEntries(projectRoot) {
|
|
|
2839
3012
|
return pkgMap;
|
|
2840
3013
|
}
|
|
2841
3014
|
|
|
3015
|
+
// src/codebase-index/atlas-brief.ts
|
|
3016
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3017
|
+
import * as path10 from "node:path";
|
|
3018
|
+
|
|
3019
|
+
// src/codebase-index/atlas-projection.ts
|
|
3020
|
+
import { createHash } from "node:crypto";
|
|
3021
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
3022
|
+
import * as fs9 from "node:fs/promises";
|
|
3023
|
+
import * as path9 from "node:path";
|
|
3024
|
+
|
|
3025
|
+
// src/codebase-index/atlas-export.ts
|
|
3026
|
+
var EXPORT_PACKAGE_LIMIT = 40;
|
|
3027
|
+
var EXPORT_FILE_LIMIT = 120;
|
|
3028
|
+
var CANVAS = { width: 1e3, height: 700 };
|
|
3029
|
+
var RADIUS = { min: 14, max: 46 };
|
|
3030
|
+
var ESCAPES = {
|
|
3031
|
+
"&": "&",
|
|
3032
|
+
"<": "<",
|
|
3033
|
+
">": ">",
|
|
3034
|
+
'"': """,
|
|
3035
|
+
"'": "'"
|
|
3036
|
+
};
|
|
3037
|
+
function escapeHtml(value) {
|
|
3038
|
+
return value.replace(/[&<>"']/g, (character) => ESCAPES[character] ?? character);
|
|
3039
|
+
}
|
|
3040
|
+
function inlineJson(value) {
|
|
3041
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
3042
|
+
}
|
|
3043
|
+
function placePackages(packages) {
|
|
3044
|
+
const shown = packages.slice(0, EXPORT_PACKAGE_LIMIT);
|
|
3045
|
+
if (shown.length === 0) return [];
|
|
3046
|
+
const maxRank = shown.reduce((max, pkg) => Math.max(max, pkg.rank), 0) || 1;
|
|
3047
|
+
const centreX = CANVAS.width / 2;
|
|
3048
|
+
const centreY = CANVAS.height / 2;
|
|
3049
|
+
const placed = [];
|
|
3050
|
+
let index = 0;
|
|
3051
|
+
let ring = 0;
|
|
3052
|
+
while (index < shown.length) {
|
|
3053
|
+
const capacity = ring === 0 ? 1 : Math.min(ring * 6, shown.length - index);
|
|
3054
|
+
const radius = ring === 0 ? 0 : ring * 115;
|
|
3055
|
+
for (let slot = 0; slot < capacity && index < shown.length; slot++, index++) {
|
|
3056
|
+
const pkg = shown[index];
|
|
3057
|
+
const angle = slot / capacity * Math.PI * 2 - Math.PI / 2;
|
|
3058
|
+
placed.push({
|
|
3059
|
+
package: pkg,
|
|
3060
|
+
x: centreX + Math.cos(angle) * radius,
|
|
3061
|
+
y: centreY + Math.sin(angle) * radius * 0.68,
|
|
3062
|
+
radius: RADIUS.min + pkg.rank / maxRank * (RADIUS.max - RADIUS.min)
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
ring++;
|
|
3066
|
+
}
|
|
3067
|
+
return placed;
|
|
3068
|
+
}
|
|
3069
|
+
var unit = (value) => value.toFixed(1);
|
|
3070
|
+
function renderSvg(document) {
|
|
3071
|
+
const placed = placePackages(document.packages);
|
|
3072
|
+
if (placed.length === 0) {
|
|
3073
|
+
return '<p class="empty">No ranked packages to draw.</p>';
|
|
3074
|
+
}
|
|
3075
|
+
const at = new Map(placed.map((entry) => [entry.package.name, entry]));
|
|
3076
|
+
const maxWeight = document.edges.reduce((max, edge) => Math.max(max, edge.weight), 0) || 1;
|
|
3077
|
+
const lines = [
|
|
3078
|
+
`<svg viewBox="0 0 ${CANVAS.width} ${CANVAS.height}" role="img" aria-label="Package dependency map">`
|
|
3079
|
+
];
|
|
3080
|
+
for (const edge of document.edges) {
|
|
3081
|
+
const from = at.get(edge.from);
|
|
3082
|
+
const to = at.get(edge.to);
|
|
3083
|
+
if (from === void 0 || to === void 0 || from === to) continue;
|
|
3084
|
+
const strength = Math.min(1, edge.weight / maxWeight);
|
|
3085
|
+
lines.push(
|
|
3086
|
+
`<line x1="${unit(from.x)}" y1="${unit(from.y)}" x2="${unit(to.x)}" y2="${unit(to.y)}" stroke-width="${(0.5 + strength * 2.5).toFixed(2)}" stroke-opacity="${(0.12 + strength * 0.4).toFixed(2)}" />`
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
for (const entry of placed) {
|
|
3090
|
+
const name = escapeHtml(entry.package.name);
|
|
3091
|
+
const summary = entry.package.summary ?? "";
|
|
3092
|
+
const title = escapeHtml(
|
|
3093
|
+
`${entry.package.name} \u2014 ${entry.package.files} files, rank ${entry.package.rank.toFixed(4)}${summary === "" ? "" : `
|
|
3094
|
+
${summary}`}`
|
|
3095
|
+
);
|
|
3096
|
+
lines.push(
|
|
3097
|
+
`<g class="pkg" data-package="${name}" tabindex="0">`,
|
|
3098
|
+
`<title>${title}</title>`,
|
|
3099
|
+
`<circle cx="${unit(entry.x)}" cy="${unit(entry.y)}" r="${unit(entry.radius)}" />`,
|
|
3100
|
+
`<text x="${unit(entry.x)}" y="${unit(entry.y + entry.radius + 13)}">${name}</text>`,
|
|
3101
|
+
"</g>"
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
lines.push("</svg>");
|
|
3105
|
+
return lines.join("\n");
|
|
3106
|
+
}
|
|
3107
|
+
function renderFileRows(document) {
|
|
3108
|
+
return document.files.slice(0, EXPORT_FILE_LIMIT).map((file) => {
|
|
3109
|
+
const declarations = file.symbols.slice(0, 4).map((symbol) => escapeHtml(symbol.name)).join(", ");
|
|
3110
|
+
const description = file.concept ?? declarations;
|
|
3111
|
+
return `<tr data-package="${escapeHtml(file.package)}"><td class="num">${file.rank.toFixed(4)}</td><td class="path">${escapeHtml(file.path)}</td><td class="num">${file.inDeg}</td><td class="num">${file.outDeg}</td><td class="desc">${escapeHtml(description)}</td></tr>`;
|
|
3112
|
+
}).join("\n");
|
|
3113
|
+
}
|
|
3114
|
+
function renderAtlasHtml(document, options = {}) {
|
|
3115
|
+
const name = options.projectName ?? "Codebase";
|
|
3116
|
+
const described = document.packages.filter((pkg) => pkg.summary !== void 0);
|
|
3117
|
+
return `<!doctype html>
|
|
3118
|
+
<html lang="en">
|
|
3119
|
+
<head>
|
|
3120
|
+
<meta charset="utf-8" />
|
|
3121
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3122
|
+
<title>${escapeHtml(name)} \u2014 Codebase Atlas</title>
|
|
3123
|
+
<style>
|
|
3124
|
+
:root {
|
|
3125
|
+
color-scheme: light dark;
|
|
3126
|
+
--bg: #ffffff; --fg: #16181d; --muted: #61656e; --line: #d9dce2;
|
|
3127
|
+
--accent: #3b5bdb; --panel: #f6f7f9;
|
|
3128
|
+
}
|
|
3129
|
+
@media (prefers-color-scheme: dark) {
|
|
3130
|
+
:root { --bg: #101216; --fg: #e6e8ec; --muted: #9aa0ab; --line: #2a2e36;
|
|
3131
|
+
--accent: #7c93f5; --panel: #171a20; }
|
|
3132
|
+
}
|
|
3133
|
+
* { box-sizing: border-box; }
|
|
3134
|
+
body { margin: 0; background: var(--bg); color: var(--fg);
|
|
3135
|
+
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
|
|
3136
|
+
header { padding: 24px 28px 12px; border-bottom: 1px solid var(--line); }
|
|
3137
|
+
h1 { margin: 0 0 4px; font-size: 20px; }
|
|
3138
|
+
h2 { margin: 28px 0 10px; font-size: 15px; text-transform: uppercase;
|
|
3139
|
+
letter-spacing: .12em; color: var(--muted); }
|
|
3140
|
+
main { padding: 8px 28px 48px; max-width: 1180px; margin: 0 auto; }
|
|
3141
|
+
.counts { color: var(--muted); font-size: 12px; }
|
|
3142
|
+
figure { margin: 16px 0 0; border: 1px solid var(--line); background: var(--panel); }
|
|
3143
|
+
svg { display: block; width: 100%; height: auto; }
|
|
3144
|
+
svg line { stroke: var(--accent); }
|
|
3145
|
+
svg circle { fill: var(--accent); fill-opacity: .22; stroke: var(--accent); stroke-width: 1.5; }
|
|
3146
|
+
svg text { fill: var(--fg); font-size: 10px; text-anchor: middle; font-family: ui-monospace, monospace; }
|
|
3147
|
+
svg .pkg { cursor: pointer; }
|
|
3148
|
+
svg .pkg:hover circle, svg .pkg:focus circle { fill-opacity: .5; outline: none; }
|
|
3149
|
+
svg .pkg.muted { opacity: .25; }
|
|
3150
|
+
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
3151
|
+
th, td { padding: 5px 8px; border-bottom: 1px solid var(--line); text-align: left;
|
|
3152
|
+
vertical-align: top; }
|
|
3153
|
+
th { color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase;
|
|
3154
|
+
letter-spacing: .08em; }
|
|
3155
|
+
td.num { text-align: right; font-family: ui-monospace, monospace; white-space: nowrap; }
|
|
3156
|
+
td.path { font-family: ui-monospace, monospace; }
|
|
3157
|
+
td.desc { color: var(--muted); }
|
|
3158
|
+
tr[hidden] { display: none; }
|
|
3159
|
+
ul.subsystems { list-style: none; padding: 0; margin: 0; }
|
|
3160
|
+
ul.subsystems li { padding: 6px 0; border-bottom: 1px solid var(--line); }
|
|
3161
|
+
ul.subsystems b { font-family: ui-monospace, monospace; }
|
|
3162
|
+
.filter { margin: 10px 0 0; font-size: 12px; color: var(--muted); }
|
|
3163
|
+
.filter button { font: inherit; color: var(--accent); background: none; border: 0;
|
|
3164
|
+
cursor: pointer; padding: 0; text-decoration: underline; }
|
|
3165
|
+
.empty { color: var(--muted); }
|
|
3166
|
+
.wrap { overflow-x: auto; }
|
|
3167
|
+
footer { color: var(--muted); font-size: 11px; padding: 0 28px 32px; max-width: 1180px;
|
|
3168
|
+
margin: 0 auto; }
|
|
3169
|
+
</style>
|
|
3170
|
+
</head>
|
|
3171
|
+
<body>
|
|
3172
|
+
<header>
|
|
3173
|
+
<h1>${escapeHtml(name)} \u2014 Codebase Atlas</h1>
|
|
3174
|
+
<p class="counts">${document.counts.files} files \xB7 ${document.counts.symbols} symbols \xB7 ${document.counts.packages} packages \xB7 ranking is PageRank over the reference graph, relative to this repository</p>
|
|
3175
|
+
</header>
|
|
3176
|
+
<main>
|
|
3177
|
+
<h2>Package map</h2>
|
|
3178
|
+
<figure>${renderSvg(document)}</figure>
|
|
3179
|
+
<p class="filter" id="filter">Click a package to filter the table. <button type="button" id="clear" hidden>Clear filter</button></p>
|
|
3180
|
+
${described.length === 0 ? "" : ` <h2>Subsystems</h2>
|
|
3181
|
+
<ul class="subsystems">
|
|
3182
|
+
${described.map((pkg) => ` <li><b>${escapeHtml(pkg.name)}</b> \u2014 ${escapeHtml(pkg.summary ?? "")}</li>`).join("\n")}
|
|
3183
|
+
</ul>
|
|
3184
|
+
`} <h2>Most central files</h2>
|
|
3185
|
+
<div class="wrap">
|
|
3186
|
+
<table>
|
|
3187
|
+
<thead><tr><th>Rank</th><th>File</th><th>In</th><th>Out</th><th>What it is</th></tr></thead>
|
|
3188
|
+
<tbody id="files">
|
|
3189
|
+
${renderFileRows(document)}
|
|
3190
|
+
</tbody>
|
|
3191
|
+
</table>
|
|
3192
|
+
</div>
|
|
3193
|
+
</main>
|
|
3194
|
+
<footer>Generated from the codebase index by <code>/codebase-map --export</code>. Ranks are relative to this repository and mean nothing across repositories.</footer>
|
|
3195
|
+
<script id="atlas" type="application/json">${inlineJson({
|
|
3196
|
+
counts: document.counts,
|
|
3197
|
+
packages: document.packages,
|
|
3198
|
+
edges: document.edges
|
|
3199
|
+
})}</script>
|
|
3200
|
+
<script>
|
|
3201
|
+
(function () {
|
|
3202
|
+
var rows = Array.prototype.slice.call(document.querySelectorAll('#files tr'));
|
|
3203
|
+
var groups = Array.prototype.slice.call(document.querySelectorAll('svg .pkg'));
|
|
3204
|
+
var clear = document.getElementById('clear');
|
|
3205
|
+
var active = null;
|
|
3206
|
+
|
|
3207
|
+
function apply(name) {
|
|
3208
|
+
active = name;
|
|
3209
|
+
rows.forEach(function (row) {
|
|
3210
|
+
row.hidden = name !== null && row.getAttribute('data-package') !== name;
|
|
3211
|
+
});
|
|
3212
|
+
groups.forEach(function (group) {
|
|
3213
|
+
group.classList.toggle('muted', name !== null && group.getAttribute('data-package') !== name);
|
|
3214
|
+
});
|
|
3215
|
+
clear.hidden = name === null;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
groups.forEach(function (group) {
|
|
3219
|
+
var name = group.getAttribute('data-package');
|
|
3220
|
+
function toggle() { apply(active === name ? null : name); }
|
|
3221
|
+
group.addEventListener('click', toggle);
|
|
3222
|
+
group.addEventListener('keydown', function (event) {
|
|
3223
|
+
if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggle(); }
|
|
3224
|
+
});
|
|
3225
|
+
});
|
|
3226
|
+
clear.addEventListener('click', function () { apply(null); });
|
|
3227
|
+
})();
|
|
3228
|
+
</script>
|
|
3229
|
+
</body>
|
|
3230
|
+
</html>
|
|
3231
|
+
`;
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// src/codebase-index/atlas-projection.ts
|
|
3235
|
+
var ATLAS_DIR = path9.join(".wrongstack", "atlas");
|
|
3236
|
+
var ATLAS_JSON = "atlas.json";
|
|
3237
|
+
var ATLAS_MARKDOWN = "ATLAS.md";
|
|
3238
|
+
var ATLAS_MANIFEST = "manifest.json";
|
|
3239
|
+
var ATLAS_SCHEMA = 2;
|
|
3240
|
+
var ATLAS_FILE_LIMIT = 300;
|
|
3241
|
+
var SYMBOLS_PER_FILE = 6;
|
|
3242
|
+
var MARKDOWN_FILE_LIMIT = 60;
|
|
3243
|
+
var MARKDOWN_SYMBOLS_PER_FILE = 3;
|
|
3244
|
+
var RANK_PRECISION = 4;
|
|
3245
|
+
function round(value) {
|
|
3246
|
+
return Number(value.toFixed(RANK_PRECISION));
|
|
3247
|
+
}
|
|
3248
|
+
function relativeFactory(projectRoot) {
|
|
3249
|
+
return (file) => {
|
|
3250
|
+
const relative12 = path9.relative(projectRoot, file);
|
|
3251
|
+
if (!relative12 || relative12.startsWith("..")) return posixIndexPath(file);
|
|
3252
|
+
return posixIndexPath(relative12);
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
function digestOf(entries) {
|
|
3256
|
+
const hash = createHash("sha256");
|
|
3257
|
+
for (const [file, contentHash] of [...entries].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
|
|
3258
|
+
hash.update(file);
|
|
3259
|
+
hash.update("\0");
|
|
3260
|
+
hash.update(contentHash);
|
|
3261
|
+
hash.update("\n");
|
|
3262
|
+
}
|
|
3263
|
+
return hash.digest("hex");
|
|
3264
|
+
}
|
|
3265
|
+
function renderMarkdown(document) {
|
|
3266
|
+
const lines = [
|
|
3267
|
+
"# Codebase Atlas",
|
|
3268
|
+
"",
|
|
3269
|
+
"Generated from the codebase index. Do not edit by hand \u2014 run `/codebase-map --write`.",
|
|
3270
|
+
"",
|
|
3271
|
+
`- Indexed files: ${document.counts.files}`,
|
|
3272
|
+
`- Indexed symbols: ${document.counts.symbols}`,
|
|
3273
|
+
`- Packages: ${document.counts.packages}`,
|
|
3274
|
+
"",
|
|
3275
|
+
"Ranking is PageRank over the reference graph (calls, imports, type references,",
|
|
3276
|
+
"inheritance). `1.0000` is the most central file in this repository; the scores",
|
|
3277
|
+
"are relative to this repository only.",
|
|
3278
|
+
"",
|
|
3279
|
+
"## Packages",
|
|
3280
|
+
"",
|
|
3281
|
+
"| Package | Files | Hub | Rank |",
|
|
3282
|
+
"| --- | ---: | --- | ---: |"
|
|
3283
|
+
];
|
|
3284
|
+
for (const pkg of document.packages) {
|
|
3285
|
+
lines.push(`| ${pkg.name} | ${pkg.files} | \`${pkg.hub}\` | ${pkg.rank.toFixed(4)} |`);
|
|
3286
|
+
}
|
|
3287
|
+
const described = document.packages.filter((pkg) => pkg.summary !== void 0);
|
|
3288
|
+
if (described.length > 0) {
|
|
3289
|
+
lines.push("", "## Subsystems", "");
|
|
3290
|
+
for (const pkg of described) {
|
|
3291
|
+
lines.push(`- **${pkg.name}** \u2014 ${pkg.summary}`);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
lines.push(
|
|
3295
|
+
"",
|
|
3296
|
+
`## Most central files (top ${Math.min(MARKDOWN_FILE_LIMIT, document.files.length)} of ${document.files.length})`,
|
|
3297
|
+
"",
|
|
3298
|
+
`The full ranking, with every declaration, is in \`${ATLAS_JSON}\`.`,
|
|
3299
|
+
"",
|
|
3300
|
+
"| Rank | File | In | Out | Declarations |",
|
|
3301
|
+
"| ---: | --- | ---: | ---: | --- |"
|
|
3302
|
+
);
|
|
3303
|
+
for (const file of document.files.slice(0, MARKDOWN_FILE_LIMIT)) {
|
|
3304
|
+
const declarations = file.symbols.slice(0, MARKDOWN_SYMBOLS_PER_FILE).map((symbol) => `\`${symbol.name}\``).join(", ");
|
|
3305
|
+
lines.push(
|
|
3306
|
+
`| ${file.rank.toFixed(4)} | \`${file.path}\` | ${file.inDeg} | ${file.outDeg} | ${declarations} |`
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
return `${lines.join("\n").trimEnd()}
|
|
3310
|
+
`;
|
|
3311
|
+
}
|
|
3312
|
+
function buildAtlas(store, projectRoot) {
|
|
3313
|
+
const relativeOf = relativeFactory(projectRoot);
|
|
3314
|
+
const stats = store.getStats();
|
|
3315
|
+
const packageCounts = store.getPackageFileCounts();
|
|
3316
|
+
const ranked = store.getRankedFiles(ATLAS_FILE_LIMIT);
|
|
3317
|
+
const concepts = store.getReadyConceptSummaries();
|
|
3318
|
+
const subsystemSummaries = new Map(
|
|
3319
|
+
store.getSubsystems().map((subsystem) => [subsystem.id, subsystem.summary])
|
|
3320
|
+
);
|
|
3321
|
+
const packages = /* @__PURE__ */ new Map();
|
|
3322
|
+
const packageNameOf = (row) => row.package || relativeOf(row.file).split("/")[0] || "(root)";
|
|
3323
|
+
for (const row of ranked) {
|
|
3324
|
+
const relative12 = relativeOf(row.file);
|
|
3325
|
+
const name = packageNameOf(row);
|
|
3326
|
+
const existing = packages.get(name);
|
|
3327
|
+
if (existing === void 0) {
|
|
3328
|
+
const summary = subsystemSummaries.get(name);
|
|
3329
|
+
packages.set(name, {
|
|
3330
|
+
name,
|
|
3331
|
+
files: packageCounts.get(row.package) ?? 0,
|
|
3332
|
+
hub: relative12,
|
|
3333
|
+
rank: round(row.rank),
|
|
3334
|
+
// Absent rather than empty: an un-enriched repository must project the
|
|
3335
|
+
// same bytes it projected before the concept layer existed.
|
|
3336
|
+
...summary !== void 0 && summary !== "" ? { summary } : {}
|
|
3337
|
+
});
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
if (row.rank > existing.rank) {
|
|
3341
|
+
existing.hub = relative12;
|
|
3342
|
+
existing.rank = round(row.rank);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
const files = ranked.map((row) => {
|
|
3346
|
+
const symbols = store.getFileSymbols(row.file, SYMBOLS_PER_FILE).map((symbol) => ({ name: symbol.name, kind: symbol.kind, line: symbol.line }));
|
|
3347
|
+
const concept = concepts.get(row.file);
|
|
3348
|
+
return {
|
|
3349
|
+
path: relativeOf(row.file),
|
|
3350
|
+
rank: round(row.rank),
|
|
3351
|
+
inDeg: row.inDeg,
|
|
3352
|
+
outDeg: row.outDeg,
|
|
3353
|
+
package: packageNameOf(row),
|
|
3354
|
+
symbols,
|
|
3355
|
+
...concept !== void 0 && concept !== "" ? { concept } : {}
|
|
3356
|
+
};
|
|
3357
|
+
});
|
|
3358
|
+
const known = new Set(packages.keys());
|
|
3359
|
+
const edges = store.getPackageGraph().edges.map((edge) => ({
|
|
3360
|
+
from: edge.source.replace(/^pkg:/, ""),
|
|
3361
|
+
to: edge.target.replace(/^pkg:/, ""),
|
|
3362
|
+
weight: edge.weight,
|
|
3363
|
+
refType: edge.refType
|
|
3364
|
+
})).filter((edge) => known.has(edge.from) && known.has(edge.to)).sort(
|
|
3365
|
+
(a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.refType.localeCompare(b.refType)
|
|
3366
|
+
);
|
|
3367
|
+
const document = {
|
|
3368
|
+
schema: ATLAS_SCHEMA,
|
|
3369
|
+
counts: { files: stats.totalFiles, symbols: stats.totalSymbols, packages: packages.size },
|
|
3370
|
+
packages: [...packages.values()].sort(
|
|
3371
|
+
(a, b) => b.rank - a.rank || a.name.localeCompare(b.name)
|
|
3372
|
+
),
|
|
3373
|
+
edges,
|
|
3374
|
+
files
|
|
3375
|
+
};
|
|
3376
|
+
const allHashes = store.getAllFileMetas().map((meta) => [relativeOf(meta.file), meta.contentHash ?? ""]);
|
|
3377
|
+
const atlasPaths = new Set(files.map((file) => file.path));
|
|
3378
|
+
const manifestFiles = {};
|
|
3379
|
+
for (const [file, hash] of [...allHashes].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
|
|
3380
|
+
if (atlasPaths.has(file)) manifestFiles[file] = hash;
|
|
3381
|
+
}
|
|
3382
|
+
const manifest = {
|
|
3383
|
+
schema: ATLAS_SCHEMA,
|
|
3384
|
+
counts: {
|
|
3385
|
+
files: document.counts.files,
|
|
3386
|
+
symbols: document.counts.symbols,
|
|
3387
|
+
tracked: allHashes.length
|
|
3388
|
+
},
|
|
3389
|
+
digest: digestOf(allHashes),
|
|
3390
|
+
files: manifestFiles
|
|
3391
|
+
};
|
|
3392
|
+
return { document, manifest, markdown: renderMarkdown(document) };
|
|
3393
|
+
}
|
|
3394
|
+
function stableJson(value) {
|
|
3395
|
+
return `${JSON.stringify(value, null, 2)}
|
|
3396
|
+
`;
|
|
3397
|
+
}
|
|
3398
|
+
async function writeAtlas(store, projectRoot) {
|
|
3399
|
+
const projection = buildAtlas(store, projectRoot);
|
|
3400
|
+
const dir = path9.join(projectRoot, ATLAS_DIR);
|
|
3401
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
3402
|
+
const written = [
|
|
3403
|
+
[ATLAS_JSON, stableJson(projection.document)],
|
|
3404
|
+
[ATLAS_MANIFEST, stableJson(projection.manifest)],
|
|
3405
|
+
[ATLAS_MARKDOWN, projection.markdown]
|
|
3406
|
+
];
|
|
3407
|
+
for (const [name, body] of written) {
|
|
3408
|
+
await fs9.writeFile(path9.join(dir, name), body, "utf8");
|
|
3409
|
+
}
|
|
3410
|
+
return {
|
|
3411
|
+
dir: posixIndexPath(ATLAS_DIR),
|
|
3412
|
+
files: written.map(([name]) => name),
|
|
3413
|
+
fileCount: projection.document.files.length,
|
|
3414
|
+
packageCount: projection.document.packages.length
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
var MAX_REPORTED_DRIFT = 20;
|
|
3418
|
+
async function checkAtlasFreshness(store, projectRoot) {
|
|
3419
|
+
const stale = (reason) => ({
|
|
3420
|
+
fresh: false,
|
|
3421
|
+
reason,
|
|
3422
|
+
changed: [],
|
|
3423
|
+
removed: [],
|
|
3424
|
+
added: 0,
|
|
3425
|
+
digestMatches: false
|
|
3426
|
+
});
|
|
3427
|
+
let raw;
|
|
3428
|
+
try {
|
|
3429
|
+
raw = await fs9.readFile(path9.join(projectRoot, ATLAS_DIR, ATLAS_MANIFEST), "utf8");
|
|
3430
|
+
} catch {
|
|
3431
|
+
return stale("missing");
|
|
3432
|
+
}
|
|
3433
|
+
let manifest;
|
|
3434
|
+
try {
|
|
3435
|
+
manifest = JSON.parse(raw);
|
|
3436
|
+
} catch {
|
|
3437
|
+
return stale("unreadable");
|
|
3438
|
+
}
|
|
3439
|
+
if (manifest?.schema !== ATLAS_SCHEMA || typeof manifest.files !== "object") {
|
|
3440
|
+
return stale("schema");
|
|
3441
|
+
}
|
|
3442
|
+
const relativeOf = relativeFactory(projectRoot);
|
|
3443
|
+
const live = /* @__PURE__ */ new Map();
|
|
3444
|
+
const allHashes = [];
|
|
3445
|
+
for (const meta of store.getAllFileMetas()) {
|
|
3446
|
+
const relative12 = relativeOf(meta.file);
|
|
3447
|
+
const hash = meta.contentHash ?? "";
|
|
3448
|
+
live.set(relative12, hash);
|
|
3449
|
+
allHashes.push([relative12, hash]);
|
|
3450
|
+
}
|
|
3451
|
+
const changed = [];
|
|
3452
|
+
const removed = [];
|
|
3453
|
+
for (const [file, hash] of Object.entries(manifest.files)) {
|
|
3454
|
+
const current = live.get(file);
|
|
3455
|
+
if (current === void 0) {
|
|
3456
|
+
removed.push(file);
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
if (hash !== "" && current !== "" && hash !== current) changed.push(file);
|
|
3460
|
+
}
|
|
3461
|
+
const digestMatches = digestOf(allHashes) === manifest.digest;
|
|
3462
|
+
const tracked = typeof manifest.counts?.tracked === "number" ? manifest.counts.tracked : live.size;
|
|
3463
|
+
const added = Math.max(0, live.size - tracked);
|
|
3464
|
+
const fresh = digestMatches && changed.length === 0 && removed.length === 0;
|
|
3465
|
+
return {
|
|
3466
|
+
fresh,
|
|
3467
|
+
...fresh ? {} : { reason: "drift" },
|
|
3468
|
+
changed: changed.sort().slice(0, MAX_REPORTED_DRIFT),
|
|
3469
|
+
removed: removed.sort().slice(0, MAX_REPORTED_DRIFT),
|
|
3470
|
+
added,
|
|
3471
|
+
digestMatches
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
function hasIndex(projectRoot, indexDir) {
|
|
3475
|
+
return existsSync3(path9.join(resolveIndexDir(projectRoot, indexDir), "index.db"));
|
|
3476
|
+
}
|
|
3477
|
+
async function withStore(projectRoot, indexDir, job) {
|
|
3478
|
+
if (!hasIndex(projectRoot, indexDir)) return { indexed: false };
|
|
3479
|
+
const store = indexStorePool.acquire(projectRoot, { indexDir });
|
|
3480
|
+
try {
|
|
3481
|
+
return await job(store);
|
|
3482
|
+
} finally {
|
|
3483
|
+
indexStorePool.release(store);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
function writeProjectAtlas(projectRoot, opts = {}) {
|
|
3487
|
+
return withStore(projectRoot, opts.indexDir, (store) => writeAtlas(store, projectRoot));
|
|
3488
|
+
}
|
|
3489
|
+
function checkProjectAtlasFreshness(projectRoot, opts = {}) {
|
|
3490
|
+
return withStore(projectRoot, opts.indexDir, (store) => checkAtlasFreshness(store, projectRoot));
|
|
3491
|
+
}
|
|
3492
|
+
function exportProjectAtlasHtml(projectRoot, opts = {}) {
|
|
3493
|
+
return withStore(
|
|
3494
|
+
projectRoot,
|
|
3495
|
+
opts.indexDir,
|
|
3496
|
+
(store) => renderAtlasHtml(buildAtlas(store, projectRoot).document, {
|
|
3497
|
+
projectName: opts.projectName ?? path9.basename(projectRoot)
|
|
3498
|
+
})
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// src/codebase-index/atlas-brief.ts
|
|
3503
|
+
var BRIEF_HUB_LIMIT = 20;
|
|
3504
|
+
var BRIEF_PACKAGE_LIMIT = 12;
|
|
3505
|
+
var BRIEF_SUBSYSTEM_LIMIT = 10;
|
|
3506
|
+
var BRIEF_SUMMARY_CHARS = 160;
|
|
3507
|
+
function trim(text) {
|
|
3508
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
3509
|
+
return clean.length > BRIEF_SUMMARY_CHARS ? `${clean.slice(0, BRIEF_SUMMARY_CHARS - 1).trimEnd()}\u2026` : clean;
|
|
3510
|
+
}
|
|
3511
|
+
async function buildAtlasBrief(store, projectRoot) {
|
|
3512
|
+
const relativeOf = (file) => {
|
|
3513
|
+
const relative12 = path10.relative(projectRoot, file);
|
|
3514
|
+
return posixIndexPath(relative12 && !relative12.startsWith("..") ? relative12 : file);
|
|
3515
|
+
};
|
|
3516
|
+
const stats = store.getStats();
|
|
3517
|
+
const concepts = store.getReadyConceptSummaries();
|
|
3518
|
+
const ranked = store.getRankedFiles(BRIEF_HUB_LIMIT);
|
|
3519
|
+
const hubs = ranked.map((row) => {
|
|
3520
|
+
const concept = concepts.get(row.file);
|
|
3521
|
+
return {
|
|
3522
|
+
path: relativeOf(row.file),
|
|
3523
|
+
rank: row.rank,
|
|
3524
|
+
...concept !== void 0 && concept !== "" ? { concept: trim(concept) } : {}
|
|
3525
|
+
};
|
|
3526
|
+
});
|
|
3527
|
+
const counts = store.getPackageFileCounts();
|
|
3528
|
+
const hubOf = /* @__PURE__ */ new Map();
|
|
3529
|
+
for (const row of store.getRankedFiles(Math.max(counts.size * 4, BRIEF_HUB_LIMIT))) {
|
|
3530
|
+
const name = row.package || relativeOf(row.file).split("/")[0] || "(root)";
|
|
3531
|
+
const current = hubOf.get(name);
|
|
3532
|
+
if (current === void 0 || row.rank > current.rank) {
|
|
3533
|
+
hubOf.set(name, { file: relativeOf(row.file), rank: row.rank });
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
const packages = [...hubOf.entries()].sort((a, b) => b[1].rank - a[1].rank || a[0].localeCompare(b[0])).slice(0, BRIEF_PACKAGE_LIMIT).map(([name, hub]) => ({ name, files: counts.get(name) ?? 0, hub: hub.file }));
|
|
3537
|
+
const subsystems = store.getSubsystems().filter((subsystem) => subsystem.summary !== "").slice(0, BRIEF_SUBSYSTEM_LIMIT).map((subsystem) => ({ name: subsystem.name, summary: trim(subsystem.summary) }));
|
|
3538
|
+
const brief = {
|
|
3539
|
+
ranked: hubs.length > 0,
|
|
3540
|
+
counts: {
|
|
3541
|
+
files: stats.totalFiles,
|
|
3542
|
+
symbols: stats.totalSymbols,
|
|
3543
|
+
packages: counts.size
|
|
3544
|
+
},
|
|
3545
|
+
hubs,
|
|
3546
|
+
packages,
|
|
3547
|
+
subsystems
|
|
3548
|
+
};
|
|
3549
|
+
try {
|
|
3550
|
+
const freshness = await checkAtlasFreshness(store, projectRoot);
|
|
3551
|
+
if (freshness.reason !== "missing") {
|
|
3552
|
+
brief.staleFiles = freshness.changed.length + freshness.removed.length + freshness.added;
|
|
3553
|
+
}
|
|
3554
|
+
} catch {
|
|
3555
|
+
}
|
|
3556
|
+
return brief;
|
|
3557
|
+
}
|
|
3558
|
+
async function buildProjectAtlasBrief(projectRoot, opts = {}) {
|
|
3559
|
+
if (!existsSync4(path10.join(resolveIndexDir(projectRoot, opts.indexDir), "index.db"))) {
|
|
3560
|
+
return { indexed: false };
|
|
3561
|
+
}
|
|
3562
|
+
const store = indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
|
|
3563
|
+
try {
|
|
3564
|
+
return await buildAtlasBrief(store, projectRoot);
|
|
3565
|
+
} finally {
|
|
3566
|
+
indexStorePool.release(store);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
// src/codebase-index/codebase-context-tool.ts
|
|
3571
|
+
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
3572
|
+
import { toErrorMessage as toErrorMessage11 } from "@wrongstack/core/utils";
|
|
3573
|
+
var TOOL_TIMEOUT_MS = 35e3;
|
|
3574
|
+
var VECTOR_HIT_LIMIT = 12;
|
|
3575
|
+
var VECTOR_MIN_SCORE = 0.25;
|
|
3576
|
+
var queryEmbedder;
|
|
3577
|
+
function setContextQueryEmbedder(port) {
|
|
3578
|
+
queryEmbedder = port;
|
|
3579
|
+
}
|
|
3580
|
+
async function semanticSeeds(projectRoot, indexDir, query) {
|
|
3581
|
+
const port = queryEmbedder;
|
|
3582
|
+
if (port === void 0) return [];
|
|
3583
|
+
try {
|
|
3584
|
+
const [vector] = await port.embed([query]);
|
|
3585
|
+
if (vector === void 0 || vector.length !== port.dimensions) return [];
|
|
3586
|
+
const result = await codebaseVectorSearch({
|
|
3587
|
+
projectRoot,
|
|
3588
|
+
indexDir,
|
|
3589
|
+
vector: Array.from(vector),
|
|
3590
|
+
limit: VECTOR_HIT_LIMIT,
|
|
3591
|
+
minScore: VECTOR_MIN_SCORE
|
|
3592
|
+
});
|
|
3593
|
+
return result.hits;
|
|
3594
|
+
} catch {
|
|
3595
|
+
return [];
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
var codebaseContextTool = {
|
|
3599
|
+
name: "codebase-context",
|
|
3600
|
+
category: "Project",
|
|
3601
|
+
icon: "index",
|
|
3602
|
+
permission: "auto",
|
|
3603
|
+
mutating: false,
|
|
3604
|
+
capabilities: ["fs.read"],
|
|
3605
|
+
timeoutMs: TOOL_TIMEOUT_MS,
|
|
3606
|
+
description: "Find the files and declarations a task touches, from a plain-language description. Combines ranked symbol search with a personalised walk over the reference graph, so results include not just what matched by name but what those matches are structurally attached to \u2014 the interface behind a handler, the store behind a route, the module every match imports. Returns ranked files with the relevant declarations, their signatures and line numbers.",
|
|
3607
|
+
usageHint: 'START HERE FOR ANY TASK THAT SPANS MORE THAN ONE FILE:\n\n- Describe the task, not a symbol name: `codebase-context({ query: "retry a provider request after a 429" })`.\n- Prefer this over `codebase-search` when you do not already know the symbol you want; prefer `codebase-search` when you do.\n- Use the returned `file` + `line` to `read` only the parts you need, instead of reading whole files.\n- `matched: true` means the file matched the query directly; the rest were reached through the graph.\n- `relevance` is relative to this answer only (top result is always 1.0) \u2014 never compare it across queries.\n- Narrow with `pathPrefix: "packages/core"` when you already know the area.\n- `indexStatus: "no-index"` means run `/codebase-reindex` first.',
|
|
3608
|
+
inputSchema: {
|
|
3609
|
+
type: "object",
|
|
3610
|
+
properties: {
|
|
3611
|
+
query: {
|
|
3612
|
+
type: "string",
|
|
3613
|
+
description: "What you are trying to do or understand, in plain words."
|
|
3614
|
+
},
|
|
3615
|
+
limit: {
|
|
3616
|
+
type: "number",
|
|
3617
|
+
description: "Number of files to return (default 12, max 50)."
|
|
3618
|
+
},
|
|
3619
|
+
symbolsPerFile: {
|
|
3620
|
+
type: "number",
|
|
3621
|
+
description: "Declarations shown per file (default 4, max 20)."
|
|
3622
|
+
},
|
|
3623
|
+
pathPrefix: {
|
|
3624
|
+
type: "string",
|
|
3625
|
+
description: "Restrict results to this project-relative path prefix."
|
|
3626
|
+
}
|
|
3627
|
+
},
|
|
3628
|
+
required: ["query"],
|
|
3629
|
+
additionalProperties: false
|
|
3630
|
+
},
|
|
3631
|
+
async execute(input, ctx, execOpts) {
|
|
3632
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
3633
|
+
if (query.length === 0) {
|
|
3634
|
+
throw new ToolValidationError5({
|
|
3635
|
+
message: "codebase-context requires a non-empty `query`.",
|
|
3636
|
+
field: "query"
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
const signal = execOpts?.signal ?? ctx.signal;
|
|
3640
|
+
const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
|
|
3641
|
+
try {
|
|
3642
|
+
signal?.throwIfAborted();
|
|
3643
|
+
const indexDir = codebaseIndexDirOverride(ctx);
|
|
3644
|
+
const vectorFiles = await semanticSeeds(projectRoot, indexDir, query);
|
|
3645
|
+
signal?.throwIfAborted();
|
|
3646
|
+
const result = await codebaseContext({
|
|
3647
|
+
projectRoot,
|
|
3648
|
+
indexDir,
|
|
3649
|
+
query,
|
|
3650
|
+
...vectorFiles.length > 0 ? { vectorFiles } : {},
|
|
3651
|
+
limit: input.limit,
|
|
3652
|
+
symbolsPerFile: input.symbolsPerFile,
|
|
3653
|
+
pathPrefix: input.pathPrefix
|
|
3654
|
+
});
|
|
3655
|
+
return {
|
|
3656
|
+
query: result.query,
|
|
3657
|
+
entries: result.entries,
|
|
3658
|
+
seedCount: result.seedCount,
|
|
3659
|
+
semanticSeedCount: result.semanticSeedCount,
|
|
3660
|
+
totalCandidates: result.totalCandidates,
|
|
3661
|
+
indexStatus: result.indexStatus,
|
|
3662
|
+
..."stale" in result && result.stale === true ? { stale: true } : {}
|
|
3663
|
+
};
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
if (signal?.aborted) throw err;
|
|
3666
|
+
const refreshing = err instanceof Error && err.name === "IndexRefreshInProgressError";
|
|
3667
|
+
return {
|
|
3668
|
+
query,
|
|
3669
|
+
entries: [],
|
|
3670
|
+
seedCount: 0,
|
|
3671
|
+
semanticSeedCount: 0,
|
|
3672
|
+
totalCandidates: 0,
|
|
3673
|
+
indexStatus: "error",
|
|
3674
|
+
error: refreshing ? "Codebase index is refreshing; retry once the current generation is published." : toErrorMessage11(err)
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
// src/codebase-index/concept-enrichment.ts
|
|
3681
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
3682
|
+
import * as fs10 from "node:fs/promises";
|
|
3683
|
+
import * as path11 from "node:path";
|
|
3684
|
+
var MAX_SOURCE_CHARS = 12e3;
|
|
3685
|
+
var MAX_CRUX_LINES = 12;
|
|
3686
|
+
var MAX_DECLARATIONS = 24;
|
|
3687
|
+
var DEFAULT_CONCURRENCY = 5;
|
|
3688
|
+
var MAX_SUMMARY_CHARS = 400;
|
|
3689
|
+
function normaliseCrux(result, lineCount) {
|
|
3690
|
+
const rawStart = result.cruxStart;
|
|
3691
|
+
const rawEnd = result.cruxEnd;
|
|
3692
|
+
if (typeof rawStart !== "number" || !Number.isFinite(rawStart)) return { start: null, end: null };
|
|
3693
|
+
const start = Math.max(1, Math.min(Math.trunc(rawStart), lineCount));
|
|
3694
|
+
const proposedEnd = typeof rawEnd === "number" && Number.isFinite(rawEnd) ? Math.trunc(rawEnd) : start;
|
|
3695
|
+
const end = Math.max(start, Math.min(proposedEnd, lineCount, start + MAX_CRUX_LINES - 1));
|
|
3696
|
+
return { start, end };
|
|
3697
|
+
}
|
|
3698
|
+
async function enrichConcepts(store, port, relativeOf, options = {}) {
|
|
3699
|
+
const startedAt = Date.now();
|
|
3700
|
+
const errors = [];
|
|
3701
|
+
const signal = options.signal;
|
|
3702
|
+
const concurrency = Math.max(1, Math.min(options.concurrency ?? DEFAULT_CONCURRENCY, 16));
|
|
3703
|
+
const pruned = store.pruneOrphanConcepts();
|
|
3704
|
+
const markedStale = store.markStaleConcepts();
|
|
3705
|
+
const hashByFile = /* @__PURE__ */ new Map();
|
|
3706
|
+
for (const meta of store.getAllFileMetas()) hashByFile.set(meta.file, meta.contentHash ?? "");
|
|
3707
|
+
const existing = /* @__PURE__ */ new Map();
|
|
3708
|
+
for (const concept of store.getAllFileConcepts()) existing.set(concept.file, concept);
|
|
3709
|
+
const rankOf = /* @__PURE__ */ new Map();
|
|
3710
|
+
for (const row of store.getRankedFiles(hashByFile.size || 1)) rankOf.set(row.file, row.rank);
|
|
3711
|
+
const ordered = [...hashByFile.keys()].sort(
|
|
3712
|
+
(a, b) => (rankOf.get(b) ?? 0) - (rankOf.get(a) ?? 0) || a.localeCompare(b)
|
|
3713
|
+
);
|
|
3714
|
+
const queue = [];
|
|
3715
|
+
let cached = 0;
|
|
3716
|
+
for (const file of ordered) {
|
|
3717
|
+
const hash = hashByFile.get(file);
|
|
3718
|
+
if (hash === void 0) continue;
|
|
3719
|
+
const concept = existing.get(file);
|
|
3720
|
+
const current = concept !== void 0 && concept.state === "ready" && concept.contentHash === hash;
|
|
3721
|
+
if (current && options.force !== true) {
|
|
3722
|
+
cached++;
|
|
3723
|
+
continue;
|
|
3724
|
+
}
|
|
3725
|
+
queue.push(file);
|
|
3726
|
+
if (options.maxFiles !== void 0 && queue.length >= options.maxFiles) break;
|
|
3727
|
+
}
|
|
3728
|
+
let summarised = 0;
|
|
3729
|
+
let failed = 0;
|
|
3730
|
+
let done = 0;
|
|
3731
|
+
let cursor = 0;
|
|
3732
|
+
const worker = async () => {
|
|
3733
|
+
for (; ; ) {
|
|
3734
|
+
if (signal?.aborted) return;
|
|
3735
|
+
const index = cursor++;
|
|
3736
|
+
const file = queue[index];
|
|
3737
|
+
if (file === void 0) return;
|
|
3738
|
+
try {
|
|
3739
|
+
const result = await summariseOne(
|
|
3740
|
+
store,
|
|
3741
|
+
port,
|
|
3742
|
+
relativeOf,
|
|
3743
|
+
file,
|
|
3744
|
+
hashByFile,
|
|
3745
|
+
existing,
|
|
3746
|
+
signal
|
|
3747
|
+
);
|
|
3748
|
+
if (result === "skipped") failed++;
|
|
3749
|
+
else summarised++;
|
|
3750
|
+
} catch (error) {
|
|
3751
|
+
failed++;
|
|
3752
|
+
if (errors.length < 50) {
|
|
3753
|
+
errors.push(
|
|
3754
|
+
`${relativeOf(file)}: ${error instanceof Error ? error.message : String(error)}`
|
|
3755
|
+
);
|
|
3756
|
+
}
|
|
3757
|
+
} finally {
|
|
3758
|
+
done++;
|
|
3759
|
+
options.onProgress?.(done, queue.length);
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
};
|
|
3763
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, () => worker()));
|
|
3764
|
+
let subsystems = 0;
|
|
3765
|
+
if (options.subsystems === true && port.describeSubsystem !== void 0 && signal?.aborted !== true) {
|
|
3766
|
+
try {
|
|
3767
|
+
subsystems = await deriveSubsystems(store, port, relativeOf, signal);
|
|
3768
|
+
} catch (error) {
|
|
3769
|
+
errors.push(`subsystems: ${error instanceof Error ? error.message : String(error)}`);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
return {
|
|
3773
|
+
summarised,
|
|
3774
|
+
cached,
|
|
3775
|
+
failed,
|
|
3776
|
+
markedStale,
|
|
3777
|
+
pruned,
|
|
3778
|
+
subsystems,
|
|
3779
|
+
durationMs: Date.now() - startedAt,
|
|
3780
|
+
errors
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
async function summariseOne(store, port, relativeOf, file, hashByFile, existing, signal) {
|
|
3784
|
+
let source;
|
|
3785
|
+
try {
|
|
3786
|
+
source = await fs10.readFile(file, "utf8");
|
|
3787
|
+
} catch {
|
|
3788
|
+
return "skipped";
|
|
3789
|
+
}
|
|
3790
|
+
if (source.trim().length === 0) return "skipped";
|
|
3791
|
+
const truncated = source.length > MAX_SOURCE_CHARS;
|
|
3792
|
+
const declarations = store.getFileSymbols(file, MAX_DECLARATIONS).map((symbol) => ({ name: symbol.name, kind: symbol.kind, line: symbol.line }));
|
|
3793
|
+
const stale = existing.get(file);
|
|
3794
|
+
const result = await port.describeFile({
|
|
3795
|
+
file: relativeOf(file),
|
|
3796
|
+
absolutePath: file,
|
|
3797
|
+
language: "",
|
|
3798
|
+
source: truncated ? source.slice(0, MAX_SOURCE_CHARS) : source,
|
|
3799
|
+
truncated,
|
|
3800
|
+
declarations,
|
|
3801
|
+
...stale !== void 0 && stale.summary !== "" ? { staleSummary: stale.summary } : {},
|
|
3802
|
+
signal
|
|
3803
|
+
});
|
|
3804
|
+
const summary = result?.summary?.trim() ?? "";
|
|
3805
|
+
if (summary.length === 0) return "skipped";
|
|
3806
|
+
const lineCount = source.split("\n").length;
|
|
3807
|
+
const crux = normaliseCrux(result, lineCount);
|
|
3808
|
+
store.upsertFileConcept({
|
|
3809
|
+
file,
|
|
3810
|
+
contentHash: hashByFile.get(file) ?? "",
|
|
3811
|
+
summary: summary.slice(0, MAX_SUMMARY_CHARS),
|
|
3812
|
+
cruxStart: crux.start,
|
|
3813
|
+
cruxEnd: crux.end,
|
|
3814
|
+
state: "ready",
|
|
3815
|
+
model: result?.model ?? "",
|
|
3816
|
+
updatedAt: Date.now()
|
|
3817
|
+
});
|
|
3818
|
+
return "stored";
|
|
3819
|
+
}
|
|
3820
|
+
var SUBSYSTEM_FILE_SAMPLE = 12;
|
|
3821
|
+
async function deriveSubsystems(store, port, relativeOf, signal) {
|
|
3822
|
+
const describe = port.describeSubsystem;
|
|
3823
|
+
if (describe === void 0) return 0;
|
|
3824
|
+
const summaries = store.getReadyConceptSummaries();
|
|
3825
|
+
if (summaries.size === 0) return 0;
|
|
3826
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
3827
|
+
for (const row of store.getRankedFiles(summaries.size * 2)) {
|
|
3828
|
+
const summary = summaries.get(row.file);
|
|
3829
|
+
if (summary === void 0) continue;
|
|
3830
|
+
const name = row.package || relativeOf(row.file).split("/")[0] || "(root)";
|
|
3831
|
+
const bucket = byPackage.get(name);
|
|
3832
|
+
const entry = { file: relativeOf(row.file), summary, rank: row.rank };
|
|
3833
|
+
if (bucket === void 0) byPackage.set(name, [entry]);
|
|
3834
|
+
else if (bucket.length < SUBSYSTEM_FILE_SAMPLE) bucket.push(entry);
|
|
3835
|
+
}
|
|
3836
|
+
const names = [...byPackage.keys()].sort();
|
|
3837
|
+
const known = new Set(names);
|
|
3838
|
+
const subsystems = [];
|
|
3839
|
+
const edges = [];
|
|
3840
|
+
for (const name of names) {
|
|
3841
|
+
if (signal?.aborted) break;
|
|
3842
|
+
const files = byPackage.get(name) ?? [];
|
|
3843
|
+
const result = await describe({ name, files, signal });
|
|
3844
|
+
const summary = result?.summary?.trim() ?? "";
|
|
3845
|
+
if (summary.length === 0) continue;
|
|
3846
|
+
subsystems.push({
|
|
3847
|
+
id: name,
|
|
3848
|
+
name,
|
|
3849
|
+
summary: summary.slice(0, MAX_SUMMARY_CHARS),
|
|
3850
|
+
memberFiles: files.map((f) => f.file),
|
|
3851
|
+
model: result?.model ?? "",
|
|
3852
|
+
updatedAt: Date.now()
|
|
3853
|
+
});
|
|
3854
|
+
for (const relation of result?.relations ?? []) {
|
|
3855
|
+
if (!known.has(relation.to) || relation.to === name) continue;
|
|
3856
|
+
if (!isConceptRelation(relation.relation)) continue;
|
|
3857
|
+
edges.push({ fromId: name, toId: relation.to, relation: relation.relation });
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
store.replaceSubsystems(subsystems, edges);
|
|
3861
|
+
return subsystems.length;
|
|
3862
|
+
}
|
|
3863
|
+
async function enrichProjectConcepts(projectRoot, port, options = {}) {
|
|
3864
|
+
const { indexDir, ...rest } = options;
|
|
3865
|
+
if (!existsSync5(path11.join(resolveIndexDir(projectRoot, indexDir), "index.db"))) {
|
|
3866
|
+
return { indexed: false };
|
|
3867
|
+
}
|
|
3868
|
+
const store = indexStorePool.acquire(projectRoot, { indexDir });
|
|
3869
|
+
try {
|
|
3870
|
+
return await enrichConcepts(
|
|
3871
|
+
store,
|
|
3872
|
+
port,
|
|
3873
|
+
(file) => {
|
|
3874
|
+
const relative12 = path11.relative(projectRoot, file);
|
|
3875
|
+
const chosen = relative12 && !relative12.startsWith("..") ? relative12 : file;
|
|
3876
|
+
return posixIndexPath(chosen);
|
|
3877
|
+
},
|
|
3878
|
+
rest
|
|
3879
|
+
);
|
|
3880
|
+
} finally {
|
|
3881
|
+
indexStorePool.release(store);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
// src/codebase-index/embedding-pass.ts
|
|
3886
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
3887
|
+
import * as path12 from "node:path";
|
|
3888
|
+
var DEFAULT_BATCH_SIZE = 16;
|
|
3889
|
+
var MAX_EMBED_CHARS = 2e3;
|
|
3890
|
+
var FALLBACK_DECLARATIONS = 12;
|
|
3891
|
+
function embedTextFor(relativePath, summary, declarations) {
|
|
3892
|
+
const described = summary?.trim() ?? "";
|
|
3893
|
+
if (described.length > 0) {
|
|
3894
|
+
return {
|
|
3895
|
+
text: `${relativePath}
|
|
3896
|
+
${described}`.slice(0, MAX_EMBED_CHARS),
|
|
3897
|
+
fromDeclarations: false
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
const names = declarations.slice(0, FALLBACK_DECLARATIONS).join(", ");
|
|
3901
|
+
return {
|
|
3902
|
+
text: `${relativePath}${names.length > 0 ? `
|
|
3903
|
+
${names}` : ""}`.slice(0, MAX_EMBED_CHARS),
|
|
3904
|
+
fromDeclarations: true
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
async function embedFiles(store, port, relativeOf, options = {}) {
|
|
3908
|
+
const startedAt = Date.now();
|
|
3909
|
+
const errors = [];
|
|
3910
|
+
const signal = options.signal;
|
|
3911
|
+
const batchSize = Math.max(1, Math.min(options.batchSize ?? DEFAULT_BATCH_SIZE, 128));
|
|
3912
|
+
const providerChanged = store.reconcileVectorProvider(port.id);
|
|
3913
|
+
const pruned = store.pruneOrphanFileVectors();
|
|
3914
|
+
const stored = store.getFileVectorStates(port.id);
|
|
3915
|
+
const summaries = store.getReadyConceptSummaries();
|
|
3916
|
+
const rankOf = /* @__PURE__ */ new Map();
|
|
3917
|
+
const metas = store.getAllFileMetas();
|
|
3918
|
+
for (const row of store.getRankedFiles(metas.length || 1)) rankOf.set(row.file, row.rank);
|
|
3919
|
+
const ordered = metas.map((meta) => meta.file).sort((a, b) => (rankOf.get(b) ?? 0) - (rankOf.get(a) ?? 0) || a.localeCompare(b));
|
|
3920
|
+
const queue = [];
|
|
3921
|
+
let cached = 0;
|
|
3922
|
+
for (const file of ordered) {
|
|
3923
|
+
const declarations = summaries.has(file) ? [] : store.getFileSymbols(file, FALLBACK_DECLARATIONS).map((symbol) => symbol.name);
|
|
3924
|
+
const { text, fromDeclarations: fromDeclarations2 } = embedTextFor(
|
|
3925
|
+
relativeOf(file),
|
|
3926
|
+
summaries.get(file),
|
|
3927
|
+
declarations
|
|
3928
|
+
);
|
|
3929
|
+
if (text.trim().length === 0) continue;
|
|
3930
|
+
const hash = xxhash64String(text);
|
|
3931
|
+
if (options.force !== true && stored.get(file) === hash) {
|
|
3932
|
+
cached++;
|
|
3933
|
+
continue;
|
|
3934
|
+
}
|
|
3935
|
+
queue.push({ file, text, hash, fromDeclarations: fromDeclarations2 });
|
|
3936
|
+
if (options.maxFiles !== void 0 && queue.length >= options.maxFiles) break;
|
|
3937
|
+
}
|
|
3938
|
+
let embedded = 0;
|
|
3939
|
+
let fromDeclarations = 0;
|
|
3940
|
+
let done = 0;
|
|
3941
|
+
for (let start = 0; start < queue.length; start += batchSize) {
|
|
3942
|
+
if (signal?.aborted) break;
|
|
3943
|
+
const batch = queue.slice(start, start + batchSize);
|
|
3944
|
+
try {
|
|
3945
|
+
const vectors = await port.embed(batch.map((entry) => entry.text));
|
|
3946
|
+
const rows = [];
|
|
3947
|
+
for (let i = 0; i < batch.length; i++) {
|
|
3948
|
+
const entry = batch[i];
|
|
3949
|
+
const vector = vectors[i];
|
|
3950
|
+
if (vector === void 0 || vector.length !== port.dimensions) continue;
|
|
3951
|
+
rows.push({ file: entry.file, vector, sourceHash: entry.hash, provider: port.id });
|
|
3952
|
+
if (entry.fromDeclarations) fromDeclarations++;
|
|
3953
|
+
}
|
|
3954
|
+
store.upsertFileVectors(rows);
|
|
3955
|
+
embedded += rows.length;
|
|
3956
|
+
} catch (error) {
|
|
3957
|
+
if (errors.length < 20) {
|
|
3958
|
+
errors.push(`batch at ${start}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3959
|
+
}
|
|
3960
|
+
} finally {
|
|
3961
|
+
done += batch.length;
|
|
3962
|
+
options.onProgress?.(done, queue.length);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
return {
|
|
3966
|
+
embedded,
|
|
3967
|
+
cached,
|
|
3968
|
+
pruned,
|
|
3969
|
+
providerChanged,
|
|
3970
|
+
fromDeclarations,
|
|
3971
|
+
durationMs: Date.now() - startedAt,
|
|
3972
|
+
errors
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
async function embedProjectFiles(projectRoot, port, options = {}) {
|
|
3976
|
+
const { indexDir, ...rest } = options;
|
|
3977
|
+
if (!existsSync6(path12.join(resolveIndexDir(projectRoot, indexDir), "index.db"))) {
|
|
3978
|
+
return { indexed: false };
|
|
3979
|
+
}
|
|
3980
|
+
const store = indexStorePool.acquire(projectRoot, { indexDir });
|
|
3981
|
+
try {
|
|
3982
|
+
return await embedFiles(
|
|
3983
|
+
store,
|
|
3984
|
+
port,
|
|
3985
|
+
(file) => {
|
|
3986
|
+
const relative12 = path12.relative(projectRoot, file);
|
|
3987
|
+
return posixIndexPath(relative12 && !relative12.startsWith("..") ? relative12 : file);
|
|
3988
|
+
},
|
|
3989
|
+
rest
|
|
3990
|
+
);
|
|
3991
|
+
} finally {
|
|
3992
|
+
indexStorePool.release(store);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
|
|
2842
3996
|
export {
|
|
2843
3997
|
PolyglotInvariantEngine,
|
|
2844
3998
|
polyglotInvariantEngine,
|
|
2845
3999
|
replaceSymbolInFile,
|
|
2846
4000
|
codebaseAstReplaceTool,
|
|
2847
|
-
codebaseInvariantCheckTool,
|
|
2848
4001
|
codebaseImpactAnalysisTool,
|
|
2849
4002
|
codebaseIncomingCallsTool,
|
|
2850
4003
|
codebaseSearchTool,
|
|
2851
4004
|
codebaseIndexTool,
|
|
4005
|
+
codebaseInvariantCheckTool,
|
|
2852
4006
|
codebaseOutgoingCallsTool,
|
|
2853
4007
|
extractFileSkeleton,
|
|
2854
4008
|
extractDirectorySkeleton,
|
|
@@ -2858,6 +4012,39 @@ export {
|
|
|
2858
4012
|
codebaseStatsTool,
|
|
2859
4013
|
codebaseTargetedTestTool,
|
|
2860
4014
|
deadCodeScanTool,
|
|
2861
|
-
runDeadCodeScan
|
|
4015
|
+
runDeadCodeScan,
|
|
4016
|
+
EXPORT_PACKAGE_LIMIT,
|
|
4017
|
+
EXPORT_FILE_LIMIT,
|
|
4018
|
+
renderAtlasHtml,
|
|
4019
|
+
ATLAS_DIR,
|
|
4020
|
+
ATLAS_JSON,
|
|
4021
|
+
ATLAS_MARKDOWN,
|
|
4022
|
+
ATLAS_MANIFEST,
|
|
4023
|
+
ATLAS_SCHEMA,
|
|
4024
|
+
ATLAS_FILE_LIMIT,
|
|
4025
|
+
buildAtlas,
|
|
4026
|
+
writeAtlas,
|
|
4027
|
+
MAX_REPORTED_DRIFT,
|
|
4028
|
+
checkAtlasFreshness,
|
|
4029
|
+
writeProjectAtlas,
|
|
4030
|
+
checkProjectAtlasFreshness,
|
|
4031
|
+
exportProjectAtlasHtml,
|
|
4032
|
+
BRIEF_HUB_LIMIT,
|
|
4033
|
+
BRIEF_PACKAGE_LIMIT,
|
|
4034
|
+
BRIEF_SUBSYSTEM_LIMIT,
|
|
4035
|
+
buildAtlasBrief,
|
|
4036
|
+
buildProjectAtlasBrief,
|
|
4037
|
+
setContextQueryEmbedder,
|
|
4038
|
+
codebaseContextTool,
|
|
4039
|
+
MAX_SOURCE_CHARS,
|
|
4040
|
+
MAX_CRUX_LINES,
|
|
4041
|
+
DEFAULT_CONCURRENCY,
|
|
4042
|
+
MAX_SUMMARY_CHARS,
|
|
4043
|
+
enrichConcepts,
|
|
4044
|
+
enrichProjectConcepts,
|
|
4045
|
+
DEFAULT_BATCH_SIZE,
|
|
4046
|
+
MAX_EMBED_CHARS,
|
|
4047
|
+
embedFiles,
|
|
4048
|
+
embedProjectFiles
|
|
2862
4049
|
};
|
|
2863
|
-
//# sourceMappingURL=chunk-
|
|
4050
|
+
//# sourceMappingURL=chunk-PNLDTRC5.js.map
|