mason-context 0.2.2 → 0.3.0
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/bin/mason-mcp.js +151 -117
- package/dist/bin/mason-mcp.js.map +1 -1
- package/dist/bin/mason.js +177 -140
- package/dist/bin/mason.js.map +1 -1
- package/dist/src/cli.js +177 -140
- package/dist/src/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/mason-mcp.js
CHANGED
|
@@ -9,18 +9,115 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/test-map.ts
|
|
13
|
+
var test_map_exports = {};
|
|
14
|
+
__export(test_map_exports, {
|
|
15
|
+
buildTestMap: () => buildTestMap
|
|
16
|
+
});
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
import fg3 from "fast-glob";
|
|
19
|
+
async function buildTestMap(dir) {
|
|
20
|
+
const rootDir = path2.resolve(dir);
|
|
21
|
+
const testPatterns = [
|
|
22
|
+
"**/*.test.*",
|
|
23
|
+
"**/*.spec.*",
|
|
24
|
+
"**/*Test.kt",
|
|
25
|
+
"**/*Test.java",
|
|
26
|
+
"**/*Tests.kt",
|
|
27
|
+
"**/*Tests.java",
|
|
28
|
+
"**/test_*.py",
|
|
29
|
+
"**/*_test.py",
|
|
30
|
+
"**/*_test.go",
|
|
31
|
+
"**/*Tests.swift",
|
|
32
|
+
"**/*Test.swift",
|
|
33
|
+
"**/*_test.rs"
|
|
34
|
+
];
|
|
35
|
+
const testFiles = await fg3(testPatterns, { cwd: rootDir, ignore: IGNORE });
|
|
36
|
+
const sourceFiles = await fg3(
|
|
37
|
+
"**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,dart}",
|
|
38
|
+
{ cwd: rootDir, ignore: IGNORE }
|
|
39
|
+
);
|
|
40
|
+
const sourceByBaseName = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const file of sourceFiles) {
|
|
42
|
+
if (testFiles.includes(file)) continue;
|
|
43
|
+
const baseName = path2.basename(file).replace(/\.[^.]+$/, "");
|
|
44
|
+
const existing = sourceByBaseName.get(baseName) ?? [];
|
|
45
|
+
existing.push(file);
|
|
46
|
+
sourceByBaseName.set(baseName, existing);
|
|
47
|
+
}
|
|
48
|
+
const paired = [];
|
|
49
|
+
const unmatched = [];
|
|
50
|
+
for (const testFile of testFiles) {
|
|
51
|
+
const testBaseName = path2.basename(testFile).replace(/\.[^.]+$/, "");
|
|
52
|
+
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
53
|
+
if (!sourceName) {
|
|
54
|
+
unmatched.push(testFile);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const candidates = sourceByBaseName.get(sourceName);
|
|
58
|
+
if (candidates && candidates.length > 0) {
|
|
59
|
+
const testDir = path2.dirname(testFile);
|
|
60
|
+
const bestMatch = candidates.reduce((best, candidate) => {
|
|
61
|
+
const candidateDir = path2.dirname(candidate);
|
|
62
|
+
const bestDir = path2.dirname(best);
|
|
63
|
+
const candidateOverlap = commonSegments(testDir, candidateDir);
|
|
64
|
+
const bestOverlap = commonSegments(testDir, bestDir);
|
|
65
|
+
return candidateOverlap > bestOverlap ? candidate : best;
|
|
66
|
+
});
|
|
67
|
+
paired.push({
|
|
68
|
+
test: testFile,
|
|
69
|
+
source: bestMatch,
|
|
70
|
+
confidence: candidates.length === 1 ? "exact" : "best-guess"
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
unmatched.push(testFile);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { totalTestFiles: testFiles.length, paired, unmatched };
|
|
77
|
+
}
|
|
78
|
+
function commonSegments(pathA, pathB) {
|
|
79
|
+
const segsA = pathA.split("/");
|
|
80
|
+
const segsB = pathB.split("/");
|
|
81
|
+
let count = 0;
|
|
82
|
+
for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {
|
|
83
|
+
if (segsA[i] === segsB[i]) count++;
|
|
84
|
+
else break;
|
|
85
|
+
}
|
|
86
|
+
return count;
|
|
87
|
+
}
|
|
88
|
+
var IGNORE;
|
|
89
|
+
var init_test_map = __esm({
|
|
90
|
+
"src/test-map.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
IGNORE = [
|
|
93
|
+
"**/node_modules/**",
|
|
94
|
+
"**/dist/**",
|
|
95
|
+
"**/build/**",
|
|
96
|
+
"**/.gradle/**",
|
|
97
|
+
"**/target/**",
|
|
98
|
+
"**/.git/**",
|
|
99
|
+
"**/vendor/**",
|
|
100
|
+
"**/__pycache__/**",
|
|
101
|
+
"**/venv/**",
|
|
102
|
+
"**/.venv/**",
|
|
103
|
+
"**/*.min.*",
|
|
104
|
+
"**/*.map"
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
12
109
|
// src/impact/impact.ts
|
|
13
110
|
var impact_exports = {};
|
|
14
111
|
__export(impact_exports, {
|
|
15
112
|
analyzeImpact: () => analyzeImpact
|
|
16
113
|
});
|
|
17
114
|
import fs5 from "fs/promises";
|
|
18
|
-
import
|
|
115
|
+
import path5 from "path";
|
|
19
116
|
import { execFile as execFile7 } from "child_process";
|
|
20
117
|
import { promisify as promisify7 } from "util";
|
|
21
|
-
import
|
|
118
|
+
import fg4 from "fast-glob";
|
|
22
119
|
async function analyzeImpact(rootDir, targetFiles) {
|
|
23
|
-
const resolvedRoot =
|
|
120
|
+
const resolvedRoot = path5.resolve(rootDir);
|
|
24
121
|
const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
|
|
25
122
|
const [cochange, references, tests] = await Promise.all([
|
|
26
123
|
getCochangeFiles(resolvedRoot, resolvedTargets),
|
|
@@ -41,17 +138,17 @@ async function resolveTargetFiles(rootDir, targets) {
|
|
|
41
138
|
resolved.push(target);
|
|
42
139
|
continue;
|
|
43
140
|
}
|
|
44
|
-
const matches = await
|
|
141
|
+
const matches = await fg4(`**/${target}`, {
|
|
45
142
|
cwd: rootDir,
|
|
46
|
-
ignore:
|
|
143
|
+
ignore: IGNORE2
|
|
47
144
|
});
|
|
48
145
|
if (matches.length > 0) {
|
|
49
146
|
resolved.push(matches[0]);
|
|
50
147
|
} else {
|
|
51
148
|
const noExt = target.replace(/\.[^.]+$/, "");
|
|
52
|
-
const extMatches = await
|
|
149
|
+
const extMatches = await fg4(`**/${noExt}.*`, {
|
|
53
150
|
cwd: rootDir,
|
|
54
|
-
ignore:
|
|
151
|
+
ignore: IGNORE2
|
|
55
152
|
});
|
|
56
153
|
if (extMatches.length > 0) {
|
|
57
154
|
resolved.push(extMatches[0]);
|
|
@@ -103,12 +200,12 @@ async function getCochangeFiles(rootDir, targetFiles) {
|
|
|
103
200
|
async function getReferences(rootDir, targetFiles) {
|
|
104
201
|
const searchNames = /* @__PURE__ */ new Set();
|
|
105
202
|
for (const target of targetFiles) {
|
|
106
|
-
const basename =
|
|
203
|
+
const basename = path5.basename(target).replace(/\.[^.]+$/, "");
|
|
107
204
|
searchNames.add(basename);
|
|
108
205
|
}
|
|
109
|
-
const allSourceFiles = await
|
|
206
|
+
const allSourceFiles = await fg4(`**/${SOURCE_EXTENSIONS2}`, {
|
|
110
207
|
cwd: rootDir,
|
|
111
|
-
ignore:
|
|
208
|
+
ignore: IGNORE2
|
|
112
209
|
});
|
|
113
210
|
const targetSet = new Set(targetFiles);
|
|
114
211
|
const filesToSearch = allSourceFiles.filter((f) => !targetSet.has(f));
|
|
@@ -120,7 +217,7 @@ async function getReferences(rootDir, targetFiles) {
|
|
|
120
217
|
batch.map(async (file) => {
|
|
121
218
|
try {
|
|
122
219
|
const content = await fs5.readFile(
|
|
123
|
-
|
|
220
|
+
path5.join(rootDir, file),
|
|
124
221
|
"utf-8"
|
|
125
222
|
);
|
|
126
223
|
for (const name of searchNames) {
|
|
@@ -155,12 +252,12 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
155
252
|
"**/*Test.swift",
|
|
156
253
|
"**/*_test.rs"
|
|
157
254
|
];
|
|
158
|
-
const testFiles = await
|
|
255
|
+
const testFiles = await fg4(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
|
|
159
256
|
const results = [];
|
|
160
257
|
for (const target of targetFiles) {
|
|
161
|
-
const targetBaseName =
|
|
258
|
+
const targetBaseName = path5.basename(target).replace(/\.[^.]+$/, "");
|
|
162
259
|
for (const testFile of testFiles) {
|
|
163
|
-
const testBaseName =
|
|
260
|
+
const testBaseName = path5.basename(testFile).replace(/\.[^.]+$/, "");
|
|
164
261
|
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
165
262
|
if (sourceName === targetBaseName) {
|
|
166
263
|
results.push({
|
|
@@ -175,12 +272,12 @@ async function getRelatedTests(rootDir, targetFiles) {
|
|
|
175
272
|
function escapeRegex(str) {
|
|
176
273
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
177
274
|
}
|
|
178
|
-
var exec7,
|
|
275
|
+
var exec7, IGNORE2, SOURCE_EXTENSIONS2;
|
|
179
276
|
var init_impact = __esm({
|
|
180
277
|
"src/impact/impact.ts"() {
|
|
181
278
|
"use strict";
|
|
182
279
|
exec7 = promisify7(execFile7);
|
|
183
|
-
|
|
280
|
+
IGNORE2 = [
|
|
184
281
|
"**/node_modules/**",
|
|
185
282
|
"**/dist/**",
|
|
186
283
|
"**/build/**",
|
|
@@ -204,10 +301,10 @@ import { z } from "zod";
|
|
|
204
301
|
|
|
205
302
|
// src/mcp/tools.ts
|
|
206
303
|
import fs6 from "fs/promises";
|
|
207
|
-
import
|
|
304
|
+
import path6 from "path";
|
|
208
305
|
import { execFile as execFile8 } from "child_process";
|
|
209
306
|
import { promisify as promisify8 } from "util";
|
|
210
|
-
import
|
|
307
|
+
import fg5 from "fast-glob";
|
|
211
308
|
|
|
212
309
|
// src/analyzers/git-history.ts
|
|
213
310
|
import { execFile } from "child_process";
|
|
@@ -810,23 +907,24 @@ function isSensitiveFile(filePath) {
|
|
|
810
907
|
|
|
811
908
|
// src/snapshot/snapshot.ts
|
|
812
909
|
import fs4 from "fs/promises";
|
|
813
|
-
import
|
|
910
|
+
import path4 from "path";
|
|
814
911
|
import { execFile as execFile6 } from "child_process";
|
|
815
912
|
import { promisify as promisify6 } from "util";
|
|
913
|
+
init_test_map();
|
|
816
914
|
|
|
817
915
|
// src/llm/providers.ts
|
|
818
|
-
import { execFile as execFile5 } from "child_process";
|
|
916
|
+
import { execFile as execFile5, spawn } from "child_process";
|
|
819
917
|
import { promisify as promisify5 } from "util";
|
|
820
918
|
|
|
821
919
|
// src/llm/config.ts
|
|
822
920
|
import fs3 from "fs/promises";
|
|
823
|
-
import
|
|
921
|
+
import path3 from "path";
|
|
824
922
|
import os from "os";
|
|
825
923
|
import { execFile as execFile4 } from "child_process";
|
|
826
924
|
import { promisify as promisify4 } from "util";
|
|
827
925
|
var exec4 = promisify4(execFile4);
|
|
828
|
-
var CONFIG_DIR =
|
|
829
|
-
var CONFIG_FILE =
|
|
926
|
+
var CONFIG_DIR = path3.join(os.homedir(), ".mason");
|
|
927
|
+
var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
|
|
830
928
|
|
|
831
929
|
// src/llm/providers.ts
|
|
832
930
|
var exec5 = promisify5(execFile5);
|
|
@@ -834,10 +932,10 @@ var exec5 = promisify5(execFile5);
|
|
|
834
932
|
// src/snapshot/snapshot.ts
|
|
835
933
|
var exec6 = promisify6(execFile6);
|
|
836
934
|
function snapshotDir(rootDir) {
|
|
837
|
-
return
|
|
935
|
+
return path4.join(rootDir, ".mason");
|
|
838
936
|
}
|
|
839
937
|
function snapshotPath(rootDir) {
|
|
840
|
-
return
|
|
938
|
+
return path4.join(snapshotDir(rootDir), "snapshot.json");
|
|
841
939
|
}
|
|
842
940
|
async function loadSnapshot(rootDir) {
|
|
843
941
|
try {
|
|
@@ -870,7 +968,7 @@ async function getCurrentGitHash(rootDir) {
|
|
|
870
968
|
|
|
871
969
|
// src/mcp/tools.ts
|
|
872
970
|
var exec8 = promisify8(execFile8);
|
|
873
|
-
var
|
|
971
|
+
var IGNORE3 = [
|
|
874
972
|
"**/node_modules/**",
|
|
875
973
|
"**/dist/**",
|
|
876
974
|
"**/build/**",
|
|
@@ -891,7 +989,7 @@ async function buildContext(dir) {
|
|
|
891
989
|
};
|
|
892
990
|
}
|
|
893
991
|
async function analyzeProject(dir) {
|
|
894
|
-
const rootDir =
|
|
992
|
+
const rootDir = path6.resolve(dir);
|
|
895
993
|
const context = await buildContext(rootDir);
|
|
896
994
|
const results = await runAll(context);
|
|
897
995
|
const projectSnapshot = await detectProjectSnapshot(rootDir);
|
|
@@ -945,7 +1043,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
945
1043
|
const present = [];
|
|
946
1044
|
for (const file of buildFiles) {
|
|
947
1045
|
try {
|
|
948
|
-
await fs6.access(
|
|
1046
|
+
await fs6.access(path6.join(rootDir, file));
|
|
949
1047
|
present.push(file);
|
|
950
1048
|
} catch {
|
|
951
1049
|
}
|
|
@@ -963,9 +1061,9 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
963
1061
|
];
|
|
964
1062
|
const testInfo = {};
|
|
965
1063
|
for (const pattern of testDirs) {
|
|
966
|
-
const files = await
|
|
1064
|
+
const files = await fg5(`${pattern}/**/*`, {
|
|
967
1065
|
cwd: rootDir,
|
|
968
|
-
ignore:
|
|
1066
|
+
ignore: IGNORE3,
|
|
969
1067
|
onlyFiles: true
|
|
970
1068
|
});
|
|
971
1069
|
if (files.length > 0) {
|
|
@@ -983,18 +1081,18 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
983
1081
|
{ pattern: "**/*_test.rs", label: "*_test.rs" }
|
|
984
1082
|
];
|
|
985
1083
|
for (const { pattern, label } of testFilePatterns) {
|
|
986
|
-
const files = await
|
|
1084
|
+
const files = await fg5(pattern, { cwd: rootDir, ignore: IGNORE3 });
|
|
987
1085
|
if (files.length > 0) {
|
|
988
1086
|
testInfo[label] = files.length;
|
|
989
1087
|
}
|
|
990
1088
|
}
|
|
991
|
-
const sourceFiles = await
|
|
1089
|
+
const sourceFiles = await fg5("**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}", {
|
|
992
1090
|
cwd: rootDir,
|
|
993
|
-
ignore:
|
|
1091
|
+
ignore: IGNORE3
|
|
994
1092
|
});
|
|
995
1093
|
const fileCounts = {};
|
|
996
1094
|
for (const file of sourceFiles) {
|
|
997
|
-
const ext =
|
|
1095
|
+
const ext = path6.extname(file).slice(1);
|
|
998
1096
|
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
999
1097
|
}
|
|
1000
1098
|
return {
|
|
@@ -1005,7 +1103,7 @@ async function detectProjectSnapshot(rootDir) {
|
|
|
1005
1103
|
};
|
|
1006
1104
|
}
|
|
1007
1105
|
async function getCodeSamples(dir, count = 15) {
|
|
1008
|
-
const rootDir =
|
|
1106
|
+
const rootDir = path6.resolve(dir);
|
|
1009
1107
|
const samples = await sampleFiles(rootDir, count);
|
|
1010
1108
|
const output = {
|
|
1011
1109
|
note: "These are previews (first ~60 lines). Use get_file_content to read the full file if needed.",
|
|
@@ -1020,10 +1118,10 @@ async function getCodeSamples(dir, count = 15) {
|
|
|
1020
1118
|
return JSON.stringify(output, null, 2);
|
|
1021
1119
|
}
|
|
1022
1120
|
async function getProjectStructure(dir) {
|
|
1023
|
-
const rootDir =
|
|
1024
|
-
const allFiles = await
|
|
1121
|
+
const rootDir = path6.resolve(dir);
|
|
1122
|
+
const allFiles = await fg5("**/*", {
|
|
1025
1123
|
cwd: rootDir,
|
|
1026
|
-
ignore:
|
|
1124
|
+
ignore: IGNORE3,
|
|
1027
1125
|
onlyFiles: true
|
|
1028
1126
|
});
|
|
1029
1127
|
const dirInfo = /* @__PURE__ */ new Map();
|
|
@@ -1036,7 +1134,7 @@ async function getProjectStructure(dir) {
|
|
|
1036
1134
|
}
|
|
1037
1135
|
const info = dirInfo.get(dirPath);
|
|
1038
1136
|
info.fileCount++;
|
|
1039
|
-
const ext =
|
|
1137
|
+
const ext = path6.extname(file).slice(1);
|
|
1040
1138
|
if (ext) {
|
|
1041
1139
|
info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
|
|
1042
1140
|
}
|
|
@@ -1058,81 +1156,12 @@ async function getProjectStructure(dir) {
|
|
|
1058
1156
|
return JSON.stringify(output, null, 2);
|
|
1059
1157
|
}
|
|
1060
1158
|
async function getTestMap(dir) {
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
"**/*.spec.*",
|
|
1065
|
-
"**/*Test.kt",
|
|
1066
|
-
"**/*Test.java",
|
|
1067
|
-
"**/*Tests.kt",
|
|
1068
|
-
"**/*Tests.java",
|
|
1069
|
-
"**/test_*.py",
|
|
1070
|
-
"**/*_test.py",
|
|
1071
|
-
"**/*_test.go",
|
|
1072
|
-
"**/*Tests.swift",
|
|
1073
|
-
"**/*Test.swift",
|
|
1074
|
-
"**/*_test.rs"
|
|
1075
|
-
];
|
|
1076
|
-
const testFiles = await fg4(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
|
|
1077
|
-
const sourceFiles = await fg4(
|
|
1078
|
-
"**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,dart}",
|
|
1079
|
-
{ cwd: rootDir, ignore: IGNORE2 }
|
|
1080
|
-
);
|
|
1081
|
-
const sourceByBaseName = /* @__PURE__ */ new Map();
|
|
1082
|
-
for (const file of sourceFiles) {
|
|
1083
|
-
if (testFiles.includes(file)) continue;
|
|
1084
|
-
const baseName = path5.basename(file).replace(/\.[^.]+$/, "");
|
|
1085
|
-
const existing = sourceByBaseName.get(baseName) ?? [];
|
|
1086
|
-
existing.push(file);
|
|
1087
|
-
sourceByBaseName.set(baseName, existing);
|
|
1088
|
-
}
|
|
1089
|
-
const pairs = [];
|
|
1090
|
-
const unmatched = [];
|
|
1091
|
-
for (const testFile of testFiles) {
|
|
1092
|
-
const testBaseName = path5.basename(testFile).replace(/\.[^.]+$/, "");
|
|
1093
|
-
const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
|
|
1094
|
-
if (!sourceName) {
|
|
1095
|
-
unmatched.push(testFile);
|
|
1096
|
-
continue;
|
|
1097
|
-
}
|
|
1098
|
-
const candidates = sourceByBaseName.get(sourceName);
|
|
1099
|
-
if (candidates && candidates.length > 0) {
|
|
1100
|
-
const testDir = path5.dirname(testFile);
|
|
1101
|
-
const bestMatch = candidates.reduce((best, candidate) => {
|
|
1102
|
-
const candidateDir = path5.dirname(candidate);
|
|
1103
|
-
const bestDir = path5.dirname(best);
|
|
1104
|
-
const candidateOverlap = commonSegments(testDir, candidateDir);
|
|
1105
|
-
const bestOverlap = commonSegments(testDir, bestDir);
|
|
1106
|
-
return candidateOverlap > bestOverlap ? candidate : best;
|
|
1107
|
-
});
|
|
1108
|
-
pairs.push({
|
|
1109
|
-
test: testFile,
|
|
1110
|
-
source: bestMatch,
|
|
1111
|
-
confidence: candidates.length === 1 ? "exact" : "best-guess"
|
|
1112
|
-
});
|
|
1113
|
-
} else {
|
|
1114
|
-
unmatched.push(testFile);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
const output = {
|
|
1118
|
-
totalTestFiles: testFiles.length,
|
|
1119
|
-
paired: pairs,
|
|
1120
|
-
unmatched
|
|
1121
|
-
};
|
|
1122
|
-
return JSON.stringify(output, null, 2);
|
|
1123
|
-
}
|
|
1124
|
-
function commonSegments(pathA, pathB) {
|
|
1125
|
-
const segsA = pathA.split("/");
|
|
1126
|
-
const segsB = pathB.split("/");
|
|
1127
|
-
let count = 0;
|
|
1128
|
-
for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {
|
|
1129
|
-
if (segsA[i] === segsB[i]) count++;
|
|
1130
|
-
else break;
|
|
1131
|
-
}
|
|
1132
|
-
return count;
|
|
1159
|
+
const { buildTestMap: buildTestMap2 } = await Promise.resolve().then(() => (init_test_map(), test_map_exports));
|
|
1160
|
+
const result = await buildTestMap2(dir);
|
|
1161
|
+
return JSON.stringify(result, null, 2);
|
|
1133
1162
|
}
|
|
1134
1163
|
async function getSnapshot(dir) {
|
|
1135
|
-
const rootDir =
|
|
1164
|
+
const rootDir = path6.resolve(dir);
|
|
1136
1165
|
const snapshot = await loadSnapshot(rootDir);
|
|
1137
1166
|
if (!snapshot) {
|
|
1138
1167
|
return JSON.stringify({
|
|
@@ -1148,7 +1177,11 @@ async function getSnapshot(dir) {
|
|
|
1148
1177
|
const unique = feat.files.filter((f) => !seenFiles.has(f));
|
|
1149
1178
|
if (unique.length === 0) continue;
|
|
1150
1179
|
for (const f of unique) seenFiles.add(f);
|
|
1151
|
-
|
|
1180
|
+
const entry = { files: unique };
|
|
1181
|
+
if (feat.tests && feat.tests.length > 0) {
|
|
1182
|
+
entry.tests = feat.tests;
|
|
1183
|
+
}
|
|
1184
|
+
compactFeatures[name] = entry;
|
|
1152
1185
|
}
|
|
1153
1186
|
const compactFlows = {};
|
|
1154
1187
|
for (const [name, flow] of Object.entries(snapshot.flows)) {
|
|
@@ -1156,6 +1189,7 @@ async function getSnapshot(dir) {
|
|
|
1156
1189
|
}
|
|
1157
1190
|
const output = {
|
|
1158
1191
|
exists: true,
|
|
1192
|
+
updatedAt: snapshot.updatedAt,
|
|
1159
1193
|
features: compactFeatures,
|
|
1160
1194
|
flows: compactFlows,
|
|
1161
1195
|
stale: isStale
|
|
@@ -1166,7 +1200,7 @@ async function getSnapshot(dir) {
|
|
|
1166
1200
|
return JSON.stringify(output);
|
|
1167
1201
|
}
|
|
1168
1202
|
async function fullAnalysis(dir) {
|
|
1169
|
-
const rootDir =
|
|
1203
|
+
const rootDir = path6.resolve(dir);
|
|
1170
1204
|
const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
|
|
1171
1205
|
analyzeProject(dir),
|
|
1172
1206
|
getProjectStructure(dir),
|
|
@@ -1192,7 +1226,7 @@ async function fullAnalysis(dir) {
|
|
|
1192
1226
|
return JSON.stringify(output, null, 2);
|
|
1193
1227
|
}
|
|
1194
1228
|
async function saveSnapshotData(dir, features, flows) {
|
|
1195
|
-
const rootDir =
|
|
1229
|
+
const rootDir = path6.resolve(dir);
|
|
1196
1230
|
const gitHash = await getCurrentGitHash(rootDir);
|
|
1197
1231
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1198
1232
|
const existing = await loadSnapshot(rootDir);
|
|
@@ -1225,7 +1259,7 @@ async function saveSnapshotData(dir, features, flows) {
|
|
|
1225
1259
|
}
|
|
1226
1260
|
async function getImpact(dir, files) {
|
|
1227
1261
|
const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
|
|
1228
|
-
const rootDir =
|
|
1262
|
+
const rootDir = path6.resolve(dir);
|
|
1229
1263
|
const result = await analyzeImpact2(rootDir, files);
|
|
1230
1264
|
return JSON.stringify(result, null, 2);
|
|
1231
1265
|
}
|
|
@@ -1235,7 +1269,7 @@ function createMcpServer() {
|
|
|
1235
1269
|
const server = new McpServer(
|
|
1236
1270
|
{
|
|
1237
1271
|
name: "mason",
|
|
1238
|
-
version: "0.
|
|
1272
|
+
version: "0.3.0"
|
|
1239
1273
|
},
|
|
1240
1274
|
{
|
|
1241
1275
|
instructions: "Mason is a context engineering tool. Always call get_snapshot before using Explore agents, Glob, or Grep to understand the codebase. The snapshot is a concept map that maps features and flows to their implementing files \u2014 it eliminates the need to search. This applies to ANY question about architecture, features, flows, how things work, cross-feature interactions, or bug investigation. Workflow: 1) Call get_snapshot first. 2) If no snapshot, call full_analysis and then save_snapshot to create one. 3) If the snapshot is stale, tell the user and offer to update it. 4) Use your native file reading tool to read files the snapshot points to. 5) Before modifying a file, call get_impact to check what else might be affected. 6) After making significant changes (new features, refactors, architecture changes), call save_snapshot to update the concept map."
|