@aiready/core 0.23.7 → 0.23.9

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-CGOS2J6T.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) {
@@ -725,148 +775,67 @@ var TypeScriptParser = class {
725
775
  this.extensions = [".ts", ".tsx", ".js", ".jsx"];
726
776
  }
727
777
  async initialize() {
728
- return Promise.resolve();
729
778
  }
730
- async getAST(code, filePath) {
731
- return parse(code, {
732
- loc: true,
733
- range: true,
734
- jsx: filePath.match(/\.[jt]sx$/i) !== null,
735
- filePath,
736
- sourceType: "module",
737
- ecmaVersion: "latest",
738
- comment: true
739
- });
779
+ canHandle(filePath) {
780
+ return this.extensions.some((ext) => filePath.endsWith(ext));
740
781
  }
741
- analyzeMetadata(node, code) {
742
- const metadata = {
743
- isPure: true,
744
- hasSideEffects: false
745
- };
746
- const start = node.range?.[0] ?? 0;
747
- const preceding = code.slice(Math.max(0, start - 1e3), start);
748
- const jsdocMatches = Array.from(
749
- preceding.matchAll(/\/\*\*([\s\S]*?)\*\//g)
750
- );
751
- if (jsdocMatches.length > 0) {
752
- const lastMatch = jsdocMatches[jsdocMatches.length - 1];
753
- const matchEndIndex = (lastMatch.index || 0) + lastMatch[0].length;
754
- const between = preceding.slice(matchEndIndex);
755
- if (/^\s*$/.test(between)) {
756
- const precedingStartOffset = Math.max(0, start - 1e3);
757
- const absoluteStartOffset = precedingStartOffset + (lastMatch.index || 0);
758
- const absoluteEndOffset = precedingStartOffset + matchEndIndex;
759
- const codeBeforeStart = code.slice(0, absoluteStartOffset);
760
- const startLines = codeBeforeStart.split("\n");
761
- const startLine = startLines.length;
762
- const startColumn = startLines[startLines.length - 1].length;
763
- const codeBeforeEnd = code.slice(0, absoluteEndOffset);
764
- const endLines = codeBeforeEnd.split("\n");
765
- const endLine = endLines.length;
766
- const endColumn = endLines[endLines.length - 1].length;
767
- metadata.documentation = {
768
- content: lastMatch[1].replace(/^\s*\*+/gm, "").trim(),
769
- type: "jsdoc",
770
- loc: {
771
- start: { line: startLine, column: startColumn },
772
- end: { line: endLine, column: endColumn }
773
- }
774
- };
775
- }
776
- }
777
- const walk = (n) => {
778
- if (!n) return;
779
- if (n.type === "AssignmentExpression") {
780
- metadata.isPure = false;
781
- metadata.hasSideEffects = true;
782
- }
783
- if (n.type === "UpdateExpression") {
784
- metadata.isPure = false;
785
- metadata.hasSideEffects = true;
786
- }
787
- if (n.type === "CallExpression" && n.callee.type === "MemberExpression" && n.callee.object.type === "Identifier") {
788
- const objName = n.callee.object.name;
789
- if (["console", "process", "fs", "window", "document"].includes(objName)) {
790
- metadata.isPure = false;
791
- metadata.hasSideEffects = true;
792
- }
793
- }
794
- if (n.type === "ThrowStatement") {
795
- metadata.isPure = false;
796
- metadata.hasSideEffects = true;
797
- }
798
- for (const key of Object.keys(n)) {
799
- if (key === "parent") continue;
800
- const child = n[key];
801
- if (child && typeof child === "object") {
802
- if (Array.isArray(child)) {
803
- child.forEach((c) => c?.type && walk(c));
804
- } else if (child.type) {
805
- walk(child);
806
- }
807
- }
808
- }
809
- };
810
- let nodeToAnalyze = node;
811
- if (node.type === "ExportNamedDeclaration" && node.declaration) {
812
- nodeToAnalyze = node.declaration;
813
- } else if (node.type === "ExportDefaultDeclaration" && node.declaration) {
814
- if (node.declaration.type !== "TSInterfaceDeclaration" && node.declaration.type !== "TSTypeAliasDeclaration") {
815
- nodeToAnalyze = node.declaration;
816
- }
817
- }
818
- if (nodeToAnalyze.type === "FunctionDeclaration" || nodeToAnalyze.type === "FunctionExpression" || nodeToAnalyze.type === "ArrowFunctionExpression") {
819
- if (nodeToAnalyze.body) walk(nodeToAnalyze.body);
820
- } else if (nodeToAnalyze.type === "ClassDeclaration" || nodeToAnalyze.type === "ClassExpression") {
821
- 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
+ });
822
797
  }
823
- return metadata;
824
798
  }
825
799
  parse(code, filePath) {
826
800
  try {
827
- const isJavaScript = filePath.match(/\.jsx?$/i);
828
801
  const ast = parse(code, {
802
+ filePath,
829
803
  loc: true,
830
804
  range: true,
831
- jsx: filePath.match(/\.[jt]sx$/i) !== null,
832
- filePath,
833
- sourceType: "module",
834
- ecmaVersion: "latest",
835
- comment: true
805
+ tokens: true,
806
+ comment: true,
807
+ jsx: filePath.endsWith("x")
836
808
  });
837
809
  const imports = this.extractImports(ast);
838
- const exports = this.extractExports(ast, imports, code);
810
+ const exports = this.extractExports(ast, code);
839
811
  return {
840
812
  exports,
841
813
  imports,
842
- language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
843
- warnings: []
814
+ language: this.language
844
815
  };
845
816
  } catch (error) {
846
- const err = error;
847
- throw new ParseError(
848
- `Failed to parse ${filePath}: ${err.message}`,
849
- filePath
850
- );
817
+ throw new ParseError(error.message, filePath, {
818
+ line: error.lineNumber || 1,
819
+ column: error.column || 0
820
+ });
851
821
  }
852
822
  }
853
823
  getNamingConventions() {
854
824
  return {
855
- // camelCase for variables and functions
856
825
  variablePattern: /^[a-z][a-zA-Z0-9]*$/,
857
826
  functionPattern: /^[a-z][a-zA-Z0-9]*$/,
858
- // PascalCase for classes, types and interfaces
859
827
  classPattern: /^[A-Z][a-zA-Z0-9]*$/,
860
- typePattern: /^[A-Z][a-zA-Z0-9]*$/,
861
- interfacePattern: /^[A-Z][a-zA-Z0-9]*$/,
862
- // UPPER_CASE for constants
863
828
  constantPattern: /^[A-Z][A-Z0-9_]*$/,
864
- // Common exceptions (React hooks, etc.)
865
- exceptions: ["__filename", "__dirname", "__esModule"]
829
+ typePattern: /^[A-Z][a-zA-Z0-9]*$/,
830
+ interfacePattern: /^I?[A-Z][a-zA-Z0-9]*$/
866
831
  };
867
832
  }
868
- canHandle(filePath) {
869
- 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
+ };
870
839
  }
871
840
  extractImports(ast) {
872
841
  const imports = [];
@@ -904,168 +873,165 @@ var TypeScriptParser = class {
904
873
  }
905
874
  return imports;
906
875
  }
907
- extractExports(ast, imports, code) {
876
+ extractExports(ast, code) {
908
877
  const exports = [];
909
- const importedNames = new Set(
910
- imports.flatMap(
911
- (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
912
- )
913
- );
914
878
  for (const node of ast.body) {
915
- if (node.type === "ExportNamedDeclaration" && node.declaration) {
916
- const extracted = this.extractFromDeclaration(
917
- node.declaration,
918
- importedNames,
919
- code,
920
- node
921
- // Pass the ExportNamedDeclaration as parent for metadata
922
- );
923
- exports.push(...extracted);
924
- } else if (node.type === "ExportDefaultDeclaration") {
925
- const metadata = this.analyzeMetadata(node, code);
926
- let name = "default";
927
- let type = "default";
928
- if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
929
- name = node.declaration.id.name;
930
- type = "function";
931
- } else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
932
- name = node.declaration.id.name;
933
- 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
+ }
934
938
  }
935
- exports.push({
936
- name,
937
- type,
938
- loc: node.loc ? {
939
- start: {
940
- line: node.loc.start.line,
941
- column: node.loc.start.column
942
- },
943
- end: { line: node.loc.end.line, column: node.loc.end.column }
944
- } : void 0,
945
- ...metadata
946
- });
939
+ } else if (node.type === "ExportDefaultDeclaration") {
940
+ exports.push(this.createExport("default", "default", node, code));
947
941
  }
948
942
  }
949
943
  return exports;
950
944
  }
951
- extractFromDeclaration(declaration, importedNames, code, parentNode) {
952
- const exports = [];
953
- const metadata = this.analyzeMetadata(parentNode || declaration, code);
954
- if ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") && declaration.id) {
955
- exports.push({
956
- name: declaration.id.name,
957
- type: "function",
958
- 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) => {
959
984
  if (p.type === "Identifier") return p.name;
960
- if (p.type === "AssignmentPattern" && p.left.type === "Identifier")
961
- return p.left.name;
962
- if (p.type === "RestElement" && p.argument.type === "Identifier")
963
- return p.argument.name;
964
- return "unknown";
965
- }),
966
- loc: declaration.loc ? {
967
- start: {
968
- line: declaration.loc.start.line,
969
- column: declaration.loc.start.column
970
- },
971
- end: {
972
- line: declaration.loc.end.line,
973
- column: declaration.loc.end.column
974
- }
975
- } : void 0,
976
- ...metadata
977
- });
978
- } else if (declaration.type === "ClassDeclaration" && declaration.id) {
979
- const body = declaration.body.body;
980
- const methods = body.filter((m) => m.type === "MethodDefinition");
981
- const properties = body.filter((m) => m.type === "PropertyDefinition");
982
- const constructor = methods.find(
983
- (m) => m.kind === "constructor"
984
- );
985
- const parameters = constructor ? constructor.value.params.map((p) => {
986
- if (p.type === "Identifier") return p.name;
987
- if (p.type === "TSParameterProperty" && p.parameter.type === "Identifier")
988
- return p.parameter.name;
989
- return "unknown";
990
- }) : [];
991
- exports.push({
992
- name: declaration.id.name,
993
- type: "class",
994
- methodCount: methods.length,
995
- propertyCount: properties.length,
996
- parameters,
997
- loc: declaration.loc ? {
998
- start: {
999
- line: declaration.loc.start.line,
1000
- column: declaration.loc.start.column
1001
- },
1002
- end: {
1003
- line: declaration.loc.end.line,
1004
- column: declaration.loc.end.column
1005
- }
1006
- } : void 0,
1007
- ...metadata
1008
- });
1009
- } else if (declaration.type === "VariableDeclaration") {
1010
- for (const declarator of declaration.declarations) {
1011
- if (declarator.id.type === "Identifier") {
1012
- exports.push({
1013
- name: declarator.id.name,
1014
- type: "const",
1015
- loc: declarator.loc ? {
1016
- start: {
1017
- line: declarator.loc.start.line,
1018
- column: declarator.loc.start.column
1019
- },
1020
- end: {
1021
- line: declarator.loc.end.line,
1022
- column: declarator.loc.end.column
1023
- }
1024
- } : void 0,
1025
- ...metadata
1026
- });
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;
1027
1029
  }
1030
+ return true;
1028
1031
  }
1029
- } else if (declaration.type === "TSTypeAliasDeclaration") {
1030
- exports.push({
1031
- name: declaration.id.name,
1032
- type: "type",
1033
- loc: declaration.loc ? {
1034
- start: {
1035
- line: declaration.loc.start.line,
1036
- column: declaration.loc.start.column
1037
- },
1038
- end: {
1039
- line: declaration.loc.end.line,
1040
- column: declaration.loc.end.column
1041
- }
1042
- } : void 0,
1043
- ...metadata
1044
- });
1045
- } else if (declaration.type === "TSInterfaceDeclaration") {
1046
- const body = declaration.body.body;
1047
- const methods = body.filter((m) => m.type === "TSMethodSignature");
1048
- const properties = body.filter((m) => m.type === "TSPropertySignature");
1049
- exports.push({
1050
- name: declaration.id.name,
1051
- type: "interface",
1052
- methodCount: methods.length,
1053
- propertyCount: properties.length || body.length,
1054
- // Fallback to body.length
1055
- loc: declaration.loc ? {
1056
- start: {
1057
- line: declaration.loc.start.line,
1058
- column: declaration.loc.start.column
1059
- },
1060
- end: {
1061
- line: declaration.loc.end.line,
1062
- column: declaration.loc.end.column
1063
- }
1064
- } : void 0,
1065
- ...metadata
1066
- });
1032
+ return true;
1067
1033
  }
1068
- return exports;
1034
+ return false;
1069
1035
  }
1070
1036
  };
1071
1037
 
@@ -1355,7 +1321,7 @@ var PythonParser = class extends BaseLanguageParser {
1355
1321
  * Extract import information using AST walk.
1356
1322
  *
1357
1323
  * @param rootNode - Root node of the Python AST.
1358
- * @returns Array of discovered ImportInfo objects.
1324
+ * @returns Array of discovered FileImport objects.
1359
1325
  */
1360
1326
  extractImportsAST(rootNode) {
1361
1327
  const imports = [];
@@ -1912,7 +1878,7 @@ var JavaParser = class extends BaseLanguageParser {
1912
1878
  * Extract import information using AST walk.
1913
1879
  *
1914
1880
  * @param rootNode - Root node of the Java AST.
1915
- * @returns Array of discovered ImportInfo objects.
1881
+ * @returns Array of discovered FileImport objects.
1916
1882
  */
1917
1883
  extractImportsAST(rootNode) {
1918
1884
  const imports = [];
@@ -2144,7 +2110,7 @@ var CSharpParser = class extends BaseLanguageParser {
2144
2110
  * Extract import information (usings) using AST walk.
2145
2111
  *
2146
2112
  * @param rootNode - Root node of the C# AST.
2147
- * @returns Array of discovered ImportInfo objects.
2113
+ * @returns Array of discovered FileImport objects.
2148
2114
  */
2149
2115
  extractImportsAST(rootNode) {
2150
2116
  const imports = [];
@@ -2397,7 +2363,7 @@ var GoParser = class extends BaseLanguageParser {
2397
2363
  * Extract import information using AST walk.
2398
2364
  *
2399
2365
  * @param rootNode - Root node of the Go AST.
2400
- * @returns Array of discovered ImportInfo objects.
2366
+ * @returns Array of discovered FileImport objects.
2401
2367
  */
2402
2368
  extractImportsAST(rootNode) {
2403
2369
  const imports = [];
@@ -2885,10 +2851,25 @@ async function loadConfig(rootDir) {
2885
2851
  const content = readFileSync(configPath, "utf-8");
2886
2852
  config = JSON.parse(content);
2887
2853
  }
2888
- if (typeof config !== "object" || config === null) {
2889
- 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
+ );
2890
2871
  }
2891
- return config;
2872
+ return AIReadyConfigSchema.parse(config);
2892
2873
  } catch (error) {
2893
2874
  const errorMessage = error instanceof Error ? error.message : String(error);
2894
2875
  const configError = new Error(
@@ -2916,12 +2897,10 @@ function mergeConfigWithDefaults(userConfig, defaults) {
2916
2897
  if (userConfig.scan.include) mergedConfig.include = userConfig.scan.include;
2917
2898
  if (userConfig.scan.exclude) mergedConfig.exclude = userConfig.scan.exclude;
2918
2899
  }
2919
- const toolOverrides = userConfig.tools && !Array.isArray(userConfig.tools) && typeof userConfig.tools === "object" ? userConfig.tools : userConfig.toolConfigs;
2920
- if (toolOverrides) {
2900
+ if (userConfig.tools) {
2921
2901
  if (!mergedConfig.toolConfigs) mergedConfig.toolConfigs = {};
2922
- for (const [toolName, toolConfig] of Object.entries(toolOverrides)) {
2902
+ for (const [toolName, toolConfig] of Object.entries(userConfig.tools)) {
2923
2903
  if (typeof toolConfig === "object" && toolConfig !== null) {
2924
- mergedConfig[toolName] = { ...mergedConfig[toolName], ...toolConfig };
2925
2904
  mergedConfig.toolConfigs[toolName] = {
2926
2905
  ...mergedConfig.toolConfigs[toolName],
2927
2906
  ...toolConfig
@@ -2937,6 +2916,13 @@ function mergeConfigWithDefaults(userConfig, defaults) {
2937
2916
 
2938
2917
  // src/business/pricing-models.ts
2939
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
+ },
2940
2926
  "gpt-5.3": {
2941
2927
  name: "GPT-5.3",
2942
2928
  pricePer1KInputTokens: 2e-3,
@@ -2958,20 +2944,6 @@ var MODEL_PRICING_PRESETS = {
2958
2944
  contextTier: "frontier",
2959
2945
  typicalQueriesPerDevPerDay: 120
2960
2946
  },
2961
- "gpt-4o": {
2962
- name: "GPT-4o (legacy)",
2963
- pricePer1KInputTokens: 5e-3,
2964
- pricePer1KOutputTokens: 0.015,
2965
- contextTier: "extended",
2966
- typicalQueriesPerDevPerDay: 60
2967
- },
2968
- "claude-3-5-sonnet": {
2969
- name: "Claude 3.5 Sonnet (legacy)",
2970
- pricePer1KInputTokens: 3e-3,
2971
- pricePer1KOutputTokens: 0.015,
2972
- contextTier: "extended",
2973
- typicalQueriesPerDevPerDay: 80
2974
- },
2975
2947
  "gemini-1-5-pro": {
2976
2948
  name: "Gemini 1.5 Pro (legacy)",
2977
2949
  pricePer1KInputTokens: 125e-5,
@@ -2995,7 +2967,7 @@ var MODEL_PRICING_PRESETS = {
2995
2967
  }
2996
2968
  };
2997
2969
  function getModelPreset(modelId) {
2998
- return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
2970
+ return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["gpt-5.4-mini"];
2999
2971
  }
3000
2972
 
3001
2973
  // src/business/cost-metrics.ts
@@ -3016,7 +2988,7 @@ function calculateMonthlyCost(tokenWaste, config = {}) {
3016
2988
  // Added baseline chattiness
3017
2989
  }
3018
2990
  });
3019
- const preset = getModelPreset("claude-3.5-sonnet");
2991
+ const preset = getModelPreset("gpt-5.4-mini");
3020
2992
  return estimateCostFromBudget(budget, preset, config);
3021
2993
  }
3022
2994
  function calculateTokenBudget(params) {
@@ -3846,6 +3818,40 @@ function calculateAgentGrounding(params) {
3846
3818
  }
3847
3819
 
3848
3820
  // src/metrics/testability-index.ts
3821
+ function isLikelyEntryPoint(filePath) {
3822
+ const basename = filePath.split("/").pop() || "";
3823
+ const lowerBasename = basename.toLowerCase();
3824
+ const entryPointPatterns = [
3825
+ "cli",
3826
+ "main",
3827
+ "bin",
3828
+ "index",
3829
+ // often used as entry point
3830
+ "run",
3831
+ "serve",
3832
+ "start",
3833
+ "boot",
3834
+ "init"
3835
+ ];
3836
+ const nameWithoutExt = lowerBasename.replace(
3837
+ /\.(ts|js|tsx|jsx|mjs|cjs)$/,
3838
+ ""
3839
+ );
3840
+ if (entryPointPatterns.some(
3841
+ (p) => nameWithoutExt === p || nameWithoutExt.endsWith(`-${p}`) || nameWithoutExt.startsWith(`${p}-`)
3842
+ )) {
3843
+ return true;
3844
+ }
3845
+ const cliDirPatterns = ["/bin/", "/cli/", "/cmd/", "/commands/"];
3846
+ if (cliDirPatterns.some((p) => filePath.includes(p))) {
3847
+ return true;
3848
+ }
3849
+ return false;
3850
+ }
3851
+ function calculateFilePurityScore(pureFunctions, totalFunctions) {
3852
+ if (totalFunctions === 0) return 100;
3853
+ return Math.round(pureFunctions / totalFunctions * 100);
3854
+ }
3849
3855
  function calculateTestabilityIndex(params) {
3850
3856
  const {
3851
3857
  testFiles,
@@ -3857,17 +3863,18 @@ function calculateTestabilityIndex(params) {
3857
3863
  bloatedInterfaces,
3858
3864
  totalInterfaces,
3859
3865
  externalStateMutations,
3860
- hasTestFramework
3866
+ hasTestFramework,
3867
+ fileDetails
3861
3868
  } = params;
3862
3869
  const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
3863
3870
  const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
3864
3871
  const purityScore = Math.round(
3865
- (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
3872
+ (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.7) * 100
3866
3873
  );
3867
3874
  const dependencyInjectionScore = Math.round(
3868
3875
  Math.min(
3869
3876
  100,
3870
- (totalClasses > 0 ? injectionPatterns / totalClasses : 0.5) * 100
3877
+ (totalClasses > 0 ? injectionPatterns / totalClasses : 0.8) * 100
3871
3878
  )
3872
3879
  );
3873
3880
  const interfaceFocusScore = Math.max(
@@ -3883,7 +3890,39 @@ function calculateTestabilityIndex(params) {
3883
3890
  )
3884
3891
  );
3885
3892
  const frameworkWeight = hasTestFramework ? 1 : 0.8;
3886
- const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
3893
+ let fileMetrics;
3894
+ let libraryPureFunctions = pureFunctions;
3895
+ let libraryTotalFunctions = totalFunctions;
3896
+ let entryPointCount = 0;
3897
+ if (fileDetails && fileDetails.length > 0) {
3898
+ fileMetrics = fileDetails.map((file) => {
3899
+ const isEntryPoint = isLikelyEntryPoint(file.filePath);
3900
+ const purityScore2 = calculateFilePurityScore(
3901
+ file.pureFunctions,
3902
+ file.totalFunctions
3903
+ );
3904
+ if (isEntryPoint) {
3905
+ entryPointCount++;
3906
+ libraryPureFunctions -= file.pureFunctions;
3907
+ libraryTotalFunctions -= file.totalFunctions;
3908
+ }
3909
+ return {
3910
+ filePath: file.filePath,
3911
+ score: purityScore2,
3912
+ // Simplified - just purity for file-level
3913
+ purityScore: purityScore2,
3914
+ isEntryPoint
3915
+ };
3916
+ });
3917
+ }
3918
+ const libraryPurityScore = Math.max(
3919
+ 0,
3920
+ Math.round(
3921
+ (libraryTotalFunctions > 0 ? libraryPureFunctions / libraryTotalFunctions : 0.7) * 100
3922
+ )
3923
+ );
3924
+ const effectivePurityScore = fileDetails && fileDetails.length > 0 ? libraryPurityScore : purityScore;
3925
+ const rawScore = (testCoverageRatio * 0.3 + effectivePurityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
3887
3926
  const score = Math.max(0, Math.min(100, Math.round(rawScore)));
3888
3927
  let rating;
3889
3928
  if (score >= 85) rating = "excellent";
@@ -3908,9 +3947,9 @@ function calculateTestabilityIndex(params) {
3908
3947
  `Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
3909
3948
  );
3910
3949
  }
3911
- if (purityScore < 50)
3950
+ if (effectivePurityScore < 50)
3912
3951
  recommendations.push(
3913
- "Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
3952
+ entryPointCount > 0 ? `Extract pure functions from library code (${entryPointCount} entry point files excluded) \u2014 pure functions are trivially AI-testable` : "Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
3914
3953
  );
3915
3954
  if (dependencyInjectionScore < 50 && totalClasses > 0)
3916
3955
  recommendations.push(
@@ -3925,13 +3964,15 @@ function calculateTestabilityIndex(params) {
3925
3964
  rating,
3926
3965
  dimensions: {
3927
3966
  testCoverageRatio,
3928
- purityScore,
3967
+ purityScore: effectivePurityScore,
3929
3968
  dependencyInjectionScore,
3930
3969
  interfaceFocusScore,
3931
3970
  observabilityScore
3932
3971
  },
3933
3972
  aiChangeSafetyRating,
3934
- recommendations
3973
+ recommendations,
3974
+ fileMetrics,
3975
+ libraryScore: Math.max(0, fileMetrics ? libraryPurityScore : purityScore)
3935
3976
  };
3936
3977
  }
3937
3978
 
@@ -3948,9 +3989,9 @@ function calculateDocDrift(params) {
3948
3989
  const outdatedRatio = totalExports > 0 ? outdatedComments / totalExports : 0;
3949
3990
  const complexityRatio = totalExports > 0 ? undocumentedComplexity / totalExports : 0;
3950
3991
  const driftRatio = totalExports > 0 ? actualDrift / totalExports : 0;
3951
- const DRIFT_THRESHOLD = 0.2;
3952
- const OUTDATED_THRESHOLD = 0.4;
3953
- const COMPLEXITY_THRESHOLD = 0.2;
3992
+ const DRIFT_THRESHOLD = 0.35;
3993
+ const OUTDATED_THRESHOLD = 0.5;
3994
+ const COMPLEXITY_THRESHOLD = 0.3;
3954
3995
  const UNCOMMENTED_THRESHOLD = 0.8;
3955
3996
  const driftRisk = Math.min(100, driftRatio / DRIFT_THRESHOLD * 100);
3956
3997
  const outdatedRisk = Math.min(
@@ -4415,6 +4456,7 @@ function emitIssuesAsAnnotations(issues) {
4415
4456
  });
4416
4457
  }
4417
4458
  export {
4459
+ AIReadyConfigSchema,
4418
4460
  AnalysisResultSchema,
4419
4461
  AnalysisStatus,
4420
4462
  AnalysisStatusSchema,
@@ -4540,6 +4582,10 @@ export {
4540
4582
  loadMergedConfig,
4541
4583
  loadScoreHistory,
4542
4584
  mergeConfigWithDefaults,
4585
+ normalizeAnalysisResult,
4586
+ normalizeIssue,
4587
+ normalizeMetrics,
4588
+ normalizeSpokeOutput,
4543
4589
  normalizeToolName,
4544
4590
  parseFileExports,
4545
4591
  parseWeightString,