@aiready/core 0.23.6 → 0.23.8

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
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AIReadyConfigSchema,
2
3
  AnalysisResultSchema,
3
4
  AnalysisStatus,
4
5
  AnalysisStatusSchema,
@@ -49,53 +50,102 @@ import {
49
50
  getToolWeight,
50
51
  normalizeToolName,
51
52
  parseWeightString
52
- } from "./chunk-Q55AMEFV.mjs";
53
+ } from "./chunk-TJXR2CHZ.mjs";
54
+
55
+ // src/utils/normalization.ts
56
+ function normalizeIssue(raw) {
57
+ return {
58
+ type: raw.type ?? "pattern-inconsistency" /* PatternInconsistency */,
59
+ severity: raw.severity ?? raw.severityLevel ?? "info" /* Info */,
60
+ message: raw.message ?? "Unknown issue",
61
+ location: raw.location ?? {
62
+ file: raw.fileName ?? raw.file ?? raw.filePath ?? "unknown",
63
+ line: raw.line ?? 1,
64
+ column: raw.column
65
+ },
66
+ suggestion: raw.suggestion
67
+ };
68
+ }
69
+ function normalizeMetrics(raw) {
70
+ return {
71
+ tokenCost: raw.tokenCost ?? 0,
72
+ complexityScore: raw.complexityScore ?? 0,
73
+ consistencyScore: raw.consistencyScore,
74
+ docFreshnessScore: raw.docFreshnessScore,
75
+ aiSignalClarityScore: raw.aiSignalClarityScore,
76
+ agentGroundingScore: raw.agentGroundingScore,
77
+ testabilityScore: raw.testabilityScore,
78
+ docDriftScore: raw.docDriftScore,
79
+ dependencyHealthScore: raw.dependencyHealthScore,
80
+ modelContextTier: raw.modelContextTier,
81
+ estimatedMonthlyCost: raw.estimatedMonthlyCost,
82
+ estimatedDeveloperHours: raw.estimatedDeveloperHours,
83
+ comprehensionDifficultyIndex: raw.comprehensionDifficultyIndex,
84
+ totalSymbols: raw.totalSymbols,
85
+ totalExports: raw.totalExports
86
+ };
87
+ }
88
+ function normalizeAnalysisResult(raw) {
89
+ const fileName = raw.fileName ?? raw.file ?? raw.filePath ?? "unknown";
90
+ const rawIssues = Array.isArray(raw.issues) ? raw.issues : [];
91
+ return {
92
+ fileName,
93
+ issues: rawIssues.map((issue) => {
94
+ if (typeof issue === "string") {
95
+ return {
96
+ type: "pattern-inconsistency" /* PatternInconsistency */,
97
+ // Default fallback
98
+ severity: raw.severity ?? "info" /* Info */,
99
+ message: issue,
100
+ location: { file: fileName, line: 1 }
101
+ };
102
+ }
103
+ return normalizeIssue({
104
+ ...issue,
105
+ fileName: issue.fileName ?? fileName,
106
+ severity: issue.severity ?? raw.severity
107
+ });
108
+ }),
109
+ metrics: normalizeMetrics(raw.metrics ?? {})
110
+ };
111
+ }
112
+ function normalizeSpokeOutput(raw, toolName) {
113
+ const rawResults = Array.isArray(raw.results) ? raw.results : [];
114
+ return {
115
+ results: rawResults.map(normalizeAnalysisResult),
116
+ summary: raw.summary ?? {
117
+ totalFiles: rawResults.length,
118
+ totalIssues: 0,
119
+ criticalIssues: 0,
120
+ majorIssues: 0
121
+ },
122
+ metadata: {
123
+ toolName: raw.metadata?.toolName ?? toolName,
124
+ version: raw.metadata?.version,
125
+ timestamp: raw.metadata?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
126
+ config: raw.metadata?.config
127
+ }
128
+ };
129
+ }
53
130
 
54
131
  // src/types/contract.ts
55
132
  function validateSpokeOutput(toolName, output) {
56
- const errors = [];
57
133
  if (!output) {
58
134
  return { valid: false, errors: ["Output is null or undefined"] };
59
135
  }
60
- if (!Array.isArray(output.results)) {
61
- errors.push(`${toolName}: 'results' must be an array`);
62
- } else {
63
- output.results.forEach((res, idx) => {
64
- const fileName = res.fileName || res.file || res.filePath;
65
- if (!fileName)
66
- errors.push(
67
- `${toolName}: results[${idx}] missing 'fileName', 'file' or 'filePath'`
68
- );
69
- const issues = res.issues;
70
- if (!Array.isArray(issues)) {
71
- errors.push(`${toolName}: results[${idx}] 'issues' must be an array`);
72
- } else if (issues.length > 0) {
73
- issues.forEach((issue, iidx) => {
74
- if (typeof issue === "string") return;
75
- if (!issue.type && !res.file)
76
- errors.push(
77
- `${toolName}: results[${idx}].issues[${iidx}] missing 'type'`
78
- );
79
- if (!issue.severity && !res.severity)
80
- errors.push(
81
- `${toolName}: results[${idx}].issues[${iidx}] missing 'severity'`
82
- );
83
- const severity = issue.severity || res.severity;
84
- if (severity && !["critical", "major", "minor", "info"].includes(severity)) {
85
- errors.push(
86
- `${toolName}: results[${idx}].issues[${iidx}] has invalid severity: ${severity}`
87
- );
88
- }
89
- });
90
- }
91
- });
92
- }
93
136
  if (!output.summary) {
94
- errors.push(`${toolName}: missing 'summary'`);
137
+ return { valid: false, errors: [`${toolName}: missing 'summary'`] };
138
+ }
139
+ const normalized = normalizeSpokeOutput(output, toolName);
140
+ const result = SpokeOutputSchema.safeParse(normalized);
141
+ if (result.success) {
142
+ return { valid: true, errors: [] };
95
143
  }
96
144
  return {
97
- valid: errors.length === 0,
98
- errors
145
+ valid: false,
146
+ errors: result.error.issues.map(
147
+ (e) => `${toolName}: ${e.path.join(".")}: ${e.message}`
148
+ )
99
149
  };
100
150
  }
101
151
  function validateWithSchema(schema, data) {
@@ -464,7 +514,7 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
464
514
  if (statSync(workingDir).isFile()) {
465
515
  baseDir = dirname2(workingDir);
466
516
  }
467
- } catch (e) {
517
+ } catch {
468
518
  }
469
519
  const aireadyDir = join2(baseDir, ".aiready");
470
520
  outputPath = join2(aireadyDir, defaultFilename);
@@ -636,8 +686,8 @@ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
636
686
  return idB - idA;
637
687
  });
638
688
  return join2(scanReportsDir, reportFiles[0]);
639
- } catch (e) {
640
- console.error("Error while finding latest scan report:", e);
689
+ } catch {
690
+ console.error("Error while finding latest scan report");
641
691
  return null;
642
692
  }
643
693
  }
@@ -702,7 +752,19 @@ function createProvider(config) {
702
752
  };
703
753
  }
704
754
 
705
- // src/utils/ast-parser.ts
755
+ // src/utils/similarity-utils.ts
756
+ function calculateImportSimilarity(export1, export2) {
757
+ if (export1.imports.length === 0 && export2.imports.length === 0) {
758
+ return 1;
759
+ }
760
+ const set1 = new Set(export1.imports);
761
+ const set2 = new Set(export2.imports);
762
+ const intersection = new Set([...set1].filter((x) => set2.has(x)));
763
+ const union = /* @__PURE__ */ new Set([...set1, ...set2]);
764
+ return intersection.size / union.size;
765
+ }
766
+
767
+ // src/utils/dependency-analyzer.ts
706
768
  import { parse as parse2 } from "@typescript-eslint/typescript-estree";
707
769
 
708
770
  // src/parsers/typescript-parser.ts
@@ -713,148 +775,67 @@ var TypeScriptParser = class {
713
775
  this.extensions = [".ts", ".tsx", ".js", ".jsx"];
714
776
  }
715
777
  async initialize() {
716
- return Promise.resolve();
717
778
  }
718
- async getAST(code, filePath) {
719
- return parse(code, {
720
- loc: true,
721
- range: true,
722
- jsx: filePath.match(/\.[jt]sx$/i) !== null,
723
- filePath,
724
- sourceType: "module",
725
- ecmaVersion: "latest",
726
- comment: true
727
- });
779
+ canHandle(filePath) {
780
+ return this.extensions.some((ext) => filePath.endsWith(ext));
728
781
  }
729
- analyzeMetadata(node, code) {
730
- const metadata = {
731
- isPure: true,
732
- hasSideEffects: false
733
- };
734
- const start = node.range?.[0] ?? 0;
735
- const preceding = code.slice(Math.max(0, start - 1e3), start);
736
- const jsdocMatches = Array.from(
737
- preceding.matchAll(/\/\*\*([\s\S]*?)\*\//g)
738
- );
739
- if (jsdocMatches.length > 0) {
740
- const lastMatch = jsdocMatches[jsdocMatches.length - 1];
741
- const matchEndIndex = (lastMatch.index || 0) + lastMatch[0].length;
742
- const between = preceding.slice(matchEndIndex);
743
- if (/^\s*$/.test(between)) {
744
- const precedingStartOffset = Math.max(0, start - 1e3);
745
- const absoluteStartOffset = precedingStartOffset + (lastMatch.index || 0);
746
- const absoluteEndOffset = precedingStartOffset + matchEndIndex;
747
- const codeBeforeStart = code.slice(0, absoluteStartOffset);
748
- const startLines = codeBeforeStart.split("\n");
749
- const startLine = startLines.length;
750
- const startColumn = startLines[startLines.length - 1].length;
751
- const codeBeforeEnd = code.slice(0, absoluteEndOffset);
752
- const endLines = codeBeforeEnd.split("\n");
753
- const endLine = endLines.length;
754
- const endColumn = endLines[endLines.length - 1].length;
755
- metadata.documentation = {
756
- content: lastMatch[1].replace(/^\s*\*+/gm, "").trim(),
757
- type: "jsdoc",
758
- loc: {
759
- start: { line: startLine, column: startColumn },
760
- end: { line: endLine, column: endColumn }
761
- }
762
- };
763
- }
764
- }
765
- const walk = (n) => {
766
- if (!n) return;
767
- if (n.type === "AssignmentExpression") {
768
- metadata.isPure = false;
769
- metadata.hasSideEffects = true;
770
- }
771
- if (n.type === "UpdateExpression") {
772
- metadata.isPure = false;
773
- metadata.hasSideEffects = true;
774
- }
775
- if (n.type === "CallExpression" && n.callee.type === "MemberExpression" && n.callee.object.type === "Identifier") {
776
- const objName = n.callee.object.name;
777
- if (["console", "process", "fs", "window", "document"].includes(objName)) {
778
- metadata.isPure = false;
779
- metadata.hasSideEffects = true;
780
- }
781
- }
782
- if (n.type === "ThrowStatement") {
783
- metadata.isPure = false;
784
- metadata.hasSideEffects = true;
785
- }
786
- for (const key of Object.keys(n)) {
787
- if (key === "parent") continue;
788
- const child = n[key];
789
- if (child && typeof child === "object") {
790
- if (Array.isArray(child)) {
791
- child.forEach((c) => c?.type && walk(c));
792
- } else if (child.type) {
793
- walk(child);
794
- }
795
- }
796
- }
797
- };
798
- let nodeToAnalyze = node;
799
- if (node.type === "ExportNamedDeclaration" && node.declaration) {
800
- nodeToAnalyze = node.declaration;
801
- } else if (node.type === "ExportDefaultDeclaration" && node.declaration) {
802
- if (node.declaration.type !== "TSInterfaceDeclaration" && node.declaration.type !== "TSTypeAliasDeclaration") {
803
- nodeToAnalyze = node.declaration;
804
- }
805
- }
806
- if (nodeToAnalyze.type === "FunctionDeclaration" || nodeToAnalyze.type === "FunctionExpression" || nodeToAnalyze.type === "ArrowFunctionExpression") {
807
- if (nodeToAnalyze.body) walk(nodeToAnalyze.body);
808
- } else if (nodeToAnalyze.type === "ClassDeclaration" || nodeToAnalyze.type === "ClassExpression") {
809
- walk(nodeToAnalyze.body);
782
+ async getAST(code, filePath) {
783
+ try {
784
+ return parse(code, {
785
+ filePath,
786
+ loc: true,
787
+ range: true,
788
+ tokens: true,
789
+ comment: true,
790
+ jsx: filePath.endsWith("x")
791
+ });
792
+ } catch (error) {
793
+ throw new ParseError(error.message, filePath, {
794
+ line: error.lineNumber || 1,
795
+ column: error.column || 0
796
+ });
810
797
  }
811
- return metadata;
812
798
  }
813
799
  parse(code, filePath) {
814
800
  try {
815
- const isJavaScript = filePath.match(/\.jsx?$/i);
816
801
  const ast = parse(code, {
802
+ filePath,
817
803
  loc: true,
818
804
  range: true,
819
- jsx: filePath.match(/\.[jt]sx$/i) !== null,
820
- filePath,
821
- sourceType: "module",
822
- ecmaVersion: "latest",
823
- comment: true
805
+ tokens: true,
806
+ comment: true,
807
+ jsx: filePath.endsWith("x")
824
808
  });
825
809
  const imports = this.extractImports(ast);
826
- const exports = this.extractExports(ast, imports, code);
810
+ const exports = this.extractExports(ast, code);
827
811
  return {
828
812
  exports,
829
813
  imports,
830
- language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
831
- warnings: []
814
+ language: this.language
832
815
  };
833
816
  } catch (error) {
834
- const err = error;
835
- throw new ParseError(
836
- `Failed to parse ${filePath}: ${err.message}`,
837
- filePath
838
- );
817
+ throw new ParseError(error.message, filePath, {
818
+ line: error.lineNumber || 1,
819
+ column: error.column || 0
820
+ });
839
821
  }
840
822
  }
841
823
  getNamingConventions() {
842
824
  return {
843
- // camelCase for variables and functions
844
825
  variablePattern: /^[a-z][a-zA-Z0-9]*$/,
845
826
  functionPattern: /^[a-z][a-zA-Z0-9]*$/,
846
- // PascalCase for classes, types and interfaces
847
827
  classPattern: /^[A-Z][a-zA-Z0-9]*$/,
848
- typePattern: /^[A-Z][a-zA-Z0-9]*$/,
849
- interfacePattern: /^[A-Z][a-zA-Z0-9]*$/,
850
- // UPPER_CASE for constants
851
828
  constantPattern: /^[A-Z][A-Z0-9_]*$/,
852
- // Common exceptions (React hooks, etc.)
853
- exceptions: ["__filename", "__dirname", "__esModule"]
829
+ typePattern: /^[A-Z][a-zA-Z0-9]*$/,
830
+ interfacePattern: /^I?[A-Z][a-zA-Z0-9]*$/
854
831
  };
855
832
  }
856
- canHandle(filePath) {
857
- return this.extensions.some((ext) => filePath.toLowerCase().endsWith(ext));
833
+ analyzeMetadata(node, code) {
834
+ if (!code) return {};
835
+ return {
836
+ isPure: this.isLikelyPure(node),
837
+ hasSideEffects: !this.isLikelyPure(node)
838
+ };
858
839
  }
859
840
  extractImports(ast) {
860
841
  const imports = [];
@@ -892,168 +873,165 @@ var TypeScriptParser = class {
892
873
  }
893
874
  return imports;
894
875
  }
895
- extractExports(ast, imports, code) {
876
+ extractExports(ast, code) {
896
877
  const exports = [];
897
- const importedNames = new Set(
898
- imports.flatMap(
899
- (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
900
- )
901
- );
902
878
  for (const node of ast.body) {
903
- if (node.type === "ExportNamedDeclaration" && node.declaration) {
904
- const extracted = this.extractFromDeclaration(
905
- node.declaration,
906
- importedNames,
907
- code,
908
- node
909
- // Pass the ExportNamedDeclaration as parent for metadata
910
- );
911
- exports.push(...extracted);
912
- } else if (node.type === "ExportDefaultDeclaration") {
913
- const metadata = this.analyzeMetadata(node, code);
914
- let name = "default";
915
- let type = "default";
916
- if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
917
- name = node.declaration.id.name;
918
- type = "function";
919
- } else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
920
- name = node.declaration.id.name;
921
- type = "class";
879
+ if (node.type === "ExportNamedDeclaration") {
880
+ if (node.declaration) {
881
+ const declaration = node.declaration;
882
+ if ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") && declaration.id) {
883
+ exports.push(
884
+ this.createExport(
885
+ declaration.id.name,
886
+ "function",
887
+ node,
888
+ // Pass the outer ExportNamedDeclaration
889
+ code
890
+ )
891
+ );
892
+ } else if (declaration.type === "ClassDeclaration" && declaration.id) {
893
+ exports.push(
894
+ this.createExport(
895
+ declaration.id.name,
896
+ "class",
897
+ node,
898
+ // Pass the outer ExportNamedDeclaration
899
+ code
900
+ )
901
+ );
902
+ } else if (declaration.type === "TSTypeAliasDeclaration") {
903
+ exports.push(
904
+ this.createExport(
905
+ declaration.id.name,
906
+ "type",
907
+ node,
908
+ // Pass the outer ExportNamedDeclaration
909
+ code
910
+ )
911
+ );
912
+ } else if (declaration.type === "TSInterfaceDeclaration") {
913
+ exports.push(
914
+ this.createExport(
915
+ declaration.id.name,
916
+ "interface",
917
+ node,
918
+ // Pass the outer ExportNamedDeclaration
919
+ code
920
+ )
921
+ );
922
+ } else if (declaration.type === "VariableDeclaration") {
923
+ for (const decl of declaration.declarations) {
924
+ if (decl.id.type === "Identifier") {
925
+ exports.push(
926
+ this.createExport(
927
+ decl.id.name,
928
+ "const",
929
+ node,
930
+ // Pass the outer ExportNamedDeclaration
931
+ code,
932
+ decl.init
933
+ )
934
+ );
935
+ }
936
+ }
937
+ }
922
938
  }
923
- exports.push({
924
- name,
925
- type,
926
- loc: node.loc ? {
927
- start: {
928
- line: node.loc.start.line,
929
- column: node.loc.start.column
930
- },
931
- end: { line: node.loc.end.line, column: node.loc.end.column }
932
- } : void 0,
933
- ...metadata
934
- });
939
+ } else if (node.type === "ExportDefaultDeclaration") {
940
+ exports.push(this.createExport("default", "default", node, code));
935
941
  }
936
942
  }
937
943
  return exports;
938
944
  }
939
- extractFromDeclaration(declaration, importedNames, code, parentNode) {
940
- const exports = [];
941
- const metadata = this.analyzeMetadata(parentNode || declaration, code);
942
- if ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") && declaration.id) {
943
- exports.push({
944
- name: declaration.id.name,
945
- type: "function",
946
- parameters: declaration.params.map((p) => {
945
+ createExport(name, type, node, code, initializer) {
946
+ const documentation = this.extractDocumentation(node, code);
947
+ let methodCount;
948
+ let propertyCount;
949
+ let parameters;
950
+ let isPrimitive = false;
951
+ if (initializer) {
952
+ if (initializer.type === "Literal" || initializer.type === "TemplateLiteral" && initializer.expressions.length === 0) {
953
+ isPrimitive = true;
954
+ }
955
+ }
956
+ const structNode = node.type === "ExportNamedDeclaration" ? node.declaration : node.type === "ExportDefaultDeclaration" ? node.declaration : node;
957
+ if (structNode.type === "ClassDeclaration" || structNode.type === "TSInterfaceDeclaration") {
958
+ const body = structNode.type === "ClassDeclaration" ? structNode.body.body : structNode.body.body;
959
+ methodCount = body.filter(
960
+ (m) => m.type === "MethodDefinition" || m.type === "TSMethodSignature"
961
+ ).length;
962
+ propertyCount = body.filter(
963
+ (m) => m.type === "PropertyDefinition" || m.type === "TSPropertySignature"
964
+ ).length;
965
+ if (structNode.type === "ClassDeclaration") {
966
+ const constructor = body.find(
967
+ (m) => m.type === "MethodDefinition" && m.kind === "constructor"
968
+ );
969
+ if (constructor && constructor.value && constructor.value.params) {
970
+ parameters = constructor.value.params.map((p) => {
971
+ if (p.type === "Identifier") return p.name;
972
+ if (p.type === "TSParameterProperty" && p.parameter.type === "Identifier") {
973
+ return p.parameter.name;
974
+ }
975
+ return void 0;
976
+ }).filter(Boolean);
977
+ }
978
+ }
979
+ }
980
+ if (!parameters && (structNode.type === "FunctionDeclaration" || structNode.type === "TSDeclareFunction" || structNode.type === "MethodDefinition")) {
981
+ const funcNode = structNode.type === "MethodDefinition" ? structNode.value : structNode;
982
+ if (funcNode && funcNode.params) {
983
+ parameters = funcNode.params.map((p) => {
947
984
  if (p.type === "Identifier") return p.name;
948
- if (p.type === "AssignmentPattern" && p.left.type === "Identifier")
949
- return p.left.name;
950
- if (p.type === "RestElement" && p.argument.type === "Identifier")
951
- return p.argument.name;
952
- return "unknown";
953
- }),
954
- loc: declaration.loc ? {
955
- start: {
956
- line: declaration.loc.start.line,
957
- column: declaration.loc.start.column
958
- },
959
- end: {
960
- line: declaration.loc.end.line,
961
- column: declaration.loc.end.column
962
- }
963
- } : void 0,
964
- ...metadata
965
- });
966
- } else if (declaration.type === "ClassDeclaration" && declaration.id) {
967
- const body = declaration.body.body;
968
- const methods = body.filter((m) => m.type === "MethodDefinition");
969
- const properties = body.filter((m) => m.type === "PropertyDefinition");
970
- const constructor = methods.find(
971
- (m) => m.kind === "constructor"
972
- );
973
- const parameters = constructor ? constructor.value.params.map((p) => {
974
- if (p.type === "Identifier") return p.name;
975
- if (p.type === "TSParameterProperty" && p.parameter.type === "Identifier")
976
- return p.parameter.name;
977
- return "unknown";
978
- }) : [];
979
- exports.push({
980
- name: declaration.id.name,
981
- type: "class",
982
- methodCount: methods.length,
983
- propertyCount: properties.length,
984
- parameters,
985
- loc: declaration.loc ? {
986
- start: {
987
- line: declaration.loc.start.line,
988
- column: declaration.loc.start.column
989
- },
990
- end: {
991
- line: declaration.loc.end.line,
992
- column: declaration.loc.end.column
993
- }
994
- } : void 0,
995
- ...metadata
996
- });
997
- } else if (declaration.type === "VariableDeclaration") {
998
- for (const declarator of declaration.declarations) {
999
- if (declarator.id.type === "Identifier") {
1000
- exports.push({
1001
- name: declarator.id.name,
1002
- type: "const",
1003
- loc: declarator.loc ? {
1004
- start: {
1005
- line: declarator.loc.start.line,
1006
- column: declarator.loc.start.column
1007
- },
1008
- end: {
1009
- line: declarator.loc.end.line,
1010
- column: declarator.loc.end.column
1011
- }
1012
- } : void 0,
1013
- ...metadata
1014
- });
985
+ return void 0;
986
+ }).filter(Boolean);
987
+ }
988
+ }
989
+ return {
990
+ name,
991
+ type,
992
+ isPrimitive,
993
+ loc: node.loc ? {
994
+ start: { line: node.loc.start.line, column: node.loc.start.column },
995
+ end: { line: node.loc.end.line, column: node.loc.end.column }
996
+ } : void 0,
997
+ documentation,
998
+ methodCount,
999
+ propertyCount,
1000
+ parameters,
1001
+ isPure: this.isLikelyPure(node),
1002
+ hasSideEffects: !this.isLikelyPure(node)
1003
+ };
1004
+ }
1005
+ extractDocumentation(node, code) {
1006
+ if (node.range) {
1007
+ const start = node.range[0];
1008
+ const precedingCode = code.substring(0, start);
1009
+ const jsdocMatch = precedingCode.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
1010
+ if (jsdocMatch) {
1011
+ return {
1012
+ content: jsdocMatch[1].trim(),
1013
+ type: "jsdoc"
1014
+ };
1015
+ }
1016
+ }
1017
+ return void 0;
1018
+ }
1019
+ isLikelyPure(node) {
1020
+ const structNode = node.type === "ExportNamedDeclaration" ? node.declaration : node.type === "ExportDefaultDeclaration" ? node.declaration : node;
1021
+ if (structNode.type === "VariableDeclaration" && structNode.kind === "const")
1022
+ return true;
1023
+ if (structNode.type === "FunctionDeclaration" || structNode.type === "TSDeclareFunction" || structNode.type === "MethodDefinition" && structNode.value) {
1024
+ const body = structNode.type === "MethodDefinition" ? structNode.value.body : structNode.body;
1025
+ if (body && body.type === "BlockStatement") {
1026
+ const bodyContent = JSON.stringify(body);
1027
+ if (bodyContent.includes('"name":"console"') || bodyContent.includes('"name":"process"') || bodyContent.includes('"type":"AssignmentExpression"')) {
1028
+ return false;
1015
1029
  }
1030
+ return true;
1016
1031
  }
1017
- } else if (declaration.type === "TSTypeAliasDeclaration") {
1018
- exports.push({
1019
- name: declaration.id.name,
1020
- type: "type",
1021
- loc: declaration.loc ? {
1022
- start: {
1023
- line: declaration.loc.start.line,
1024
- column: declaration.loc.start.column
1025
- },
1026
- end: {
1027
- line: declaration.loc.end.line,
1028
- column: declaration.loc.end.column
1029
- }
1030
- } : void 0,
1031
- ...metadata
1032
- });
1033
- } else if (declaration.type === "TSInterfaceDeclaration") {
1034
- const body = declaration.body.body;
1035
- const methods = body.filter((m) => m.type === "TSMethodSignature");
1036
- const properties = body.filter((m) => m.type === "TSPropertySignature");
1037
- exports.push({
1038
- name: declaration.id.name,
1039
- type: "interface",
1040
- methodCount: methods.length,
1041
- propertyCount: properties.length || body.length,
1042
- // Fallback to body.length
1043
- loc: declaration.loc ? {
1044
- start: {
1045
- line: declaration.loc.start.line,
1046
- column: declaration.loc.start.column
1047
- },
1048
- end: {
1049
- line: declaration.loc.end.line,
1050
- column: declaration.loc.end.column
1051
- }
1052
- } : void 0,
1053
- ...metadata
1054
- });
1032
+ return true;
1055
1033
  }
1056
- return exports;
1034
+ return false;
1057
1035
  }
1058
1036
  };
1059
1037
 
@@ -1327,11 +1305,24 @@ var PythonParser = class extends BaseLanguageParser {
1327
1305
  getParserName() {
1328
1306
  return "python";
1329
1307
  }
1308
+ /**
1309
+ * Analyze metadata for a Python node (purity, side effects).
1310
+ *
1311
+ * @param node - Tree-sitter node to analyze.
1312
+ * @param code - Source code for context.
1313
+ * @returns Partial ExportInfo containing discovered metadata.
1314
+ */
1330
1315
  analyzeMetadata(node, code) {
1331
1316
  return analyzeNodeMetadata(node, code, {
1332
1317
  sideEffectSignatures: ["print(", "input(", "open("]
1333
1318
  });
1334
1319
  }
1320
+ /**
1321
+ * Extract import information using AST walk.
1322
+ *
1323
+ * @param rootNode - Root node of the Python AST.
1324
+ * @returns Array of discovered FileImport objects.
1325
+ */
1335
1326
  extractImportsAST(rootNode) {
1336
1327
  const imports = [];
1337
1328
  const processImportNode = (node) => {
@@ -1413,6 +1404,13 @@ var PythonParser = class extends BaseLanguageParser {
1413
1404
  }
1414
1405
  return imports;
1415
1406
  }
1407
+ /**
1408
+ * Extract export information using AST walk.
1409
+ *
1410
+ * @param rootNode - Root node of the Python AST.
1411
+ * @param code - Source code for documentation extraction.
1412
+ * @returns Array of discovered ExportInfo objects.
1413
+ */
1416
1414
  extractExportsAST(rootNode, code) {
1417
1415
  const exports = [];
1418
1416
  for (const node of rootNode.children) {
@@ -1491,6 +1489,12 @@ var PythonParser = class extends BaseLanguageParser {
1491
1489
  }
1492
1490
  return exports;
1493
1491
  }
1492
+ /**
1493
+ * Extract parameter names from a function definition node.
1494
+ *
1495
+ * @param node - Function definition node.
1496
+ * @returns Array of parameter name strings.
1497
+ */
1494
1498
  extractParameters(node) {
1495
1499
  const paramsNode = node.childForFieldName("parameters");
1496
1500
  if (!paramsNode) return [];
@@ -1504,6 +1508,13 @@ var PythonParser = class extends BaseLanguageParser {
1504
1508
  return "unknown";
1505
1509
  });
1506
1510
  }
1511
+ /**
1512
+ * Fallback regex-based parsing when tree-sitter is unavailable.
1513
+ *
1514
+ * @param code - Source code content.
1515
+ * @param filePath - Path to the file being parsed.
1516
+ * @returns Consolidated ParseResult.
1517
+ */
1507
1518
  parseRegex(code, filePath) {
1508
1519
  try {
1509
1520
  const imports = this.extractImportsRegex(code, filePath);
@@ -1777,6 +1788,13 @@ var JavaParser = class extends BaseLanguageParser {
1777
1788
  getParserName() {
1778
1789
  return "java";
1779
1790
  }
1791
+ /**
1792
+ * Analyze metadata for a Java node (purity, side effects).
1793
+ *
1794
+ * @param node - Tree-sitter node to analyze.
1795
+ * @param code - Source code for context.
1796
+ * @returns Partial ExportInfo containing discovered metadata.
1797
+ */
1780
1798
  analyzeMetadata(node, code) {
1781
1799
  return analyzeGeneralMetadata(node, code, {
1782
1800
  sideEffectSignatures: [
@@ -1856,6 +1874,12 @@ var JavaParser = class extends BaseLanguageParser {
1856
1874
  warnings: ["Parser falling back to regex-based analysis"]
1857
1875
  };
1858
1876
  }
1877
+ /**
1878
+ * Extract import information using AST walk.
1879
+ *
1880
+ * @param rootNode - Root node of the Java AST.
1881
+ * @returns Array of discovered FileImport objects.
1882
+ */
1859
1883
  extractImportsAST(rootNode) {
1860
1884
  const imports = [];
1861
1885
  for (const node of rootNode.children) {
@@ -1889,6 +1913,13 @@ var JavaParser = class extends BaseLanguageParser {
1889
1913
  }
1890
1914
  return imports;
1891
1915
  }
1916
+ /**
1917
+ * Extract export information (classes, interfaces, methods) using AST walk.
1918
+ *
1919
+ * @param rootNode - Root node of the Java AST.
1920
+ * @param code - Source code for documentation extraction.
1921
+ * @returns Array of discovered ExportInfo objects.
1922
+ */
1892
1923
  extractExportsAST(rootNode, code) {
1893
1924
  const exports = [];
1894
1925
  for (const node of rootNode.children) {
@@ -1919,11 +1950,25 @@ var JavaParser = class extends BaseLanguageParser {
1919
1950
  }
1920
1951
  return exports;
1921
1952
  }
1953
+ /**
1954
+ * Extract modifiers (visibility, static, etc.) from a node.
1955
+ *
1956
+ * @param node - AST node to extract modifiers from.
1957
+ * @returns Array of modifier strings.
1958
+ */
1922
1959
  getModifiers(node) {
1923
1960
  const modifiersNode = node.children.find((c) => c.type === "modifiers");
1924
1961
  if (!modifiersNode) return [];
1925
1962
  return modifiersNode.children.map((c) => c.text);
1926
1963
  }
1964
+ /**
1965
+ * Extract methods and nested exports from a class or interface body.
1966
+ *
1967
+ * @param parentNode - Class or interface declaration node.
1968
+ * @param parentName - Name of the parent class/interface.
1969
+ * @param exports - Array to collect discovered exports into.
1970
+ * @param code - Source code for context.
1971
+ */
1927
1972
  extractSubExports(parentNode, parentName, exports, code) {
1928
1973
  const bodyNode = parentNode.children.find((c) => c.type === "class_body");
1929
1974
  if (!bodyNode) return;
@@ -1982,11 +2027,24 @@ var CSharpParser = class extends BaseLanguageParser {
1982
2027
  getParserName() {
1983
2028
  return "c_sharp";
1984
2029
  }
2030
+ /**
2031
+ * Analyze metadata for a C# node (purity, side effects).
2032
+ *
2033
+ * @param node - Tree-sitter node to analyze.
2034
+ * @param code - Source code for context.
2035
+ * @returns Partial ExportInfo containing discovered metadata.
2036
+ */
1985
2037
  analyzeMetadata(node, code) {
1986
2038
  return analyzeGeneralMetadata(node, code, {
1987
2039
  sideEffectSignatures: ["Console.Write", "File.Write", "Logging."]
1988
2040
  });
1989
2041
  }
2042
+ /**
2043
+ * Fallback regex-based parsing when tree-sitter is unavailable.
2044
+ *
2045
+ * @param code - Source code content.
2046
+ * @returns Consolidated ParseResult.
2047
+ */
1990
2048
  parseRegex(code) {
1991
2049
  const lines = code.split("\n");
1992
2050
  const exports = [];
@@ -2048,6 +2106,12 @@ var CSharpParser = class extends BaseLanguageParser {
2048
2106
  warnings: ["Parser falling back to regex-based analysis"]
2049
2107
  };
2050
2108
  }
2109
+ /**
2110
+ * Extract import information (usings) using AST walk.
2111
+ *
2112
+ * @param rootNode - Root node of the C# AST.
2113
+ * @returns Array of discovered FileImport objects.
2114
+ */
2051
2115
  extractImportsAST(rootNode) {
2052
2116
  const imports = [];
2053
2117
  const findUsings = (node) => {
@@ -2081,6 +2145,14 @@ var CSharpParser = class extends BaseLanguageParser {
2081
2145
  findUsings(rootNode);
2082
2146
  return imports;
2083
2147
  }
2148
+ /**
2149
+ * Extract export information (classes, methods, properties) using AST walk.
2150
+ * Handles nested namespaces and classes.
2151
+ *
2152
+ * @param rootNode - Root node of the C# AST.
2153
+ * @param code - Source code for documentation extraction.
2154
+ * @returns Array of discovered ExportInfo objects.
2155
+ */
2084
2156
  extractExportsAST(rootNode, code) {
2085
2157
  const exports = [];
2086
2158
  const traverse = (node, currentNamespace, currentClass) => {
@@ -2192,11 +2264,24 @@ var GoParser = class extends BaseLanguageParser {
2192
2264
  getParserName() {
2193
2265
  return "go";
2194
2266
  }
2267
+ /**
2268
+ * Analyze metadata for a Go node (purity, side effects).
2269
+ *
2270
+ * @param node - Tree-sitter node to analyze.
2271
+ * @param code - Source code for context.
2272
+ * @returns Partial ExportInfo containing discovered metadata.
2273
+ */
2195
2274
  analyzeMetadata(node, code) {
2196
2275
  return analyzeGeneralMetadata(node, code, {
2197
2276
  sideEffectSignatures: ["<-", "fmt.Print", "fmt.Fprintf", "os.Exit"]
2198
2277
  });
2199
2278
  }
2279
+ /**
2280
+ * Fallback regex-based parsing when tree-sitter is unavailable.
2281
+ *
2282
+ * @param code - Source code content.
2283
+ * @returns Consolidated ParseResult.
2284
+ */
2200
2285
  parseRegex(code) {
2201
2286
  const lines = code.split("\n");
2202
2287
  const exports = [];
@@ -2274,6 +2359,12 @@ var GoParser = class extends BaseLanguageParser {
2274
2359
  warnings: ["Parser falling back to regex-based analysis"]
2275
2360
  };
2276
2361
  }
2362
+ /**
2363
+ * Extract import information using AST walk.
2364
+ *
2365
+ * @param rootNode - Root node of the Go AST.
2366
+ * @returns Array of discovered FileImport objects.
2367
+ */
2277
2368
  extractImportsAST(rootNode) {
2278
2369
  const imports = [];
2279
2370
  const findImports = (node) => {
@@ -2307,6 +2398,13 @@ var GoParser = class extends BaseLanguageParser {
2307
2398
  findImports(rootNode);
2308
2399
  return imports;
2309
2400
  }
2401
+ /**
2402
+ * Extract export information (functions, types, vars) using AST walk.
2403
+ *
2404
+ * @param rootNode - Root node of the Go AST.
2405
+ * @param code - Source code for documentation extraction.
2406
+ * @returns Array of discovered ExportInfo objects.
2407
+ */
2310
2408
  extractExportsAST(rootNode, code) {
2311
2409
  const exports = [];
2312
2410
  const isExported = (name) => {
@@ -2663,7 +2761,7 @@ function extractTypeReferences(node) {
2663
2761
  return Array.from(types);
2664
2762
  }
2665
2763
 
2666
- // src/utils/ast-parser.ts
2764
+ // src/utils/dependency-analyzer.ts
2667
2765
  function parseFileExports(code, filePath) {
2668
2766
  const parser = getParser(filePath);
2669
2767
  if (parser && parser.language !== "typescript" /* TypeScript */ && parser.language !== "javascript" /* JavaScript */) {
@@ -2705,29 +2803,6 @@ function parseFileExports(code, filePath) {
2705
2803
  return { exports: [], imports: [] };
2706
2804
  }
2707
2805
  }
2708
- function calculateImportSimilarity(export1, export2) {
2709
- if (export1.imports.length === 0 && export2.imports.length === 0) {
2710
- return 1;
2711
- }
2712
- const set1 = new Set(export1.imports);
2713
- const set2 = new Set(export2.imports);
2714
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
2715
- const union = /* @__PURE__ */ new Set([...set1, ...set2]);
2716
- return intersection.size / union.size;
2717
- }
2718
- function parseCode(_code, _language) {
2719
- void _code;
2720
- void _language;
2721
- return null;
2722
- }
2723
- function extractFunctions(_ast) {
2724
- void _ast;
2725
- return [];
2726
- }
2727
- function extractImports(_ast) {
2728
- void _ast;
2729
- return [];
2730
- }
2731
2806
 
2732
2807
  // src/utils/metrics.ts
2733
2808
  function estimateTokens(text) {
@@ -2776,10 +2851,25 @@ async function loadConfig(rootDir) {
2776
2851
  const content = readFileSync(configPath, "utf-8");
2777
2852
  config = JSON.parse(content);
2778
2853
  }
2779
- if (typeof config !== "object" || config === null) {
2780
- throw new Error("Config must be an object");
2854
+ const legacyKeys = ["toolConfigs"];
2855
+ const rootLevelTools = [
2856
+ "pattern-detect",
2857
+ "context-analyzer",
2858
+ "naming-consistency",
2859
+ "ai-signal-clarity"
2860
+ ];
2861
+ const allKeys = Object.keys(config);
2862
+ const foundLegacy = allKeys.filter(
2863
+ (k) => legacyKeys.includes(k) || rootLevelTools.includes(k)
2864
+ );
2865
+ if (foundLegacy.length > 0) {
2866
+ console.warn(
2867
+ `\u26A0\uFE0F Legacy configuration keys found: ${foundLegacy.join(
2868
+ ", "
2869
+ )}. These are deprecated and should be moved under the "tools" key.`
2870
+ );
2781
2871
  }
2782
- return config;
2872
+ return AIReadyConfigSchema.parse(config);
2783
2873
  } catch (error) {
2784
2874
  const errorMessage = error instanceof Error ? error.message : String(error);
2785
2875
  const configError = new Error(
@@ -2807,12 +2897,10 @@ function mergeConfigWithDefaults(userConfig, defaults) {
2807
2897
  if (userConfig.scan.include) mergedConfig.include = userConfig.scan.include;
2808
2898
  if (userConfig.scan.exclude) mergedConfig.exclude = userConfig.scan.exclude;
2809
2899
  }
2810
- const toolOverrides = userConfig.tools && !Array.isArray(userConfig.tools) && typeof userConfig.tools === "object" ? userConfig.tools : userConfig.toolConfigs;
2811
- if (toolOverrides) {
2900
+ if (userConfig.tools) {
2812
2901
  if (!mergedConfig.toolConfigs) mergedConfig.toolConfigs = {};
2813
- for (const [toolName, toolConfig] of Object.entries(toolOverrides)) {
2902
+ for (const [toolName, toolConfig] of Object.entries(userConfig.tools)) {
2814
2903
  if (typeof toolConfig === "object" && toolConfig !== null) {
2815
- mergedConfig[toolName] = { ...mergedConfig[toolName], ...toolConfig };
2816
2904
  mergedConfig.toolConfigs[toolName] = {
2817
2905
  ...mergedConfig.toolConfigs[toolName],
2818
2906
  ...toolConfig
@@ -2828,6 +2916,13 @@ function mergeConfigWithDefaults(userConfig, defaults) {
2828
2916
 
2829
2917
  // src/business/pricing-models.ts
2830
2918
  var MODEL_PRICING_PRESETS = {
2919
+ "gpt-5.4-mini": {
2920
+ name: "GPT-5.4 Mini",
2921
+ pricePer1KInputTokens: 1e-4,
2922
+ pricePer1KOutputTokens: 4e-4,
2923
+ contextTier: "extended",
2924
+ typicalQueriesPerDevPerDay: 200
2925
+ },
2831
2926
  "gpt-5.3": {
2832
2927
  name: "GPT-5.3",
2833
2928
  pricePer1KInputTokens: 2e-3,
@@ -2849,20 +2944,6 @@ var MODEL_PRICING_PRESETS = {
2849
2944
  contextTier: "frontier",
2850
2945
  typicalQueriesPerDevPerDay: 120
2851
2946
  },
2852
- "gpt-4o": {
2853
- name: "GPT-4o (legacy)",
2854
- pricePer1KInputTokens: 5e-3,
2855
- pricePer1KOutputTokens: 0.015,
2856
- contextTier: "extended",
2857
- typicalQueriesPerDevPerDay: 60
2858
- },
2859
- "claude-3-5-sonnet": {
2860
- name: "Claude 3.5 Sonnet (legacy)",
2861
- pricePer1KInputTokens: 3e-3,
2862
- pricePer1KOutputTokens: 0.015,
2863
- contextTier: "extended",
2864
- typicalQueriesPerDevPerDay: 80
2865
- },
2866
2947
  "gemini-1-5-pro": {
2867
2948
  name: "Gemini 1.5 Pro (legacy)",
2868
2949
  pricePer1KInputTokens: 125e-5,
@@ -2886,7 +2967,7 @@ var MODEL_PRICING_PRESETS = {
2886
2967
  }
2887
2968
  };
2888
2969
  function getModelPreset(modelId) {
2889
- return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
2970
+ return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["gpt-5.4-mini"];
2890
2971
  }
2891
2972
 
2892
2973
  // src/business/cost-metrics.ts
@@ -2907,7 +2988,7 @@ function calculateMonthlyCost(tokenWaste, config = {}) {
2907
2988
  // Added baseline chattiness
2908
2989
  }
2909
2990
  });
2910
- const preset = getModelPreset("claude-3.5-sonnet");
2991
+ const preset = getModelPreset("gpt-5.4-mini");
2911
2992
  return estimateCostFromBudget(budget, preset, config);
2912
2993
  }
2913
2994
  function calculateTokenBudget(params) {
@@ -3753,12 +3834,12 @@ function calculateTestabilityIndex(params) {
3753
3834
  const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
3754
3835
  const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
3755
3836
  const purityScore = Math.round(
3756
- (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
3837
+ (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.7) * 100
3757
3838
  );
3758
3839
  const dependencyInjectionScore = Math.round(
3759
3840
  Math.min(
3760
3841
  100,
3761
- (totalClasses > 0 ? injectionPatterns / totalClasses : 0.5) * 100
3842
+ (totalClasses > 0 ? injectionPatterns / totalClasses : 0.8) * 100
3762
3843
  )
3763
3844
  );
3764
3845
  const interfaceFocusScore = Math.max(
@@ -3839,9 +3920,9 @@ function calculateDocDrift(params) {
3839
3920
  const outdatedRatio = totalExports > 0 ? outdatedComments / totalExports : 0;
3840
3921
  const complexityRatio = totalExports > 0 ? undocumentedComplexity / totalExports : 0;
3841
3922
  const driftRatio = totalExports > 0 ? actualDrift / totalExports : 0;
3842
- const DRIFT_THRESHOLD = 0.2;
3843
- const OUTDATED_THRESHOLD = 0.4;
3844
- const COMPLEXITY_THRESHOLD = 0.2;
3923
+ const DRIFT_THRESHOLD = 0.35;
3924
+ const OUTDATED_THRESHOLD = 0.5;
3925
+ const COMPLEXITY_THRESHOLD = 0.3;
3845
3926
  const UNCOMMENTED_THRESHOLD = 0.8;
3846
3927
  const driftRisk = Math.min(100, driftRatio / DRIFT_THRESHOLD * 100);
3847
3928
  const outdatedRisk = Math.min(
@@ -4306,6 +4387,7 @@ function emitIssuesAsAnnotations(issues) {
4306
4387
  });
4307
4388
  }
4308
4389
  export {
4390
+ AIReadyConfigSchema,
4309
4391
  AnalysisResultSchema,
4310
4392
  AnalysisStatus,
4311
4393
  AnalysisStatusSchema,
@@ -4387,8 +4469,6 @@ export {
4387
4469
  estimateCostFromBudget,
4388
4470
  estimateTokens,
4389
4471
  exportHistory,
4390
- extractFunctions,
4391
- extractImports,
4392
4472
  findLatestReport,
4393
4473
  findLatestScanReport,
4394
4474
  formatAcceptanceRate,
@@ -4433,8 +4513,11 @@ export {
4433
4513
  loadMergedConfig,
4434
4514
  loadScoreHistory,
4435
4515
  mergeConfigWithDefaults,
4516
+ normalizeAnalysisResult,
4517
+ normalizeIssue,
4518
+ normalizeMetrics,
4519
+ normalizeSpokeOutput,
4436
4520
  normalizeToolName,
4437
- parseCode,
4438
4521
  parseFileExports,
4439
4522
  parseWeightString,
4440
4523
  predictAcceptanceRate,