@aiready/core 0.23.0 → 0.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -321,7 +321,8 @@ async function scanFiles(options) {
321
321
  const files = await glob(include, {
322
322
  cwd: rootDir,
323
323
  ignore: finalExclude,
324
- absolute: true
324
+ absolute: true,
325
+ nodir: true
325
326
  });
326
327
  const gitignoreFiles = await glob("**/.gitignore", {
327
328
  cwd: rootDir,
@@ -438,8 +439,14 @@ function isSourceFile(filePath) {
438
439
  }
439
440
 
440
441
  // src/utils/cli-helpers.ts
441
- import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
442
- import { join as join2, dirname as dirname2 } from "path";
442
+ import {
443
+ writeFileSync,
444
+ mkdirSync,
445
+ existsSync as existsSync2,
446
+ readdirSync,
447
+ statSync
448
+ } from "fs";
449
+ import { join as join2, dirname as dirname2, resolve as resolvePath } from "path";
443
450
  function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
444
451
  let outputPath;
445
452
  if (userPath) {
@@ -525,6 +532,111 @@ function getSeverityColor(severity, chalk) {
525
532
  return chalk.white;
526
533
  }
527
534
  }
535
+ function findLatestReport(dirPath) {
536
+ const aireadyDir = resolvePath(dirPath, ".aiready");
537
+ if (!existsSync2(aireadyDir)) {
538
+ return null;
539
+ }
540
+ let files = readdirSync(aireadyDir).filter(
541
+ (f) => f.startsWith("aiready-report-") && f.endsWith(".json")
542
+ );
543
+ if (files.length === 0) {
544
+ files = readdirSync(aireadyDir).filter(
545
+ (f) => f.startsWith("aiready-scan-") && f.endsWith(".json")
546
+ );
547
+ }
548
+ if (files.length === 0) {
549
+ return null;
550
+ }
551
+ const sortedFiles = files.map((f) => ({
552
+ name: f,
553
+ path: resolvePath(aireadyDir, f),
554
+ mtime: statSync(resolvePath(aireadyDir, f)).mtime
555
+ })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
556
+ return sortedFiles[0].path;
557
+ }
558
+ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
559
+ try {
560
+ let reportFiles = [];
561
+ if (existsSync2(scanReportsDir)) {
562
+ const files = readdirSync(scanReportsDir);
563
+ if (files.length > 0) {
564
+ const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
565
+ reportFiles = files.filter((file) => prefixRegex.test(file));
566
+ }
567
+ }
568
+ if (reportFiles.length === 0) return null;
569
+ reportFiles.sort((a, b) => {
570
+ const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
571
+ const idB = parseInt(b.match(/\d+/)?.[0] || "0", 10);
572
+ return idB - idA;
573
+ });
574
+ return join2(scanReportsDir, reportFiles[0]);
575
+ } catch (e) {
576
+ console.error("Error while finding latest scan report:", e);
577
+ return null;
578
+ }
579
+ }
580
+
581
+ // src/utils/provider-utils.ts
582
+ function groupIssuesByFile(issues) {
583
+ const fileIssuesMap = /* @__PURE__ */ new Map();
584
+ for (const issue of issues) {
585
+ const file = issue.location?.file ?? "unknown";
586
+ if (!fileIssuesMap.has(file)) fileIssuesMap.set(file, []);
587
+ fileIssuesMap.get(file).push(issue);
588
+ }
589
+ return Array.from(fileIssuesMap.entries()).map(([fileName, issueList]) => ({
590
+ fileName,
591
+ issues: issueList,
592
+ metrics: {}
593
+ }));
594
+ }
595
+ function buildSimpleProviderScore(toolName, summary, rawData = {}) {
596
+ return {
597
+ toolName,
598
+ score: summary.score ?? 0,
599
+ rawMetrics: { ...summary, ...rawData },
600
+ factors: [],
601
+ recommendations: (summary.recommendations ?? []).map((action) => ({
602
+ action,
603
+ estimatedImpact: 5,
604
+ priority: "medium"
605
+ }))
606
+ };
607
+ }
608
+ function buildSpokeOutput(toolName, version, summary, results, metadata = {}) {
609
+ return SpokeOutputSchema.parse({
610
+ results,
611
+ summary,
612
+ metadata: {
613
+ toolName,
614
+ version,
615
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
616
+ ...metadata
617
+ }
618
+ });
619
+ }
620
+ function createProvider(config) {
621
+ return {
622
+ id: config.id,
623
+ alias: config.alias,
624
+ defaultWeight: config.defaultWeight,
625
+ async analyze(options) {
626
+ const report = await config.analyzeReport(options);
627
+ return buildSpokeOutput(
628
+ config.id,
629
+ config.version,
630
+ config.getSummary(report),
631
+ config.getResults(report),
632
+ config.getMetadata?.(report) ?? {}
633
+ );
634
+ },
635
+ score(output, options) {
636
+ return config.score(output, options);
637
+ }
638
+ };
639
+ }
528
640
 
529
641
  // src/utils/ast-parser.ts
530
642
  import { parse as parse2 } from "@typescript-eslint/typescript-estree";
@@ -652,8 +764,10 @@ var TypeScriptParser = class {
652
764
  // camelCase for variables and functions
653
765
  variablePattern: /^[a-z][a-zA-Z0-9]*$/,
654
766
  functionPattern: /^[a-z][a-zA-Z0-9]*$/,
655
- // PascalCase for classes
767
+ // PascalCase for classes, types and interfaces
656
768
  classPattern: /^[A-Z][a-zA-Z0-9]*$/,
769
+ typePattern: /^[A-Z][a-zA-Z0-9]*$/,
770
+ interfacePattern: /^[A-Z][a-zA-Z0-9]*$/,
657
771
  // UPPER_CASE for constants
658
772
  constantPattern: /^[A-Z][A-Z0-9_]*$/,
659
773
  // Common exceptions (React hooks, etc.)
@@ -952,6 +1066,101 @@ async function setupParser(language) {
952
1066
  }
953
1067
  }
954
1068
 
1069
+ // src/parsers/metadata-utils.ts
1070
+ function analyzeNodeMetadata(node, code, options) {
1071
+ const metadata = {
1072
+ isPure: true,
1073
+ hasSideEffects: false
1074
+ };
1075
+ try {
1076
+ let prev = node.previousSibling || null;
1077
+ while (prev && /comment/i.test(prev.type)) {
1078
+ const text = prev.text || "";
1079
+ if (text.trim().startsWith("/**") || text.trim().startsWith("/*")) {
1080
+ metadata.documentation = {
1081
+ content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
1082
+ type: "comment"
1083
+ };
1084
+ break;
1085
+ }
1086
+ if (text.trim().startsWith("///")) {
1087
+ metadata.documentation = {
1088
+ content: text.replace(/^\/\/\//, "").trim(),
1089
+ type: "xml-doc"
1090
+ };
1091
+ break;
1092
+ }
1093
+ if (text.trim().startsWith("//")) {
1094
+ metadata.documentation = {
1095
+ content: text.replace(/^\/\//, "").trim(),
1096
+ type: "comment"
1097
+ };
1098
+ break;
1099
+ }
1100
+ prev = prev.previousSibling;
1101
+ }
1102
+ if (node.type === "function_definition") {
1103
+ const body2 = node.childForFieldName ? node.childForFieldName("body") : node.children.find((c) => c.type === "block");
1104
+ if (body2 && body2.children.length > 0) {
1105
+ const firstStmt = body2.children[0];
1106
+ if (firstStmt.type === "expression_statement" && firstStmt.firstChild?.type === "string") {
1107
+ metadata.documentation = {
1108
+ content: firstStmt.firstChild.text.replace(/['"`]/g, "").trim(),
1109
+ type: "docstring"
1110
+ };
1111
+ }
1112
+ }
1113
+ }
1114
+ } catch {
1115
+ }
1116
+ const defaultSignatures = [
1117
+ "console.",
1118
+ "fmt.",
1119
+ "panic(",
1120
+ "os.Exit",
1121
+ "log.",
1122
+ "Console.Write",
1123
+ "File.Write",
1124
+ "System.out",
1125
+ "System.err",
1126
+ "Files.write",
1127
+ "process.exit",
1128
+ "exit("
1129
+ ];
1130
+ const signatures = Array.from(
1131
+ /* @__PURE__ */ new Set([...options?.sideEffectSignatures || [], ...defaultSignatures])
1132
+ );
1133
+ const walk = (n) => {
1134
+ try {
1135
+ const t = n.type || "";
1136
+ if (/assign|assignment|assignment_statement|assignment_expression|throw|throw_statement|send_statement|global_statement|nonlocal_statement/i.test(
1137
+ t
1138
+ )) {
1139
+ metadata.isPure = false;
1140
+ metadata.hasSideEffects = true;
1141
+ }
1142
+ const text = n.text || "";
1143
+ for (const s of signatures) {
1144
+ if (text.includes(s)) {
1145
+ metadata.isPure = false;
1146
+ metadata.hasSideEffects = true;
1147
+ break;
1148
+ }
1149
+ }
1150
+ for (let i = 0; i < n.childCount; i++) {
1151
+ const c = n.child(i);
1152
+ if (c) walk(c);
1153
+ }
1154
+ } catch {
1155
+ }
1156
+ };
1157
+ const body = node.childForFieldName?.("body") || node.children.find(
1158
+ (c) => /body|block|class_body|declaration_list|function_body/.test(c.type)
1159
+ );
1160
+ if (body) walk(body);
1161
+ return metadata;
1162
+ }
1163
+
955
1164
  // src/parsers/python-parser.ts
956
1165
  var PythonParser = class {
957
1166
  constructor() {
@@ -974,38 +1183,9 @@ var PythonParser = class {
974
1183
  return this.parser.parse(code);
975
1184
  }
976
1185
  analyzeMetadata(node, code) {
977
- const metadata = {
978
- isPure: true,
979
- hasSideEffects: false
980
- };
981
- const body = node.childForFieldName("body");
982
- if (body && body.children.length > 0) {
983
- const firstStmt = body.children[0];
984
- if (firstStmt.type === "expression_statement" && firstStmt.firstChild?.type === "string") {
985
- metadata.documentation = {
986
- content: firstStmt.firstChild.text.replace(/['"`]/g, "").trim(),
987
- type: "docstring"
988
- };
989
- }
990
- }
991
- const walk = (n) => {
992
- if (n.type === "global_statement" || n.type === "nonlocal_statement") {
993
- metadata.isPure = false;
994
- metadata.hasSideEffects = true;
995
- }
996
- if (n.type === "call") {
997
- const functionNode = n.childForFieldName("function");
998
- if (functionNode && ["print", "input", "open"].includes(functionNode.text)) {
999
- metadata.isPure = false;
1000
- metadata.hasSideEffects = true;
1001
- }
1002
- }
1003
- for (const child of n.children) {
1004
- walk(child);
1005
- }
1006
- };
1007
- if (body) walk(body);
1008
- return metadata;
1186
+ return analyzeNodeMetadata(node, code, {
1187
+ sideEffectSignatures: ["print(", "input(", "open("]
1188
+ });
1009
1189
  }
1010
1190
  parse(code, filePath) {
1011
1191
  if (!this.initialized || !this.parser) {
@@ -1354,6 +1534,113 @@ var PythonParser = class {
1354
1534
  }
1355
1535
  };
1356
1536
 
1537
+ // src/parsers/shared-parser-utils.ts
1538
+ var SIDE_EFFECT_KEYWORDS = [
1539
+ "print(",
1540
+ "console.",
1541
+ "System.out",
1542
+ "System.err",
1543
+ "fmt.",
1544
+ "File.Write",
1545
+ "Files.write",
1546
+ "os.Exit",
1547
+ "panic(",
1548
+ "throw ",
1549
+ "Logging.",
1550
+ "log."
1551
+ ];
1552
+ function analyzeGeneralMetadata(node, code, options = {}) {
1553
+ const metadata = {
1554
+ isPure: true,
1555
+ hasSideEffects: false
1556
+ };
1557
+ try {
1558
+ let prev = node.previousSibling || null;
1559
+ while (prev && /comment/i.test(prev.type)) {
1560
+ const text = prev.text || "";
1561
+ if (text.trim().startsWith("/**")) {
1562
+ metadata.documentation = {
1563
+ content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
1564
+ type: "jsdoc"
1565
+ };
1566
+ break;
1567
+ }
1568
+ if (text.trim().startsWith("///")) {
1569
+ metadata.documentation = {
1570
+ content: text.replace(/^\/\/\//, "").trim(),
1571
+ type: "xml-doc"
1572
+ };
1573
+ break;
1574
+ }
1575
+ if (text.trim().startsWith("//")) {
1576
+ metadata.documentation = {
1577
+ content: text.replace(/^\/\//, "").trim(),
1578
+ type: "comment"
1579
+ };
1580
+ break;
1581
+ }
1582
+ prev = prev.previousSibling;
1583
+ }
1584
+ } catch {
1585
+ }
1586
+ const signatures = [
1587
+ ...SIDE_EFFECT_KEYWORDS,
1588
+ ...options.sideEffectSignatures || []
1589
+ ];
1590
+ const walk = (n) => {
1591
+ if (/assign|assignment|assignment_statement|assignment_expression/i.test(
1592
+ n.type
1593
+ )) {
1594
+ metadata.isPure = false;
1595
+ metadata.hasSideEffects = true;
1596
+ }
1597
+ const text = n.text;
1598
+ for (const sig of signatures) {
1599
+ if (text.includes(sig)) {
1600
+ metadata.isPure = false;
1601
+ metadata.hasSideEffects = true;
1602
+ break;
1603
+ }
1604
+ }
1605
+ if (!metadata.hasSideEffects) {
1606
+ for (let i = 0; i < n.childCount; i++) {
1607
+ const child = n.child(i);
1608
+ if (child) walk(child);
1609
+ }
1610
+ }
1611
+ };
1612
+ walk(node);
1613
+ return metadata;
1614
+ }
1615
+ function extractParameterNames(node) {
1616
+ const params = [];
1617
+ const candidates = [
1618
+ // common field name
1619
+ node.childForFieldName ? node.childForFieldName("parameters") : null,
1620
+ node.childForFieldName ? node.childForFieldName("parameter_list") : null,
1621
+ node.children.find((c) => c.type === "parameter_list") || null,
1622
+ node.children.find((c) => c.type === "parameters") || null,
1623
+ node.children.find((c) => c.type === "formal_parameters") || null,
1624
+ node.children.find((c) => c.type === "formal_parameter") || null
1625
+ ];
1626
+ const list = candidates.find(Boolean);
1627
+ if (!list) return params;
1628
+ for (const child of list.children) {
1629
+ if (!child) continue;
1630
+ const id = child.childForFieldName?.("name") || child.children.find(
1631
+ (c) => [
1632
+ "identifier",
1633
+ "variable_name",
1634
+ "name",
1635
+ "parameter",
1636
+ "formal_parameter"
1637
+ ].includes(c.type)
1638
+ ) || (child.type === "identifier" ? child : void 0);
1639
+ if (id && typeof id.text === "string") params.push(id.text);
1640
+ }
1641
+ return params;
1642
+ }
1643
+
1357
1644
  // src/parsers/java-parser.ts
1358
1645
  var JavaParser = class {
1359
1646
  constructor() {
@@ -1376,47 +1663,14 @@ var JavaParser = class {
1376
1663
  return this.parser.parse(code);
1377
1664
  }
1378
1665
  analyzeMetadata(node, code) {
1379
- const metadata = {
1380
- isPure: true,
1381
- hasSideEffects: false
1382
- };
1383
- let prev = node.previousSibling;
1384
- while (prev && (prev.type === "comment" || prev.type === "line_comment")) {
1385
- if (prev.text.startsWith("/**")) {
1386
- metadata.documentation = {
1387
- content: prev.text.replace(/[/*]/g, "").trim(),
1388
- type: "xml-doc"
1389
- // Using xml-doc as a catch-all for structured or we can add 'javadoc'
1390
- };
1391
- break;
1392
- }
1393
- prev = prev.previousSibling;
1394
- }
1395
- const walk = (n) => {
1396
- if (n.type === "assignment_expression") {
1397
- metadata.isPure = false;
1398
- metadata.hasSideEffects = true;
1399
- }
1400
- if (n.type === "method_invocation") {
1401
- const text = n.text;
1402
- if (text.includes("System.out.print") || text.includes("System.err.print") || text.includes("Files.write")) {
1403
- metadata.isPure = false;
1404
- metadata.hasSideEffects = true;
1405
- }
1406
- }
1407
- if (n.type === "throw_statement") {
1408
- metadata.isPure = false;
1409
- metadata.hasSideEffects = true;
1410
- }
1411
- for (const child of n.children) {
1412
- walk(child);
1413
- }
1414
- };
1415
- const body = node.children.find(
1416
- (c) => c.type === "block" || c.type === "class_body"
1417
- );
1418
- if (body) walk(body);
1419
- return metadata;
1666
+ return analyzeGeneralMetadata(node, code, {
1667
+ sideEffectSignatures: [
1668
+ "System.out",
1669
+ "System.err",
1670
+ "Files.write",
1671
+ "Logging."
1672
+ ]
1673
+ });
1420
1674
  }
1421
1675
  parse(code, filePath) {
1422
1676
  if (!this.initialized || !this.parser) {
@@ -1516,7 +1770,7 @@ var JavaParser = class {
1516
1770
  const imports = [];
1517
1771
  for (const node of rootNode.children) {
1518
1772
  if (node.type === "import_declaration") {
1519
- let sourceArr = [];
1773
+ const sourceArr = [];
1520
1774
  let isStatic = false;
1521
1775
  let isWildcard = false;
1522
1776
  for (const child of node.children) {
@@ -1614,14 +1868,7 @@ var JavaParser = class {
1614
1868
  }
1615
1869
  }
1616
1870
  extractParameters(node) {
1617
- const paramsNode = node.children.find(
1618
- (c) => c.type === "formal_parameters"
1619
- );
1620
- if (!paramsNode) return [];
1621
- return paramsNode.children.filter((c) => c.type === "formal_parameter").map((c) => {
1622
- const idNode = c.children.find((child) => child.type === "identifier");
1623
- return idNode ? idNode.text : "unknown";
1624
- });
1871
+ return extractParameterNames(node);
1625
1872
  }
1626
1873
  getNamingConventions() {
1627
1874
  return {
@@ -1659,47 +1906,9 @@ var CSharpParser = class {
1659
1906
  return this.parser.parse(code);
1660
1907
  }
1661
1908
  analyzeMetadata(node, code) {
1662
- const metadata = {
1663
- isPure: true,
1664
- hasSideEffects: false
1665
- };
1666
- let prev = node.previousSibling;
1667
- while (prev && (prev.type === "comment" || prev.type === "triple_slash_comment")) {
1668
- if (prev.text.trim().startsWith("///") || prev.type === "triple_slash_comment") {
1669
- metadata.documentation = {
1670
- content: prev.text.replace("///", "").trim(),
1671
- type: "xml-doc"
1672
- };
1673
- break;
1674
- }
1675
- prev = prev.previousSibling;
1676
- }
1677
- const walk = (n) => {
1678
- if (n.type === "assignment_expression") {
1679
- metadata.isPure = false;
1680
- metadata.hasSideEffects = true;
1681
- }
1682
- if (n.type === "invocation_expression") {
1683
- const text = n.text;
1684
- if (text.includes("Console.Write") || text.includes("File.Write") || text.includes("Log.")) {
1685
- metadata.isPure = false;
1686
- metadata.hasSideEffects = true;
1687
- }
1688
- }
1689
- if (n.type === "throw_statement") {
1690
- metadata.isPure = false;
1691
- metadata.hasSideEffects = true;
1692
- }
1693
- for (let i = 0; i < n.childCount; i++) {
1694
- const child = n.child(i);
1695
- if (child) walk(child);
1696
- }
1697
- };
1698
- const body = node.children.find(
1699
- (c) => c.type === "block" || c.type === "declaration_list"
1700
- );
1701
- if (body) walk(body);
1702
- return metadata;
1909
+ return analyzeGeneralMetadata(node, code, {
1910
+ sideEffectSignatures: ["Console.Write", "File.Write", "Logging."]
1911
+ });
1703
1912
  }
1704
1913
  parse(code, filePath) {
1705
1914
  if (!this.initialized || !this.parser) {
@@ -1904,19 +2113,7 @@ var CSharpParser = class {
1904
2113
  return modifiers;
1905
2114
  }
1906
2115
  extractParameters(node) {
1907
- const params = [];
1908
- const parameterList = node.childForFieldName("parameters") || node.children.find((c) => c.type === "parameter_list");
1909
- if (parameterList) {
1910
- for (const param of parameterList.children) {
1911
- if (param.type === "parameter") {
1912
- const nameNode = param.childForFieldName("name") || param.children.find((c) => c.type === "identifier");
1913
- if (nameNode) {
1914
- params.push(nameNode.text);
1915
- }
1916
- }
1917
- }
1918
- }
1919
- return params;
2116
+ return extractParameterNames(node);
1920
2117
  }
1921
2118
  getNamingConventions() {
1922
2119
  return {
@@ -1953,40 +2150,9 @@ var GoParser = class {
1953
2150
  return this.parser.parse(code);
1954
2151
  }
1955
2152
  analyzeMetadata(node, code) {
1956
- const metadata = {
1957
- isPure: true,
1958
- hasSideEffects: false
1959
- };
1960
- let prev = node.previousSibling;
1961
- while (prev && prev.type === "comment") {
1962
- metadata.documentation = {
1963
- content: prev.text.replace(/\/\/|\/\*|\*\//g, "").trim(),
1964
- type: "comment"
1965
- };
1966
- break;
1967
- }
1968
- const walk = (n) => {
1969
- if (n.type === "send_statement" || n.type === "expression_statement" && n.text.includes("<-")) {
1970
- metadata.isPure = false;
1971
- metadata.hasSideEffects = true;
1972
- }
1973
- if (n.type === "assignment_statement" || n.type === "short_var_declaration") {
1974
- }
1975
- if (n.type === "call_expression") {
1976
- const text = n.text;
1977
- if (text.includes("fmt.Print") || text.includes("os.Exit") || text.includes("panic(") || text.includes("log.")) {
1978
- metadata.isPure = false;
1979
- metadata.hasSideEffects = true;
1980
- }
1981
- }
1982
- for (let i = 0; i < n.childCount; i++) {
1983
- const child = n.child(i);
1984
- if (child) walk(child);
1985
- }
1986
- };
1987
- const body = node.childForFieldName("body");
1988
- if (body) walk(body);
1989
- return metadata;
2153
+ return analyzeGeneralMetadata(node, code, {
2154
+ sideEffectSignatures: ["<-", "fmt.Print", "fmt.Fprintf", "os.Exit"]
2155
+ });
1990
2156
  }
1991
2157
  parse(code, filePath) {
1992
2158
  if (!this.initialized || !this.parser) {
@@ -2208,17 +2374,7 @@ var GoParser = class {
2208
2374
  return exports;
2209
2375
  }
2210
2376
  extractParameters(node) {
2211
- const params = [];
2212
- const parameterList = node.childForFieldName("parameters") || node.children.find((c) => c.type === "parameter_list");
2213
- if (parameterList) {
2214
- for (const param of parameterList.children) {
2215
- if (param.type === "parameter_declaration") {
2216
- const names = param.children.filter((c) => c.type === "identifier");
2217
- names.forEach((n) => params.push(n.text));
2218
- }
2219
- }
2220
- }
2221
- return params;
2377
+ return extractParameterNames(node);
2222
2378
  }
2223
2379
  getNamingConventions() {
2224
2380
  return {
@@ -2568,34 +2724,47 @@ var CONFIG_FILES = [
2568
2724
  async function loadConfig(rootDir) {
2569
2725
  let currentDir = resolve(rootDir);
2570
2726
  while (true) {
2727
+ const foundConfigs = [];
2571
2728
  for (const configFile of CONFIG_FILES) {
2729
+ if (existsSync4(join4(currentDir, configFile))) {
2730
+ foundConfigs.push(configFile);
2731
+ }
2732
+ }
2733
+ if (foundConfigs.length > 0) {
2734
+ if (foundConfigs.length > 1) {
2735
+ console.warn(
2736
+ `\u26A0\uFE0F Multiple configuration files found in ${currentDir}: ${foundConfigs.join(
2737
+ ", "
2738
+ )}. Using ${foundConfigs[0]}.`
2739
+ );
2740
+ } else {
2741
+ }
2742
+ const configFile = foundConfigs[0];
2572
2743
  const configPath = join4(currentDir, configFile);
2573
- if (existsSync4(configPath)) {
2744
+ try {
2745
+ let config;
2746
+ if (configFile.endsWith(".js")) {
2747
+ const fileUrl = pathToFileURL(configPath).href;
2748
+ const module = await import(`${fileUrl}?t=${Date.now()}`);
2749
+ config = module.default || module;
2750
+ } else {
2751
+ const content = readFileSync(configPath, "utf-8");
2752
+ config = JSON.parse(content);
2753
+ }
2754
+ if (typeof config !== "object" || config === null) {
2755
+ throw new Error("Config must be an object");
2756
+ }
2757
+ return config;
2758
+ } catch (error) {
2759
+ const errorMessage = error instanceof Error ? error.message : String(error);
2760
+ const e = new Error(
2761
+ `Failed to load config from ${configPath}: ${errorMessage}`
2762
+ );
2574
2763
  try {
2575
- let config;
2576
- if (configFile.endsWith(".js")) {
2577
- const fileUrl = pathToFileURL(configPath).href;
2578
- const module = await import(`${fileUrl}?t=${Date.now()}`);
2579
- config = module.default || module;
2580
- } else {
2581
- const content = readFileSync(configPath, "utf-8");
2582
- config = JSON.parse(content);
2583
- }
2584
- if (typeof config !== "object" || config === null) {
2585
- throw new Error("Config must be an object");
2586
- }
2587
- return config;
2588
- } catch (error) {
2589
- const errorMessage = error instanceof Error ? error.message : String(error);
2590
- const e = new Error(
2591
- `Failed to load config from ${configPath}: ${errorMessage}`
2592
- );
2593
- try {
2594
- e.cause = error instanceof Error ? error : void 0;
2595
- } catch {
2596
- }
2597
- throw e;
2764
+ e.cause = error instanceof Error ? error : void 0;
2765
+ } catch {
2598
2766
  }
2767
+ throw e;
2599
2768
  }
2600
2769
  }
2601
2770
  const parent = dirname4(currentDir);
@@ -3085,6 +3254,24 @@ function collectFutureProofRecommendations(params) {
3085
3254
  }
3086
3255
  return recommendations;
3087
3256
  }
3257
+ function collectBaseFutureProofRecommendations(params) {
3258
+ const recommendations = [];
3259
+ for (const rec of params.patternEntropy.recommendations) {
3260
+ recommendations.push({
3261
+ action: rec,
3262
+ estimatedImpact: 5,
3263
+ priority: "medium"
3264
+ });
3265
+ }
3266
+ if (params.conceptCohesion.rating === "poor") {
3267
+ recommendations.push({
3268
+ action: "Improve concept cohesion by grouping related exports",
3269
+ estimatedImpact: 8,
3270
+ priority: "high"
3271
+ });
3272
+ }
3273
+ return recommendations;
3274
+ }
3088
3275
 
3089
3276
  // src/metrics/cognitive-load.ts
3090
3277
  function calculateCognitiveLoad(params) {
@@ -3775,21 +3962,10 @@ function calculateFutureProofScore(params) {
3775
3962
  description: params.conceptCohesion.rating
3776
3963
  }
3777
3964
  ];
3778
- const recommendations = [];
3779
- for (const rec of params.patternEntropy.recommendations) {
3780
- recommendations.push({
3781
- action: rec,
3782
- estimatedImpact: 5,
3783
- priority: "medium"
3784
- });
3785
- }
3786
- if (params.conceptCohesion.rating === "poor") {
3787
- recommendations.push({
3788
- action: "Improve concept cohesion by grouping related exports",
3789
- estimatedImpact: 8,
3790
- priority: "high"
3791
- });
3792
- }
3965
+ const recommendations = collectBaseFutureProofRecommendations({
3966
+ patternEntropy: params.patternEntropy,
3967
+ conceptCohesion: params.conceptCohesion
3968
+ });
3793
3969
  const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
3794
3970
  return {
3795
3971
  toolName: "future-proof",
@@ -4121,6 +4297,8 @@ export {
4121
4297
  TypeScriptParser,
4122
4298
  UnifiedReportSchema,
4123
4299
  VAGUE_FILE_NAMES,
4300
+ buildSimpleProviderScore,
4301
+ buildSpokeOutput,
4124
4302
  calculateAgentGrounding,
4125
4303
  calculateAiSignalClarity,
4126
4304
  calculateBusinessROI,
@@ -4144,6 +4322,7 @@ export {
4144
4322
  calculateTestabilityIndex,
4145
4323
  calculateTokenBudget,
4146
4324
  clearHistory,
4325
+ createProvider,
4147
4326
  emitAnnotation,
4148
4327
  emitIssuesAsAnnotations,
4149
4328
  emitProgress,
@@ -4152,6 +4331,8 @@ export {
4152
4331
  exportHistory,
4153
4332
  extractFunctions,
4154
4333
  extractImports,
4334
+ findLatestReport,
4335
+ findLatestScanReport,
4155
4336
  formatAcceptanceRate,
4156
4337
  formatCost,
4157
4338
  formatHours,
@@ -4178,6 +4359,7 @@ export {
4178
4359
  getSupportedLanguages,
4179
4360
  getToolWeight,
4180
4361
  getWasmPath,
4362
+ groupIssuesByFile,
4181
4363
  handleCLIError,
4182
4364
  handleJSONOutput,
4183
4365
  initTreeSitter,