@aiready/ast-mcp-server 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/security.ts
4
+ import path from "path";
5
+ function resolveWorkspaceRoot() {
6
+ return process.env.AST_WORKSPACE_ROOT || process.cwd();
7
+ }
8
+ function validateWorkspacePath(inputPath) {
9
+ const root = resolveWorkspaceRoot();
10
+ const resolved = path.resolve(root, inputPath);
11
+ const normalized = path.normalize(resolved);
12
+ if (!normalized.startsWith(root)) {
13
+ throw new Error(
14
+ `Path traversal detected: ${inputPath} escapes workspace root`
15
+ );
16
+ }
17
+ if (normalized.includes("\0")) {
18
+ throw new Error("Path contains null bytes");
19
+ }
20
+ return normalized;
21
+ }
22
+
23
+ export {
24
+ validateWorkspacePath
25
+ };
26
+ //# sourceMappingURL=chunk-EHE3VUUP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/security.ts"],"sourcesContent":["import path from 'path';\nimport fs from 'fs';\n\nexport function resolveWorkspaceRoot(): string {\n return process.env.AST_WORKSPACE_ROOT || process.cwd();\n}\n\nexport function validateWorkspacePath(inputPath: string): string {\n const root = resolveWorkspaceRoot();\n const resolved = path.resolve(root, inputPath);\n const normalized = path.normalize(resolved);\n\n // Reject path traversal\n if (!normalized.startsWith(root)) {\n throw new Error(\n `Path traversal detected: ${inputPath} escapes workspace root`\n );\n }\n\n // Reject null bytes\n if (normalized.includes('\\0')) {\n throw new Error('Path contains null bytes');\n }\n\n return normalized;\n}\n\nexport function validateFileExists(filePath: string): string {\n const safe = validateWorkspacePath(filePath);\n if (!fs.existsSync(safe)) {\n throw new Error(`File not found: ${filePath}`);\n }\n if (!fs.statSync(safe).isFile()) {\n throw new Error(`Not a file: ${filePath}`);\n }\n return safe;\n}\n"],"mappings":";;;AAAA,OAAO,UAAU;AAGV,SAAS,uBAA+B;AAC7C,SAAO,QAAQ,IAAI,sBAAsB,QAAQ,IAAI;AACvD;AAEO,SAAS,sBAAsB,WAA2B;AAC/D,QAAM,OAAO,qBAAqB;AAClC,QAAM,WAAW,KAAK,QAAQ,MAAM,SAAS;AAC7C,QAAM,aAAa,KAAK,UAAU,QAAQ;AAG1C,MAAI,CAAC,WAAW,WAAW,IAAI,GAAG;AAChC,UAAM,IAAI;AAAA,MACR,4BAA4B,SAAS;AAAA,IACvC;AAAA,EACF;AAGA,MAAI,WAAW,SAAS,IAAI,GAAG;AAC7B,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,SAAO;AACT;","names":[]}
package/dist/index.cjs CHANGED
@@ -162,6 +162,16 @@ var FindImplementationsSchema = import_zod.z.object({
162
162
  var GetFileStructureSchema = import_zod.z.object({
163
163
  file: import_zod.z.string().describe("Absolute path to the file to analyze")
164
164
  });
165
+ var GrepSearchSchema = import_zod.z.object({
166
+ pattern: import_zod.z.string().describe("Search pattern (regex by default)"),
167
+ path: import_zod.z.string().describe("Directory to search in"),
168
+ include: import_zod.z.array(import_zod.z.string()).optional().describe('Include only these glob patterns (e.g., ["*.ts", "src/**"])'),
169
+ exclude: import_zod.z.array(import_zod.z.string()).optional().describe('Exclude these glob patterns (e.g., ["**/*.test.ts"])'),
170
+ limit: import_zod.z.number().optional().default(50).describe("Max matches to return (default 50)"),
171
+ offset: import_zod.z.number().optional().default(0).describe("Pagination offset (default 0)"),
172
+ context: import_zod.z.number().optional().default(2).describe("Number of context lines before and after (default 2)"),
173
+ isRegex: import_zod.z.boolean().optional().default(true).describe("Use regex mode (default true)")
174
+ });
165
175
  var SearchCodeSchema = import_zod.z.object({
166
176
  pattern: import_zod.z.string().describe("Search pattern (regex by default)"),
167
177
  path: import_zod.z.string().describe("Directory to search in"),
@@ -587,9 +597,9 @@ var TypeScriptAdapter = class {
587
597
  await symbolIndex.buildIndex(searchPath);
588
598
  }
589
599
  }
590
- async resolveDefinition(symbolName, path7) {
591
- validateWorkspacePath(path7);
592
- await this.ensureIndex(path7);
600
+ async resolveDefinition(symbolName, path6) {
601
+ validateWorkspacePath(path6);
602
+ await this.ensureIndex(path6);
593
603
  const indexHits = symbolIndex.lookup(symbolName);
594
604
  if (indexHits.length > 0) {
595
605
  const results = [];
@@ -617,33 +627,33 @@ var TypeScriptAdapter = class {
617
627
  }
618
628
  return results;
619
629
  }
620
- if (import_fs3.default.statSync(path7).isDirectory()) {
630
+ if (import_fs3.default.statSync(path6).isDirectory()) {
621
631
  return [];
622
632
  }
623
- const tsconfig = await projectManager.findNearestTsConfig(path7);
633
+ const tsconfig = await projectManager.findNearestTsConfig(path6);
624
634
  if (!tsconfig) return [];
625
635
  try {
626
636
  const result = await this.pool.execute(
627
637
  "resolve_definition",
628
638
  {
629
639
  tsconfig,
630
- file: path7,
640
+ file: path6,
631
641
  symbol: symbolName
632
642
  }
633
643
  );
634
644
  return result;
635
645
  } catch {
636
646
  const project = projectManager.ensureProject(tsconfig);
637
- const sourceFile = project.addSourceFileAtPathIfExists(path7);
647
+ const sourceFile = project.addSourceFileAtPathIfExists(path6);
638
648
  if (!sourceFile) return [];
639
649
  const exported = sourceFile.getExportedDeclarations().get(symbolName);
640
650
  if (!exported) return [];
641
651
  return exported.map((decl) => this.mapToDefinitionLocation(decl));
642
652
  }
643
653
  }
644
- async findReferences(symbolName, path7, limit = 50, offset = 0) {
645
- validateWorkspacePath(path7);
646
- await this.ensureIndex(path7);
654
+ async findReferences(symbolName, path6, limit = 50, offset = 0) {
655
+ validateWorkspacePath(path6);
656
+ await this.ensureIndex(path6);
647
657
  const hits = symbolIndex.lookup(symbolName);
648
658
  if (hits.length === 0) return { references: [], total_count: 0 };
649
659
  const hit = hits[0];
@@ -656,7 +666,7 @@ var TypeScriptAdapter = class {
656
666
  const { searchCode: searchCode2 } = await Promise.resolve().then(() => (init_search_code(), search_code_exports));
657
667
  const searchResults = await searchCode2(
658
668
  symbolName,
659
- path7,
669
+ path6,
660
670
  "**/*.{ts,tsx,js,jsx}",
661
671
  1e3,
662
672
  false
@@ -693,9 +703,9 @@ var TypeScriptAdapter = class {
693
703
  total_count: unique.length
694
704
  };
695
705
  }
696
- async findImplementations(symbolName, path7, limit = 50, offset = 0) {
697
- validateWorkspacePath(path7);
698
- await this.ensureIndex(path7);
706
+ async findImplementations(symbolName, path6, limit = 50, offset = 0) {
707
+ validateWorkspacePath(path6);
708
+ await this.ensureIndex(path6);
699
709
  const hits = symbolIndex.lookup(symbolName);
700
710
  if (hits.length === 0) return { implementations: [], total_count: 0 };
701
711
  const hit = hits[0];
@@ -726,7 +736,7 @@ var TypeScriptAdapter = class {
726
736
  const { searchCode: searchCode2 } = await Promise.resolve().then(() => (init_search_code(), search_code_exports));
727
737
  const searchResults = await searchCode2(
728
738
  symbolName,
729
- path7,
739
+ path6,
730
740
  "**/*.{ts,tsx,js,jsx}",
731
741
  1e3,
732
742
  false
@@ -905,33 +915,33 @@ var TypeScriptAdapter = class {
905
915
  var typescriptAdapter = new TypeScriptAdapter();
906
916
 
907
917
  // src/utils/tool-utils.ts
908
- async function wrapAdapterCall(methodName, symbol, path7, ...args) {
918
+ async function wrapAdapterCall(methodName, symbol, path6, ...args) {
909
919
  const method = typescriptAdapter[methodName];
910
- return await method.apply(typescriptAdapter, [symbol, path7, ...args]);
920
+ return await method.apply(typescriptAdapter, [symbol, path6, ...args]);
911
921
  }
912
922
 
913
923
  // src/tools/resolve-definition.ts
914
- async function resolveDefinition(symbol, path7) {
924
+ async function resolveDefinition(symbol, path6) {
915
925
  return await wrapAdapterCall(
916
926
  "resolveDefinition",
917
927
  symbol,
918
- path7
928
+ path6
919
929
  );
920
930
  }
921
931
 
922
932
  // src/tools/find-references.ts
923
933
  init_cjs_shims();
924
- async function findReferences(symbol, path7, limit = 50, offset = 0) {
925
- return await wrapAdapterCall("findReferences", symbol, path7, limit, offset);
934
+ async function findReferences(symbol, path6, limit = 50, offset = 0) {
935
+ return await wrapAdapterCall("findReferences", symbol, path6, limit, offset);
926
936
  }
927
937
 
928
938
  // src/tools/find-implementations.ts
929
939
  init_cjs_shims();
930
- async function findImplementations(symbol, path7, limit = 50, offset = 0) {
940
+ async function findImplementations(symbol, path6, limit = 50, offset = 0) {
931
941
  return await wrapAdapterCall(
932
942
  "findImplementations",
933
943
  symbol,
934
- path7,
944
+ path6,
935
945
  limit,
936
946
  offset
937
947
  );
@@ -944,7 +954,7 @@ async function getFileStructure(file) {
944
954
  }
945
955
 
946
956
  // src/index.ts
947
- init_search_code();
957
+ var import_core = require("@aiready/core");
948
958
 
949
959
  // src/tools/get-symbol-docs.ts
950
960
  init_cjs_shims();
@@ -978,8 +988,8 @@ async function getSymbolDocs(symbol, filePath) {
978
988
 
979
989
  // src/tools/build-symbol-index.ts
980
990
  init_cjs_shims();
981
- async function buildSymbolIndex(path7) {
982
- return await symbolIndex.buildIndex(path7);
991
+ async function buildSymbolIndex(path6) {
992
+ return await symbolIndex.buildIndex(path6);
983
993
  }
984
994
 
985
995
  // src/tools/call-hierarchy.ts
@@ -1139,117 +1149,6 @@ async function checkSymbolGrounding(symbol, filePath) {
1139
1149
  };
1140
1150
  }
1141
1151
 
1142
- // src/tools/codebase-audit.ts
1143
- init_cjs_shims();
1144
- var import_child_process2 = require("child_process");
1145
- var import_util2 = require("util");
1146
- var import_ripgrep2 = require("@vscode/ripgrep");
1147
- var fs4 = __toESM(require("fs"), 1);
1148
- var path6 = __toESM(require("path"), 1);
1149
- init_security();
1150
- var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
1151
- async function codebaseAudit(rootDir) {
1152
- const safePath = validateWorkspacePath(rootDir);
1153
- let debtMarkers = 0;
1154
- try {
1155
- const { stdout } = await execFileAsync2(import_ripgrep2.rgPath, [
1156
- "--count-matches",
1157
- "--fixed-strings",
1158
- "-e",
1159
- "TODO",
1160
- "-e",
1161
- "FIXME",
1162
- "--glob",
1163
- "!**/node_modules/**",
1164
- "--glob",
1165
- "!**/.git/**",
1166
- "--glob",
1167
- "!**/dist/**",
1168
- safePath
1169
- ]);
1170
- const lines = stdout.split("\n").filter(Boolean);
1171
- for (const line of lines) {
1172
- const match = line.match(/:(\d+)$/);
1173
- if (match) {
1174
- debtMarkers += parseInt(match[1], 10);
1175
- }
1176
- }
1177
- } catch (error) {
1178
- if (error.code !== 1) {
1179
- console.error("[Audit] Error counting debt markers:", error);
1180
- }
1181
- }
1182
- const emptyDirs = [];
1183
- const scanEmpty = (dir) => {
1184
- const files = fs4.readdirSync(dir);
1185
- if (files.length === 0) {
1186
- emptyDirs.push(path6.relative(safePath, dir));
1187
- return;
1188
- }
1189
- for (const file of files) {
1190
- const fullPath = path6.join(dir, file);
1191
- if (fs4.statSync(fullPath).isDirectory()) {
1192
- if (["node_modules", ".git", "dist", ".sst", ".turbo", ".next"].includes(
1193
- file
1194
- ))
1195
- continue;
1196
- scanEmpty(fullPath);
1197
- }
1198
- }
1199
- };
1200
- scanEmpty(safePath);
1201
- const orphanedFiles = [];
1202
- const allFiles = [];
1203
- const collectFiles = (dir) => {
1204
- const files = fs4.readdirSync(dir);
1205
- for (const file of files) {
1206
- const fullPath = path6.join(dir, file);
1207
- if (fs4.statSync(fullPath).isDirectory()) {
1208
- if (["node_modules", ".git", "dist", ".sst", ".turbo", ".next"].includes(
1209
- file
1210
- ))
1211
- continue;
1212
- collectFiles(fullPath);
1213
- } else if (file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".tsx") || file.endsWith(".jsx")) {
1214
- allFiles.push(fullPath);
1215
- }
1216
- }
1217
- };
1218
- collectFiles(safePath);
1219
- for (const file of allFiles) {
1220
- const base = path6.basename(file, path6.extname(file));
1221
- if (base === "index" || base.endsWith(".test") || base.endsWith(".spec") || base === "sst.config")
1222
- continue;
1223
- let referenced = false;
1224
- try {
1225
- const { status } = await execFileAsync2(import_ripgrep2.rgPath, [
1226
- "--quiet",
1227
- "--fixed-strings",
1228
- "--word-regexp",
1229
- "--glob",
1230
- `!${path6.relative(safePath, file)}`,
1231
- "--glob",
1232
- "!**/node_modules/**",
1233
- "--glob",
1234
- "!**/.git/**",
1235
- base,
1236
- safePath
1237
- ]);
1238
- if (status === 0) referenced = true;
1239
- } catch (e) {
1240
- if (e.code === 0) referenced = true;
1241
- }
1242
- if (!referenced) {
1243
- orphanedFiles.push(path6.relative(safePath, file));
1244
- }
1245
- }
1246
- return {
1247
- debtMarkers,
1248
- emptyDirs,
1249
- orphanedFiles
1250
- };
1251
- }
1252
-
1253
1152
  // src/index.ts
1254
1153
  var import_types2 = require("@modelcontextprotocol/sdk/types.js");
1255
1154
  var ASTExplorerServer = class {
@@ -1368,9 +1267,35 @@ var ASTExplorerServer = class {
1368
1267
  required: ["file"]
1369
1268
  }
1370
1269
  },
1270
+ {
1271
+ name: "grep_search",
1272
+ description: "Fast, context-aware text search via ripgrep. Supports context lines and result summarization.",
1273
+ inputSchema: {
1274
+ type: "object",
1275
+ properties: {
1276
+ pattern: { type: "string", description: "Search pattern" },
1277
+ path: { type: "string", description: "Directory to search" },
1278
+ include: {
1279
+ type: "array",
1280
+ items: { type: "string" },
1281
+ description: "Glob filter"
1282
+ },
1283
+ exclude: {
1284
+ type: "array",
1285
+ items: { type: "string" },
1286
+ description: "Exclusion filter"
1287
+ },
1288
+ limit: { type: "number", default: 50 },
1289
+ offset: { type: "number", default: 0 },
1290
+ context: { type: "number", default: 2 },
1291
+ isRegex: { type: "boolean", default: true }
1292
+ },
1293
+ required: ["pattern", "path"]
1294
+ }
1295
+ },
1371
1296
  {
1372
1297
  name: "search_code",
1373
- description: "Fast regex search via bundled ripgrep.",
1298
+ description: "Fast regex search via bundled ripgrep (alias for grep_search).",
1374
1299
  inputSchema: {
1375
1300
  type: "object",
1376
1301
  properties: {
@@ -1437,8 +1362,8 @@ var ASTExplorerServer = class {
1437
1362
  }
1438
1363
  },
1439
1364
  {
1440
- name: "codebase_audit",
1441
- description: "Performs a codebase-level audit for technical debt (TODOs) and bloat (empty dirs, orphans).",
1365
+ name: "hygiene_audit",
1366
+ description: "Performs a codebase-level hygiene check for technical debt (TODOs) and waste (empty dirs, orphans).",
1442
1367
  inputSchema: {
1443
1368
  type: "object",
1444
1369
  properties: {
@@ -1449,6 +1374,28 @@ var ASTExplorerServer = class {
1449
1374
  },
1450
1375
  required: ["path"]
1451
1376
  }
1377
+ },
1378
+ {
1379
+ name: "metabolism_audit",
1380
+ description: "Alias for hygiene_audit (Serverless Claw compat).",
1381
+ inputSchema: {
1382
+ type: "object",
1383
+ properties: {
1384
+ path: { type: "string" }
1385
+ },
1386
+ required: ["path"]
1387
+ }
1388
+ },
1389
+ {
1390
+ name: "codebase_audit",
1391
+ description: "Alias for hygiene_audit.",
1392
+ inputSchema: {
1393
+ type: "object",
1394
+ properties: {
1395
+ path: { type: "string" }
1396
+ },
1397
+ required: ["path"]
1398
+ }
1452
1399
  }
1453
1400
  ]
1454
1401
  };
@@ -1458,8 +1405,8 @@ var ASTExplorerServer = class {
1458
1405
  try {
1459
1406
  switch (name) {
1460
1407
  case "resolve_definition": {
1461
- const { symbol, path: path7 } = ResolveDefinitionSchema.parse(args);
1462
- const results = await resolveDefinition(symbol, path7);
1408
+ const { symbol, path: path6 } = ResolveDefinitionSchema.parse(args);
1409
+ const results = await resolveDefinition(symbol, path6);
1463
1410
  return {
1464
1411
  content: [
1465
1412
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -1467,8 +1414,8 @@ var ASTExplorerServer = class {
1467
1414
  };
1468
1415
  }
1469
1416
  case "find_references": {
1470
- const { symbol, path: path7, limit, offset } = FindReferencesSchema.parse(args);
1471
- const results = await findReferences(symbol, path7, limit, offset);
1417
+ const { symbol, path: path6, limit, offset } = FindReferencesSchema.parse(args);
1418
+ const results = await findReferences(symbol, path6, limit, offset);
1472
1419
  return {
1473
1420
  content: [
1474
1421
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -1476,10 +1423,10 @@ var ASTExplorerServer = class {
1476
1423
  };
1477
1424
  }
1478
1425
  case "find_implementations": {
1479
- const { symbol, path: path7, limit, offset } = FindImplementationsSchema.parse(args);
1426
+ const { symbol, path: path6, limit, offset } = FindImplementationsSchema.parse(args);
1480
1427
  const results = await findImplementations(
1481
1428
  symbol,
1482
- path7,
1429
+ path6,
1483
1430
  limit,
1484
1431
  offset
1485
1432
  );
@@ -1498,39 +1445,67 @@ var ASTExplorerServer = class {
1498
1445
  ]
1499
1446
  };
1500
1447
  }
1448
+ case "grep_search": {
1449
+ const {
1450
+ pattern,
1451
+ path: path6,
1452
+ include,
1453
+ exclude,
1454
+ limit,
1455
+ offset,
1456
+ context,
1457
+ isRegex
1458
+ } = GrepSearchSchema.parse(args);
1459
+ const result = await (0, import_core.grepSearch)({
1460
+ pattern,
1461
+ path: path6,
1462
+ include,
1463
+ exclude,
1464
+ limit,
1465
+ offset,
1466
+ context,
1467
+ isRegex
1468
+ });
1469
+ return {
1470
+ content: [
1471
+ { type: "text", text: JSON.stringify(result, null, 2) }
1472
+ ]
1473
+ };
1474
+ }
1501
1475
  case "search_code": {
1502
- const { pattern, path: path7, filePattern, limit, offset, regex } = SearchCodeSchema.parse(args);
1503
- const results = await searchCode(
1476
+ const { pattern, path: path6, filePattern, limit, offset, regex } = SearchCodeSchema.parse(args);
1477
+ const result = await (0, import_core.grepSearch)({
1504
1478
  pattern,
1505
- path7,
1506
- filePattern,
1479
+ path: path6,
1480
+ include: filePattern ? [filePattern] : [],
1507
1481
  limit,
1508
- regex,
1509
- offset
1510
- );
1482
+ offset,
1483
+ isRegex: regex,
1484
+ context: 0
1485
+ });
1511
1486
  return {
1512
1487
  content: [
1513
- { type: "text", text: JSON.stringify(results, null, 2) }
1488
+ { type: "text", text: JSON.stringify(result, null, 2) }
1514
1489
  ]
1515
1490
  };
1516
1491
  }
1517
1492
  case "get_symbol_docs": {
1518
- const { symbol, path: path7 } = GetSymbolDocsSchema.parse(args);
1519
- const docs = await getSymbolDocs(symbol, path7);
1493
+ const { symbol, path: path6 } = GetSymbolDocsSchema.parse(args);
1494
+ const docs = await getSymbolDocs(symbol, path6);
1520
1495
  return {
1521
1496
  content: [{ type: "text", text: JSON.stringify(docs, null, 2) }]
1522
1497
  };
1523
1498
  }
1524
1499
  case "build_symbol_index": {
1525
- const { path: path7 } = BuildSymbolIndexSchema.parse(args);
1526
- const stats = await buildSymbolIndex(path7);
1500
+ const { path: path6 } = BuildSymbolIndexSchema.parse(args);
1501
+ const stats = await buildSymbolIndex(path6);
1527
1502
  return {
1528
1503
  content: [{ type: "text", text: JSON.stringify(stats, null, 2) }]
1529
1504
  };
1530
1505
  }
1531
1506
  case "get_call_hierarchy": {
1532
- const { symbol, path: path7, direction } = GetCallHierarchySchema.parse(args);
1533
- const hierarchy = await getCallHierarchy(symbol, path7, direction);
1507
+ const { symbol, path: path6, direction } = GetCallHierarchySchema.parse(args);
1508
+ const hierarchy = await getCallHierarchy(symbol, path6, direction);
1534
1509
  return {
1535
1510
  content: [
1536
1511
  { type: "text", text: JSON.stringify(hierarchy, null, 2) }
@@ -1538,21 +1513,38 @@ var ASTExplorerServer = class {
1538
1513
  };
1539
1514
  }
1540
1515
  case "check_symbol_grounding": {
1541
- const { symbol, path: path7 } = CheckSymbolGroundingSchema.parse(args);
1542
- const result = await checkSymbolGrounding(symbol, path7);
1516
+ const { symbol, path: path6 } = CheckSymbolGroundingSchema.parse(args);
1517
+ const result = await checkSymbolGrounding(symbol, path6);
1543
1518
  return {
1544
1519
  content: [
1545
1520
  { type: "text", text: JSON.stringify(result, null, 2) }
1546
1521
  ]
1547
1522
  };
1548
1523
  }
1524
+ case "hygiene_audit":
1525
+ case "metabolism_audit":
1549
1526
  case "codebase_audit": {
1550
- const { path: path7 } = CodebaseAuditSchema.parse(args);
1551
- const result = await codebaseAudit(path7);
1527
+ const { path: rootDir } = CodebaseAuditSchema.parse(args);
1528
+ const { HygieneAuditProvider } = await import("@aiready/hygiene-audit");
1529
+ const provider = new HygieneAuditProvider();
1530
+ const result = await provider.analyze({ rootDir });
1531
+ const allIssues = result.results.flatMap((r) => r.issues);
1532
+ const metadata = {
1533
+ debtMarkers: result.metadata?.debtMarkers || 0,
1534
+ emptyDirs: result.metadata?.emptyDirs || [],
1535
+ orphanedFiles: result.metadata?.orphanedFiles || [],
1536
+ findings: allIssues.map((i) => ({
1537
+ expected: i.recommendation || i.suggestion || "",
1538
+ actual: i.message,
1539
+ severity: i.severity === "critical" ? "P0" : i.severity === "major" ? "P1" : "P2",
1540
+ recommendation: i.recommendation || i.suggestion || ""
1541
+ }))
1542
+ };
1552
1543
  return {
1553
1544
  content: [
1554
1545
  { type: "text", text: JSON.stringify(result, null, 2) }
1555
- ]
1546
+ ],
1547
+ metadata
1556
1548
  };
1557
1549
  }
1558
1550
  default: