@aiready/core 0.23.1 → 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
@@ -439,8 +439,14 @@ function isSourceFile(filePath) {
439
439
  }
440
440
 
441
441
  // src/utils/cli-helpers.ts
442
- import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
443
- 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";
444
450
  function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
445
451
  let outputPath;
446
452
  if (userPath) {
@@ -526,6 +532,111 @@ function getSeverityColor(severity, chalk) {
526
532
  return chalk.white;
527
533
  }
528
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
+ }
529
640
 
530
641
  // src/utils/ast-parser.ts
531
642
  import { parse as parse2 } from "@typescript-eslint/typescript-estree";
@@ -653,8 +764,10 @@ var TypeScriptParser = class {
653
764
  // camelCase for variables and functions
654
765
  variablePattern: /^[a-z][a-zA-Z0-9]*$/,
655
766
  functionPattern: /^[a-z][a-zA-Z0-9]*$/,
656
- // PascalCase for classes
767
+ // PascalCase for classes, types and interfaces
657
768
  classPattern: /^[A-Z][a-zA-Z0-9]*$/,
769
+ typePattern: /^[A-Z][a-zA-Z0-9]*$/,
770
+ interfacePattern: /^[A-Z][a-zA-Z0-9]*$/,
658
771
  // UPPER_CASE for constants
659
772
  constantPattern: /^[A-Z][A-Z0-9_]*$/,
660
773
  // Common exceptions (React hooks, etc.)
@@ -953,6 +1066,101 @@ async function setupParser(language) {
953
1066
  }
954
1067
  }
955
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
+
956
1164
  // src/parsers/python-parser.ts
957
1165
  var PythonParser = class {
958
1166
  constructor() {
@@ -975,38 +1183,9 @@ var PythonParser = class {
975
1183
  return this.parser.parse(code);
976
1184
  }
977
1185
  analyzeMetadata(node, code) {
978
- const metadata = {
979
- isPure: true,
980
- hasSideEffects: false
981
- };
982
- const body = node.childForFieldName("body");
983
- if (body && body.children.length > 0) {
984
- const firstStmt = body.children[0];
985
- if (firstStmt.type === "expression_statement" && firstStmt.firstChild?.type === "string") {
986
- metadata.documentation = {
987
- content: firstStmt.firstChild.text.replace(/['"`]/g, "").trim(),
988
- type: "docstring"
989
- };
990
- }
991
- }
992
- const walk = (n) => {
993
- if (n.type === "global_statement" || n.type === "nonlocal_statement") {
994
- metadata.isPure = false;
995
- metadata.hasSideEffects = true;
996
- }
997
- if (n.type === "call") {
998
- const functionNode = n.childForFieldName("function");
999
- if (functionNode && ["print", "input", "open"].includes(functionNode.text)) {
1000
- metadata.isPure = false;
1001
- metadata.hasSideEffects = true;
1002
- }
1003
- }
1004
- for (const child of n.children) {
1005
- walk(child);
1006
- }
1007
- };
1008
- if (body) walk(body);
1009
- return metadata;
1186
+ return analyzeNodeMetadata(node, code, {
1187
+ sideEffectSignatures: ["print(", "input(", "open("]
1188
+ });
1010
1189
  }
1011
1190
  parse(code, filePath) {
1012
1191
  if (!this.initialized || !this.parser) {
@@ -1355,6 +1534,113 @@ var PythonParser = class {
1355
1534
  }
1356
1535
  };
1357
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
+
1358
1644
  // src/parsers/java-parser.ts
1359
1645
  var JavaParser = class {
1360
1646
  constructor() {
@@ -1377,47 +1663,14 @@ var JavaParser = class {
1377
1663
  return this.parser.parse(code);
1378
1664
  }
1379
1665
  analyzeMetadata(node, code) {
1380
- const metadata = {
1381
- isPure: true,
1382
- hasSideEffects: false
1383
- };
1384
- let prev = node.previousSibling;
1385
- while (prev && (prev.type === "comment" || prev.type === "line_comment")) {
1386
- if (prev.text.startsWith("/**")) {
1387
- metadata.documentation = {
1388
- content: prev.text.replace(/[/*]/g, "").trim(),
1389
- type: "xml-doc"
1390
- // Using xml-doc as a catch-all for structured or we can add 'javadoc'
1391
- };
1392
- break;
1393
- }
1394
- prev = prev.previousSibling;
1395
- }
1396
- const walk = (n) => {
1397
- if (n.type === "assignment_expression") {
1398
- metadata.isPure = false;
1399
- metadata.hasSideEffects = true;
1400
- }
1401
- if (n.type === "method_invocation") {
1402
- const text = n.text;
1403
- if (text.includes("System.out.print") || text.includes("System.err.print") || text.includes("Files.write")) {
1404
- metadata.isPure = false;
1405
- metadata.hasSideEffects = true;
1406
- }
1407
- }
1408
- if (n.type === "throw_statement") {
1409
- metadata.isPure = false;
1410
- metadata.hasSideEffects = true;
1411
- }
1412
- for (const child of n.children) {
1413
- walk(child);
1414
- }
1415
- };
1416
- const body = node.children.find(
1417
- (c) => c.type === "block" || c.type === "class_body"
1418
- );
1419
- if (body) walk(body);
1420
- return metadata;
1666
+ return analyzeGeneralMetadata(node, code, {
1667
+ sideEffectSignatures: [
1668
+ "System.out",
1669
+ "System.err",
1670
+ "Files.write",
1671
+ "Logging."
1672
+ ]
1673
+ });
1421
1674
  }
1422
1675
  parse(code, filePath) {
1423
1676
  if (!this.initialized || !this.parser) {
@@ -1517,7 +1770,7 @@ var JavaParser = class {
1517
1770
  const imports = [];
1518
1771
  for (const node of rootNode.children) {
1519
1772
  if (node.type === "import_declaration") {
1520
- let sourceArr = [];
1773
+ const sourceArr = [];
1521
1774
  let isStatic = false;
1522
1775
  let isWildcard = false;
1523
1776
  for (const child of node.children) {
@@ -1615,14 +1868,7 @@ var JavaParser = class {
1615
1868
  }
1616
1869
  }
1617
1870
  extractParameters(node) {
1618
- const paramsNode = node.children.find(
1619
- (c) => c.type === "formal_parameters"
1620
- );
1621
- if (!paramsNode) return [];
1622
- return paramsNode.children.filter((c) => c.type === "formal_parameter").map((c) => {
1623
- const idNode = c.children.find((child) => child.type === "identifier");
1624
- return idNode ? idNode.text : "unknown";
1625
- });
1871
+ return extractParameterNames(node);
1626
1872
  }
1627
1873
  getNamingConventions() {
1628
1874
  return {
@@ -1660,47 +1906,9 @@ var CSharpParser = class {
1660
1906
  return this.parser.parse(code);
1661
1907
  }
1662
1908
  analyzeMetadata(node, code) {
1663
- const metadata = {
1664
- isPure: true,
1665
- hasSideEffects: false
1666
- };
1667
- let prev = node.previousSibling;
1668
- while (prev && (prev.type === "comment" || prev.type === "triple_slash_comment")) {
1669
- if (prev.text.trim().startsWith("///") || prev.type === "triple_slash_comment") {
1670
- metadata.documentation = {
1671
- content: prev.text.replace("///", "").trim(),
1672
- type: "xml-doc"
1673
- };
1674
- break;
1675
- }
1676
- prev = prev.previousSibling;
1677
- }
1678
- const walk = (n) => {
1679
- if (n.type === "assignment_expression") {
1680
- metadata.isPure = false;
1681
- metadata.hasSideEffects = true;
1682
- }
1683
- if (n.type === "invocation_expression") {
1684
- const text = n.text;
1685
- if (text.includes("Console.Write") || text.includes("File.Write") || text.includes("Log.")) {
1686
- metadata.isPure = false;
1687
- metadata.hasSideEffects = true;
1688
- }
1689
- }
1690
- if (n.type === "throw_statement") {
1691
- metadata.isPure = false;
1692
- metadata.hasSideEffects = true;
1693
- }
1694
- for (let i = 0; i < n.childCount; i++) {
1695
- const child = n.child(i);
1696
- if (child) walk(child);
1697
- }
1698
- };
1699
- const body = node.children.find(
1700
- (c) => c.type === "block" || c.type === "declaration_list"
1701
- );
1702
- if (body) walk(body);
1703
- return metadata;
1909
+ return analyzeGeneralMetadata(node, code, {
1910
+ sideEffectSignatures: ["Console.Write", "File.Write", "Logging."]
1911
+ });
1704
1912
  }
1705
1913
  parse(code, filePath) {
1706
1914
  if (!this.initialized || !this.parser) {
@@ -1905,19 +2113,7 @@ var CSharpParser = class {
1905
2113
  return modifiers;
1906
2114
  }
1907
2115
  extractParameters(node) {
1908
- const params = [];
1909
- const parameterList = node.childForFieldName("parameters") || node.children.find((c) => c.type === "parameter_list");
1910
- if (parameterList) {
1911
- for (const param of parameterList.children) {
1912
- if (param.type === "parameter") {
1913
- const nameNode = param.childForFieldName("name") || param.children.find((c) => c.type === "identifier");
1914
- if (nameNode) {
1915
- params.push(nameNode.text);
1916
- }
1917
- }
1918
- }
1919
- }
1920
- return params;
2116
+ return extractParameterNames(node);
1921
2117
  }
1922
2118
  getNamingConventions() {
1923
2119
  return {
@@ -1954,40 +2150,9 @@ var GoParser = class {
1954
2150
  return this.parser.parse(code);
1955
2151
  }
1956
2152
  analyzeMetadata(node, code) {
1957
- const metadata = {
1958
- isPure: true,
1959
- hasSideEffects: false
1960
- };
1961
- let prev = node.previousSibling;
1962
- while (prev && prev.type === "comment") {
1963
- metadata.documentation = {
1964
- content: prev.text.replace(/\/\/|\/\*|\*\//g, "").trim(),
1965
- type: "comment"
1966
- };
1967
- break;
1968
- }
1969
- const walk = (n) => {
1970
- if (n.type === "send_statement" || n.type === "expression_statement" && n.text.includes("<-")) {
1971
- metadata.isPure = false;
1972
- metadata.hasSideEffects = true;
1973
- }
1974
- if (n.type === "assignment_statement" || n.type === "short_var_declaration") {
1975
- }
1976
- if (n.type === "call_expression") {
1977
- const text = n.text;
1978
- if (text.includes("fmt.Print") || text.includes("os.Exit") || text.includes("panic(") || text.includes("log.")) {
1979
- metadata.isPure = false;
1980
- metadata.hasSideEffects = true;
1981
- }
1982
- }
1983
- for (let i = 0; i < n.childCount; i++) {
1984
- const child = n.child(i);
1985
- if (child) walk(child);
1986
- }
1987
- };
1988
- const body = node.childForFieldName("body");
1989
- if (body) walk(body);
1990
- return metadata;
2153
+ return analyzeGeneralMetadata(node, code, {
2154
+ sideEffectSignatures: ["<-", "fmt.Print", "fmt.Fprintf", "os.Exit"]
2155
+ });
1991
2156
  }
1992
2157
  parse(code, filePath) {
1993
2158
  if (!this.initialized || !this.parser) {
@@ -2209,17 +2374,7 @@ var GoParser = class {
2209
2374
  return exports;
2210
2375
  }
2211
2376
  extractParameters(node) {
2212
- const params = [];
2213
- const parameterList = node.childForFieldName("parameters") || node.children.find((c) => c.type === "parameter_list");
2214
- if (parameterList) {
2215
- for (const param of parameterList.children) {
2216
- if (param.type === "parameter_declaration") {
2217
- const names = param.children.filter((c) => c.type === "identifier");
2218
- names.forEach((n) => params.push(n.text));
2219
- }
2220
- }
2221
- }
2222
- return params;
2377
+ return extractParameterNames(node);
2223
2378
  }
2224
2379
  getNamingConventions() {
2225
2380
  return {
@@ -2569,34 +2724,47 @@ var CONFIG_FILES = [
2569
2724
  async function loadConfig(rootDir) {
2570
2725
  let currentDir = resolve(rootDir);
2571
2726
  while (true) {
2727
+ const foundConfigs = [];
2572
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];
2573
2743
  const configPath = join4(currentDir, configFile);
2574
- 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
+ );
2575
2763
  try {
2576
- let config;
2577
- if (configFile.endsWith(".js")) {
2578
- const fileUrl = pathToFileURL(configPath).href;
2579
- const module = await import(`${fileUrl}?t=${Date.now()}`);
2580
- config = module.default || module;
2581
- } else {
2582
- const content = readFileSync(configPath, "utf-8");
2583
- config = JSON.parse(content);
2584
- }
2585
- if (typeof config !== "object" || config === null) {
2586
- throw new Error("Config must be an object");
2587
- }
2588
- return config;
2589
- } catch (error) {
2590
- const errorMessage = error instanceof Error ? error.message : String(error);
2591
- const e = new Error(
2592
- `Failed to load config from ${configPath}: ${errorMessage}`
2593
- );
2594
- try {
2595
- e.cause = error instanceof Error ? error : void 0;
2596
- } catch {
2597
- }
2598
- throw e;
2764
+ e.cause = error instanceof Error ? error : void 0;
2765
+ } catch {
2599
2766
  }
2767
+ throw e;
2600
2768
  }
2601
2769
  }
2602
2770
  const parent = dirname4(currentDir);
@@ -3086,6 +3254,24 @@ function collectFutureProofRecommendations(params) {
3086
3254
  }
3087
3255
  return recommendations;
3088
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
+ }
3089
3275
 
3090
3276
  // src/metrics/cognitive-load.ts
3091
3277
  function calculateCognitiveLoad(params) {
@@ -3776,21 +3962,10 @@ function calculateFutureProofScore(params) {
3776
3962
  description: params.conceptCohesion.rating
3777
3963
  }
3778
3964
  ];
3779
- const recommendations = [];
3780
- for (const rec of params.patternEntropy.recommendations) {
3781
- recommendations.push({
3782
- action: rec,
3783
- estimatedImpact: 5,
3784
- priority: "medium"
3785
- });
3786
- }
3787
- if (params.conceptCohesion.rating === "poor") {
3788
- recommendations.push({
3789
- action: "Improve concept cohesion by grouping related exports",
3790
- estimatedImpact: 8,
3791
- priority: "high"
3792
- });
3793
- }
3965
+ const recommendations = collectBaseFutureProofRecommendations({
3966
+ patternEntropy: params.patternEntropy,
3967
+ conceptCohesion: params.conceptCohesion
3968
+ });
3794
3969
  const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
3795
3970
  return {
3796
3971
  toolName: "future-proof",
@@ -4122,6 +4297,8 @@ export {
4122
4297
  TypeScriptParser,
4123
4298
  UnifiedReportSchema,
4124
4299
  VAGUE_FILE_NAMES,
4300
+ buildSimpleProviderScore,
4301
+ buildSpokeOutput,
4125
4302
  calculateAgentGrounding,
4126
4303
  calculateAiSignalClarity,
4127
4304
  calculateBusinessROI,
@@ -4145,6 +4322,7 @@ export {
4145
4322
  calculateTestabilityIndex,
4146
4323
  calculateTokenBudget,
4147
4324
  clearHistory,
4325
+ createProvider,
4148
4326
  emitAnnotation,
4149
4327
  emitIssuesAsAnnotations,
4150
4328
  emitProgress,
@@ -4153,6 +4331,8 @@ export {
4153
4331
  exportHistory,
4154
4332
  extractFunctions,
4155
4333
  extractImports,
4334
+ findLatestReport,
4335
+ findLatestScanReport,
4156
4336
  formatAcceptanceRate,
4157
4337
  formatCost,
4158
4338
  formatHours,
@@ -4179,6 +4359,7 @@ export {
4179
4359
  getSupportedLanguages,
4180
4360
  getToolWeight,
4181
4361
  getWasmPath,
4362
+ groupIssuesByFile,
4182
4363
  handleCLIError,
4183
4364
  handleJSONOutput,
4184
4365
  initTreeSitter,