@aiready/core 0.21.13 → 0.21.15

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.js CHANGED
@@ -123,6 +123,7 @@ __export(index_exports, {
123
123
  getToolWeight: () => getToolWeight,
124
124
  handleCLIError: () => handleCLIError,
125
125
  handleJSONOutput: () => handleJSONOutput,
126
+ initializeParsers: () => initializeParsers,
126
127
  isFileSupported: () => isFileSupported,
127
128
  isSourceFile: () => isSourceFile,
128
129
  loadConfig: () => loadConfig,
@@ -332,15 +333,15 @@ var COMMON_FINE_TUNING_OPTIONS = [
332
333
  var GLOBAL_SCAN_OPTIONS = [...GLOBAL_INFRA_OPTIONS];
333
334
 
334
335
  // src/types/language.ts
335
- var Language = /* @__PURE__ */ ((Language2) => {
336
- Language2["TypeScript"] = "typescript";
337
- Language2["JavaScript"] = "javascript";
338
- Language2["Python"] = "python";
339
- Language2["Java"] = "java";
340
- Language2["Go"] = "go";
341
- Language2["Rust"] = "rust";
342
- Language2["CSharp"] = "csharp";
343
- return Language2;
336
+ var Language = /* @__PURE__ */ ((Language3) => {
337
+ Language3["TypeScript"] = "typescript";
338
+ Language3["JavaScript"] = "javascript";
339
+ Language3["Python"] = "python";
340
+ Language3["Java"] = "java";
341
+ Language3["Go"] = "go";
342
+ Language3["Rust"] = "rust";
343
+ Language3["CSharp"] = "csharp";
344
+ return Language3;
344
345
  })(Language || {});
345
346
  var LANGUAGE_EXTENSIONS = {
346
347
  ".ts": "typescript" /* TypeScript */,
@@ -788,1531 +789,1787 @@ function getSeverityColor(severity, chalk) {
788
789
  }
789
790
 
790
791
  // src/utils/ast-parser.ts
792
+ var import_typescript_estree2 = require("@typescript-eslint/typescript-estree");
793
+
794
+ // src/parsers/typescript-parser.ts
791
795
  var import_typescript_estree = require("@typescript-eslint/typescript-estree");
792
- function parseFileExports(code, filePath) {
793
- try {
794
- const ast = (0, import_typescript_estree.parse)(code, {
795
- loc: true,
796
- range: true,
797
- jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
798
- filePath
799
- });
800
- const imports = extractFileImports(ast);
801
- const exports2 = extractExportsWithDependencies(ast, imports);
802
- return { exports: exports2, imports };
803
- } catch (error) {
804
- return { exports: [], imports: [] };
796
+ var TypeScriptParser = class {
797
+ constructor() {
798
+ this.language = "typescript" /* TypeScript */;
799
+ this.extensions = [".ts", ".tsx", ".js", ".jsx"];
805
800
  }
806
- }
807
- function extractFileImports(ast) {
808
- const imports = [];
809
- for (const node of ast.body) {
810
- if (node.type === "ImportDeclaration") {
811
- const source = node.source.value;
812
- const specifiers = [];
813
- const isTypeOnly = node.importKind === "type";
814
- for (const spec of node.specifiers) {
815
- if (spec.type === "ImportSpecifier") {
816
- const imported = spec.imported;
817
- const importName = imported.type === "Identifier" ? imported.name : imported.value;
818
- specifiers.push(importName);
819
- } else if (spec.type === "ImportDefaultSpecifier") {
820
- specifiers.push("default");
821
- } else if (spec.type === "ImportNamespaceSpecifier") {
822
- specifiers.push("*");
823
- }
824
- }
825
- imports.push({ source, specifiers, isTypeOnly });
826
- }
801
+ async initialize() {
802
+ return Promise.resolve();
827
803
  }
828
- return imports;
829
- }
830
- function extractExportsWithDependencies(ast, fileImports) {
831
- const exports2 = [];
832
- const importedNames = new Set(fileImports.flatMap((imp) => imp.specifiers));
833
- for (const node of ast.body) {
834
- if (node.type === "ExportNamedDeclaration") {
835
- if (node.declaration) {
836
- const exportNodes = extractFromDeclaration(node.declaration);
837
- for (const exp of exportNodes) {
838
- const usedImports = findUsedImports(node.declaration, importedNames);
839
- const typeReferences = extractTypeReferences(node.declaration);
840
- exports2.push({
841
- ...exp,
842
- imports: usedImports,
843
- dependencies: [],
844
- typeReferences,
845
- loc: node.loc
846
- });
847
- }
848
- }
849
- } else if (node.type === "ExportDefaultDeclaration") {
850
- const usedImports = findUsedImports(node.declaration, importedNames);
851
- const typeReferences = extractTypeReferences(node.declaration);
852
- exports2.push({
853
- name: "default",
854
- type: "default",
855
- imports: usedImports,
856
- dependencies: [],
857
- typeReferences,
858
- loc: node.loc
804
+ parse(code, filePath) {
805
+ try {
806
+ const isJavaScript = filePath.match(/\.jsx?$/i);
807
+ const ast = (0, import_typescript_estree.parse)(code, {
808
+ loc: true,
809
+ range: true,
810
+ jsx: filePath.match(/\.[jt]sx$/i) !== null,
811
+ filePath,
812
+ sourceType: "module",
813
+ ecmaVersion: "latest"
859
814
  });
815
+ const imports = this.extractImports(ast);
816
+ const exports2 = this.extractExports(ast, imports);
817
+ return {
818
+ exports: exports2,
819
+ imports,
820
+ language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
821
+ warnings: []
822
+ };
823
+ } catch (error) {
824
+ const err = error;
825
+ throw new ParseError(
826
+ `Failed to parse ${filePath}: ${err.message}`,
827
+ filePath
828
+ );
860
829
  }
861
830
  }
862
- return exports2;
863
- }
864
- function extractFromDeclaration(declaration) {
865
- const results = [];
866
- if (declaration.type === "FunctionDeclaration" && "id" in declaration && declaration.id) {
867
- results.push({ name: declaration.id.name, type: "function" });
868
- } else if (declaration.type === "ClassDeclaration" && "id" in declaration && declaration.id) {
869
- results.push({ name: declaration.id.name, type: "class" });
870
- } else if (declaration.type === "VariableDeclaration") {
871
- for (const declarator of declaration.declarations) {
872
- if (declarator.id.type === "Identifier") {
873
- results.push({ name: declarator.id.name, type: "const" });
874
- }
875
- }
876
- } else if (declaration.type === "TSTypeAliasDeclaration") {
877
- results.push({ name: declaration.id.name, type: "type" });
878
- } else if (declaration.type === "TSInterfaceDeclaration") {
879
- results.push({ name: declaration.id.name, type: "interface" });
880
- }
881
- return results;
882
- }
883
- function findUsedImports(node, importedNames) {
884
- const usedImports = /* @__PURE__ */ new Set();
885
- function visit(n) {
886
- if (n.type === "Identifier" && importedNames.has(n.name)) {
887
- usedImports.add(n.name);
888
- }
889
- for (const key in n) {
890
- const value = n[key];
891
- if (value && typeof value === "object") {
892
- if (Array.isArray(value)) {
893
- value.forEach((child) => {
894
- if (child && typeof child === "object" && "type" in child) {
895
- visit(child);
896
- }
897
- });
898
- } else if ("type" in value) {
899
- visit(value);
900
- }
901
- }
902
- }
831
+ getNamingConventions() {
832
+ return {
833
+ // camelCase for variables and functions
834
+ variablePattern: /^[a-z][a-zA-Z0-9]*$/,
835
+ functionPattern: /^[a-z][a-zA-Z0-9]*$/,
836
+ // PascalCase for classes
837
+ classPattern: /^[A-Z][a-zA-Z0-9]*$/,
838
+ // UPPER_CASE for constants
839
+ constantPattern: /^[A-Z][A-Z0-9_]*$/,
840
+ // Common exceptions (React hooks, etc.)
841
+ exceptions: ["__filename", "__dirname", "__esModule"]
842
+ };
903
843
  }
904
- visit(node);
905
- return Array.from(usedImports);
906
- }
907
- function calculateImportSimilarity(export1, export2) {
908
- if (export1.imports.length === 0 && export2.imports.length === 0) {
909
- return 1;
844
+ canHandle(filePath) {
845
+ return this.extensions.some((ext) => filePath.toLowerCase().endsWith(ext));
910
846
  }
911
- const set1 = new Set(export1.imports);
912
- const set2 = new Set(export2.imports);
913
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
914
- const union = /* @__PURE__ */ new Set([...set1, ...set2]);
915
- return intersection.size / union.size;
916
- }
917
- function extractTypeReferences(node) {
918
- const types = /* @__PURE__ */ new Set();
919
- function visit(n) {
920
- if (!n || typeof n !== "object") return;
921
- if (n.type === "TSTypeReference" && n.typeName) {
922
- if (n.typeName.type === "Identifier") {
923
- types.add(n.typeName.name);
924
- } else if (n.typeName.type === "TSQualifiedName") {
925
- let current = n.typeName;
926
- while (current.type === "TSQualifiedName") {
927
- if (current.right?.type === "Identifier") {
928
- types.add(current.right.name);
929
- }
930
- current = current.left;
847
+ extractImports(ast) {
848
+ const imports = [];
849
+ for (const node of ast.body) {
850
+ if (node.type === "ImportDeclaration") {
851
+ const specifiers = [];
852
+ let isTypeOnly = false;
853
+ if (node.importKind === "type") {
854
+ isTypeOnly = true;
931
855
  }
932
- if (current.type === "Identifier") {
933
- types.add(current.name);
856
+ for (const spec of node.specifiers) {
857
+ if (spec.type === "ImportSpecifier") {
858
+ const imported = spec.imported;
859
+ const name = imported.type === "Identifier" ? imported.name : imported.value;
860
+ specifiers.push(name);
861
+ } else if (spec.type === "ImportDefaultSpecifier") {
862
+ specifiers.push("default");
863
+ } else if (spec.type === "ImportNamespaceSpecifier") {
864
+ specifiers.push("*");
865
+ }
934
866
  }
867
+ imports.push({
868
+ source: node.source.value,
869
+ specifiers,
870
+ isTypeOnly,
871
+ loc: node.loc ? {
872
+ start: {
873
+ line: node.loc.start.line,
874
+ column: node.loc.start.column
875
+ },
876
+ end: { line: node.loc.end.line, column: node.loc.end.column }
877
+ } : void 0
878
+ });
935
879
  }
936
880
  }
937
- if (n.type === "TSInterfaceHeritage" && n.expression) {
938
- if (n.expression.type === "Identifier") {
939
- types.add(n.expression.name);
940
- }
941
- }
942
- for (const key of Object.keys(n)) {
943
- const value = n[key];
944
- if (Array.isArray(value)) {
945
- value.forEach(visit);
946
- } else if (value && typeof value === "object") {
947
- visit(value);
881
+ return imports;
882
+ }
883
+ extractExports(ast, imports) {
884
+ const exports2 = [];
885
+ const importedNames = new Set(
886
+ imports.flatMap(
887
+ (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
888
+ )
889
+ );
890
+ for (const node of ast.body) {
891
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
892
+ const extracted = this.extractFromDeclaration(
893
+ node.declaration,
894
+ importedNames
895
+ );
896
+ exports2.push(...extracted);
897
+ } else if (node.type === "ExportDefaultDeclaration") {
898
+ let name = "default";
899
+ let type = "default";
900
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
901
+ name = node.declaration.id.name;
902
+ type = "function";
903
+ } else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
904
+ name = node.declaration.id.name;
905
+ type = "class";
906
+ }
907
+ exports2.push({
908
+ name,
909
+ type,
910
+ loc: node.loc ? {
911
+ start: {
912
+ line: node.loc.start.line,
913
+ column: node.loc.start.column
914
+ },
915
+ end: { line: node.loc.end.line, column: node.loc.end.column }
916
+ } : void 0
917
+ });
948
918
  }
949
919
  }
920
+ return exports2;
950
921
  }
951
- visit(node);
952
- return Array.from(types);
953
- }
954
- function parseCode(code, language) {
955
- return null;
956
- }
957
- function extractFunctions(ast) {
958
- return [];
959
- }
960
- function extractImports(ast) {
961
- return [];
962
- }
963
-
964
- // src/utils/metrics.ts
965
- function estimateTokens(text) {
966
- return Math.ceil(text.length / 4);
967
- }
968
-
969
- // src/utils/config.ts
970
- var import_fs3 = require("fs");
971
- var import_path3 = require("path");
972
- var import_url = require("url");
973
- var CONFIG_FILES = [
974
- "aiready.json",
975
- "aiready.config.json",
976
- ".aiready.json",
977
- ".aireadyrc.json",
978
- "aiready.config.js",
979
- ".aireadyrc.js"
980
- ];
981
- async function loadConfig(rootDir) {
982
- let currentDir = (0, import_path3.resolve)(rootDir);
983
- while (true) {
984
- for (const configFile of CONFIG_FILES) {
985
- const configPath = (0, import_path3.join)(currentDir, configFile);
986
- if ((0, import_fs3.existsSync)(configPath)) {
987
- try {
988
- let config;
989
- if (configFile.endsWith(".js")) {
990
- const fileUrl = (0, import_url.pathToFileURL)(configPath).href;
991
- const module2 = await import(`${fileUrl}?t=${Date.now()}`);
992
- config = module2.default || module2;
993
- } else {
994
- const content = (0, import_fs3.readFileSync)(configPath, "utf-8");
995
- config = JSON.parse(content);
922
+ extractFromDeclaration(declaration, importedNames) {
923
+ const exports2 = [];
924
+ if (declaration.type === "FunctionDeclaration" && declaration.id) {
925
+ exports2.push({
926
+ name: declaration.id.name,
927
+ type: "function",
928
+ parameters: declaration.params.map(
929
+ (p) => p.type === "Identifier" ? p.name : "unknown"
930
+ ),
931
+ loc: declaration.loc ? {
932
+ start: {
933
+ line: declaration.loc.start.line,
934
+ column: declaration.loc.start.column
935
+ },
936
+ end: {
937
+ line: declaration.loc.end.line,
938
+ column: declaration.loc.end.column
996
939
  }
997
- if (typeof config !== "object" || config === null) {
998
- throw new Error("Config must be an object");
940
+ } : void 0
941
+ });
942
+ } else if (declaration.type === "ClassDeclaration" && declaration.id) {
943
+ exports2.push({
944
+ name: declaration.id.name,
945
+ type: "class",
946
+ loc: declaration.loc ? {
947
+ start: {
948
+ line: declaration.loc.start.line,
949
+ column: declaration.loc.start.column
950
+ },
951
+ end: {
952
+ line: declaration.loc.end.line,
953
+ column: declaration.loc.end.column
999
954
  }
1000
- return config;
1001
- } catch (error) {
1002
- const errorMessage = error instanceof Error ? error.message : String(error);
1003
- const e = new Error(
1004
- `Failed to load config from ${configPath}: ${errorMessage}`
1005
- );
1006
- try {
1007
- e.cause = error instanceof Error ? error : void 0;
1008
- } catch {
955
+ } : void 0
956
+ });
957
+ } else if (declaration.type === "VariableDeclaration") {
958
+ for (const declarator of declaration.declarations) {
959
+ if (declarator.id.type === "Identifier") {
960
+ exports2.push({
961
+ name: declarator.id.name,
962
+ type: "const",
963
+ loc: declarator.loc ? {
964
+ start: {
965
+ line: declarator.loc.start.line,
966
+ column: declarator.loc.start.column
967
+ },
968
+ end: {
969
+ line: declarator.loc.end.line,
970
+ column: declarator.loc.end.column
971
+ }
972
+ } : void 0
973
+ });
974
+ }
975
+ }
976
+ } else if (declaration.type === "TSTypeAliasDeclaration") {
977
+ exports2.push({
978
+ name: declaration.id.name,
979
+ type: "type",
980
+ loc: declaration.loc ? {
981
+ start: {
982
+ line: declaration.loc.start.line,
983
+ column: declaration.loc.start.column
984
+ },
985
+ end: {
986
+ line: declaration.loc.end.line,
987
+ column: declaration.loc.end.column
1009
988
  }
1010
- throw e;
989
+ } : void 0
990
+ });
991
+ } else if (declaration.type === "TSInterfaceDeclaration") {
992
+ exports2.push({
993
+ name: declaration.id.name,
994
+ type: "interface",
995
+ loc: declaration.loc ? {
996
+ start: {
997
+ line: declaration.loc.start.line,
998
+ column: declaration.loc.start.column
999
+ },
1000
+ end: {
1001
+ line: declaration.loc.end.line,
1002
+ column: declaration.loc.end.column
1003
+ }
1004
+ } : void 0
1005
+ });
1006
+ }
1007
+ return exports2;
1008
+ }
1009
+ };
1010
+
1011
+ // src/parsers/python-parser.ts
1012
+ var Parser = __toESM(require("web-tree-sitter"));
1013
+ var path = __toESM(require("path"));
1014
+ var fs = __toESM(require("fs"));
1015
+ var PythonParser = class {
1016
+ constructor() {
1017
+ this.language = "python" /* Python */;
1018
+ this.extensions = [".py"];
1019
+ this.parser = null;
1020
+ this.initialized = false;
1021
+ }
1022
+ /**
1023
+ * Initialize the tree-sitter parser
1024
+ */
1025
+ async initialize() {
1026
+ if (this.initialized) return;
1027
+ try {
1028
+ await Parser.Parser.init();
1029
+ this.parser = new Parser.Parser();
1030
+ const possiblePaths = [
1031
+ path.join(
1032
+ process.cwd(),
1033
+ "node_modules/tree-sitter-python/tree-sitter-python.wasm"
1034
+ ),
1035
+ path.join(
1036
+ __dirname,
1037
+ "../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
1038
+ ),
1039
+ path.join(
1040
+ __dirname,
1041
+ "../../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
1042
+ ),
1043
+ path.join(
1044
+ __dirname,
1045
+ "../../../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
1046
+ ),
1047
+ path.join(__dirname, "../assets/tree-sitter-python.wasm")
1048
+ ];
1049
+ let wasmPath = "";
1050
+ for (const p of possiblePaths) {
1051
+ if (fs.existsSync(p)) {
1052
+ wasmPath = p;
1053
+ break;
1011
1054
  }
1012
1055
  }
1056
+ if (!wasmPath) {
1057
+ console.warn(
1058
+ "Python WASM not found in common locations, attempting fallback regex parser"
1059
+ );
1060
+ return;
1061
+ }
1062
+ const Python = await Parser.Language.load(wasmPath);
1063
+ this.parser.setLanguage(Python);
1064
+ this.initialized = true;
1065
+ } catch (error) {
1066
+ console.error(
1067
+ `Failed to initialize tree-sitter-python: ${error.message}`
1068
+ );
1013
1069
  }
1014
- const parent = (0, import_path3.dirname)(currentDir);
1015
- if (parent === currentDir) {
1016
- break;
1070
+ }
1071
+ parse(code, filePath) {
1072
+ if (!this.initialized || !this.parser) {
1073
+ return this.parseRegex(code, filePath);
1074
+ }
1075
+ try {
1076
+ const tree = this.parser.parse(code);
1077
+ if (!tree) throw new Error("Parser.parse(code) returned null");
1078
+ const rootNode = tree.rootNode;
1079
+ const imports = this.extractImportsAST(rootNode);
1080
+ const exports2 = this.extractExportsAST(rootNode);
1081
+ return {
1082
+ exports: exports2,
1083
+ imports,
1084
+ language: "python" /* Python */,
1085
+ warnings: []
1086
+ };
1087
+ } catch (error) {
1088
+ console.warn(
1089
+ `AST parsing failed for ${filePath}, falling back to regex: ${error.message}`
1090
+ );
1091
+ return this.parseRegex(code, filePath);
1017
1092
  }
1018
- currentDir = parent;
1019
1093
  }
1020
- return null;
1021
- }
1022
- function mergeConfigWithDefaults(userConfig, defaults) {
1023
- if (!userConfig) return defaults;
1024
- const result = { ...defaults };
1025
- if (userConfig.scan) {
1026
- if (userConfig.scan.include) result.include = userConfig.scan.include;
1027
- if (userConfig.scan.exclude) result.exclude = userConfig.scan.exclude;
1094
+ extractImportsAST(rootNode) {
1095
+ const imports = [];
1096
+ const processImportNode = (node) => {
1097
+ if (node.type === "import_statement") {
1098
+ for (const child of node.children) {
1099
+ if (child.type === "dotted_name") {
1100
+ const source = child.text;
1101
+ imports.push({
1102
+ source,
1103
+ specifiers: [source],
1104
+ loc: {
1105
+ start: {
1106
+ line: child.startPosition.row + 1,
1107
+ column: child.startPosition.column
1108
+ },
1109
+ end: {
1110
+ line: child.endPosition.row + 1,
1111
+ column: child.endPosition.column
1112
+ }
1113
+ }
1114
+ });
1115
+ } else if (child.type === "aliased_import") {
1116
+ const nameNode = child.childForFieldName("name");
1117
+ if (nameNode) {
1118
+ const source = nameNode.text;
1119
+ imports.push({
1120
+ source,
1121
+ specifiers: [source],
1122
+ loc: {
1123
+ start: {
1124
+ line: child.startPosition.row + 1,
1125
+ column: child.startPosition.column
1126
+ },
1127
+ end: {
1128
+ line: child.endPosition.row + 1,
1129
+ column: child.endPosition.column
1130
+ }
1131
+ }
1132
+ });
1133
+ }
1134
+ }
1135
+ }
1136
+ } else if (node.type === "import_from_statement") {
1137
+ const moduleNameNode = node.childForFieldName("module_name");
1138
+ if (moduleNameNode) {
1139
+ const source = moduleNameNode.text;
1140
+ const specifiers = [];
1141
+ for (const child of node.children) {
1142
+ if (child.type === "dotted_name" && child !== moduleNameNode) {
1143
+ specifiers.push(child.text);
1144
+ } else if (child.type === "aliased_import") {
1145
+ const nameNode = child.childForFieldName("name");
1146
+ if (nameNode) specifiers.push(nameNode.text);
1147
+ } else if (child.type === "wildcard_import") {
1148
+ specifiers.push("*");
1149
+ }
1150
+ }
1151
+ if (specifiers.length > 0) {
1152
+ imports.push({
1153
+ source,
1154
+ specifiers,
1155
+ loc: {
1156
+ start: {
1157
+ line: node.startPosition.row + 1,
1158
+ column: node.startPosition.column
1159
+ },
1160
+ end: {
1161
+ line: node.endPosition.row + 1,
1162
+ column: node.endPosition.column
1163
+ }
1164
+ }
1165
+ });
1166
+ }
1167
+ }
1168
+ }
1169
+ };
1170
+ for (const node of rootNode.children) {
1171
+ processImportNode(node);
1172
+ }
1173
+ return imports;
1028
1174
  }
1029
- const toolOverrides = userConfig.tools && !Array.isArray(userConfig.tools) && typeof userConfig.tools === "object" ? userConfig.tools : userConfig.toolConfigs;
1030
- if (toolOverrides) {
1031
- if (!result.toolConfigs) result.toolConfigs = {};
1032
- for (const [toolName, toolConfig] of Object.entries(toolOverrides)) {
1033
- if (typeof toolConfig === "object" && toolConfig !== null) {
1034
- result[toolName] = { ...result[toolName], ...toolConfig };
1035
- result.toolConfigs[toolName] = {
1036
- ...result.toolConfigs[toolName],
1037
- ...toolConfig
1038
- };
1175
+ extractExportsAST(rootNode) {
1176
+ const exports2 = [];
1177
+ for (const node of rootNode.children) {
1178
+ if (node.type === "function_definition") {
1179
+ const nameNode = node.childForFieldName("name");
1180
+ if (nameNode) {
1181
+ const name = nameNode.text;
1182
+ const isPrivate = name.startsWith("_") && !name.startsWith("__");
1183
+ if (!isPrivate) {
1184
+ exports2.push({
1185
+ name,
1186
+ type: "function",
1187
+ loc: {
1188
+ start: {
1189
+ line: node.startPosition.row + 1,
1190
+ column: node.startPosition.column
1191
+ },
1192
+ end: {
1193
+ line: node.endPosition.row + 1,
1194
+ column: node.endPosition.column
1195
+ }
1196
+ },
1197
+ parameters: this.extractParameters(node)
1198
+ });
1199
+ }
1200
+ }
1201
+ } else if (node.type === "class_definition") {
1202
+ const nameNode = node.childForFieldName("name");
1203
+ if (nameNode) {
1204
+ exports2.push({
1205
+ name: nameNode.text,
1206
+ type: "class",
1207
+ loc: {
1208
+ start: {
1209
+ line: node.startPosition.row + 1,
1210
+ column: node.startPosition.column
1211
+ },
1212
+ end: {
1213
+ line: node.endPosition.row + 1,
1214
+ column: node.endPosition.column
1215
+ }
1216
+ }
1217
+ });
1218
+ }
1219
+ } else if (node.type === "expression_statement") {
1220
+ const assignment = node.firstChild;
1221
+ if (assignment && assignment.type === "assignment") {
1222
+ const left = assignment.childForFieldName("left");
1223
+ if (left && left.type === "identifier") {
1224
+ const name = left.text;
1225
+ const isInternal = name === "__all__" || name === "__version__" || name === "__author__";
1226
+ const isPrivate = name.startsWith("_") && !name.startsWith("__");
1227
+ if (!isInternal && !isPrivate) {
1228
+ exports2.push({
1229
+ name,
1230
+ type: name === name.toUpperCase() ? "const" : "variable",
1231
+ loc: {
1232
+ start: {
1233
+ line: node.startPosition.row + 1,
1234
+ column: node.startPosition.column
1235
+ },
1236
+ end: {
1237
+ line: node.endPosition.row + 1,
1238
+ column: node.endPosition.column
1239
+ }
1240
+ }
1241
+ });
1242
+ }
1243
+ }
1244
+ }
1039
1245
  }
1040
1246
  }
1247
+ return exports2;
1041
1248
  }
1042
- if (userConfig.output) {
1043
- result.output = { ...result.output, ...userConfig.output };
1249
+ extractParameters(node) {
1250
+ const paramsNode = node.childForFieldName("parameters");
1251
+ if (!paramsNode) return [];
1252
+ return paramsNode.children.filter(
1253
+ (c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter"
1254
+ ).map((c) => {
1255
+ if (c.type === "identifier") return c.text;
1256
+ if (c.type === "typed_parameter" || c.type === "default_parameter") {
1257
+ return c.firstChild?.text || "unknown";
1258
+ }
1259
+ return "unknown";
1260
+ });
1044
1261
  }
1045
- return result;
1046
- }
1047
-
1048
- // src/utils/visualization.ts
1049
- function generateHTML(graph) {
1050
- const payload = JSON.stringify(graph, null, 2);
1051
- return `<!doctype html>
1052
- <html>
1053
- <head>
1054
- <meta charset="utf-8" />
1055
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1056
- <title>AIReady Visualization</title>
1057
- <style>
1058
- html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
1059
- #container { display:flex; height:100vh }
1060
- #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
1061
- #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
1062
- canvas { background: #0b1220; border-radius:8px }
1063
- .stat { margin-bottom:12px }
1064
- </style>
1065
- </head>
1066
- <body>
1067
- <div id="container">
1068
- <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
1069
- <div id="panel">
1070
- <h2>AIReady Visualization</h2>
1071
- <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
1072
- <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
1073
- <div class="stat"><strong>Legend</strong></div>
1074
- <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
1075
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
1076
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
1077
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
1078
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
1079
- <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
1080
- <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
1081
- <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
1082
- </div>
1083
- </div>
1084
- </div>
1085
-
1086
- <script>
1087
- const graphData = ${payload};
1088
- document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
1089
- document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
1090
-
1091
- const canvas = document.getElementById('canvas');
1092
- const ctx = canvas.getContext('2d');
1093
-
1094
- const nodes = graphData.nodes.map((n, i) => ({
1095
- ...n,
1096
- x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
1097
- y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
1098
- }));
1099
-
1100
- function draw() {
1101
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1102
-
1103
- graphData.edges.forEach(edge => {
1104
- const s = nodes.find(n => n.id === edge.source);
1105
- const t = nodes.find(n => n.id === edge.target);
1106
- if (!s || !t) return;
1107
- if (edge.type === 'related') return;
1108
- if (edge.type === 'similarity') {
1109
- ctx.strokeStyle = '#fb7e81';
1110
- ctx.lineWidth = 1.2;
1111
- } else if (edge.type === 'dependency') {
1112
- ctx.strokeStyle = '#84c1ff';
1113
- ctx.lineWidth = 1.0;
1114
- } else if (edge.type === 'reference') {
1115
- ctx.strokeStyle = '#ffa500';
1116
- ctx.lineWidth = 0.9;
1117
- } else {
1118
- ctx.strokeStyle = '#334155';
1119
- ctx.lineWidth = 0.8;
1120
- }
1121
- ctx.beginPath();
1122
- ctx.moveTo(s.x, s.y);
1123
- ctx.lineTo(t.x, t.y);
1124
- ctx.stroke();
1125
- });
1126
-
1127
- const groups = {};
1128
- nodes.forEach(n => {
1129
- const g = n.group || '__default';
1130
- if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
1131
- groups[g].minX = Math.min(groups[g].minX, n.x);
1132
- groups[g].minY = Math.min(groups[g].minY, n.y);
1133
- groups[g].maxX = Math.max(groups[g].maxX, n.x);
1134
- groups[g].maxY = Math.max(groups[g].maxY, n.y);
1135
- });
1136
-
1137
- const groupRelations = {};
1138
- graphData.edges.forEach(edge => {
1139
- const sNode = nodes.find(n => n.id === edge.source);
1140
- const tNode = nodes.find(n => n.id === edge.target);
1141
- if (!sNode || !tNode) return;
1142
- const g1 = sNode.group || '__default';
1143
- const g2 = tNode.group || '__default';
1144
- if (g1 === g2) return;
1145
- const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
1146
- groupRelations[key] = (groupRelations[key] || 0) + 1;
1147
- });
1148
-
1149
- Object.keys(groupRelations).forEach(k => {
1150
- const count = groupRelations[k];
1151
- const [ga, gb] = k.split('::');
1152
- if (!groups[ga] || !groups[gb]) return;
1153
- const ax = (groups[ga].minX + groups[ga].maxX) / 2;
1154
- const ay = (groups[ga].minY + groups[ga].maxY) / 2;
1155
- const bx = (groups[gb].minX + groups[gb].maxX) / 2;
1156
- const by = (groups[gb].minY + groups[gb].maxY) / 2;
1157
- ctx.beginPath();
1158
- ctx.strokeStyle = 'rgba(148,163,184,0.25)';
1159
- ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
1160
- ctx.moveTo(ax, ay);
1161
- ctx.lineTo(bx, by);
1162
- ctx.stroke();
1163
- });
1164
-
1165
- Object.keys(groups).forEach(g => {
1166
- if (g === '__default') return;
1167
- const box = groups[g];
1168
- const pad = 16;
1169
- const x = box.minX - pad;
1170
- const y = box.minY - pad;
1171
- const w = (box.maxX - box.minX) + pad * 2;
1172
- const h = (box.maxY - box.minY) + pad * 2;
1173
- ctx.save();
1174
- ctx.fillStyle = 'rgba(30,64,175,0.04)';
1175
- ctx.strokeStyle = 'rgba(30,64,175,0.12)';
1176
- ctx.lineWidth = 1.2;
1177
- const r = 8;
1178
- ctx.beginPath();
1179
- ctx.moveTo(x + r, y);
1180
- ctx.arcTo(x + w, y, x + w, y + h, r);
1181
- ctx.arcTo(x + w, y + h, x, y + h, r);
1182
- ctx.arcTo(x, y + h, x, y, r);
1183
- ctx.arcTo(x, y, x + w, y, r);
1184
- ctx.closePath();
1185
- ctx.fill();
1186
- ctx.stroke();
1187
- ctx.restore();
1188
- ctx.fillStyle = '#94a3b8';
1189
- ctx.font = '11px sans-serif';
1190
- ctx.fillText(g, x + 8, y + 14);
1191
- });
1192
-
1193
- nodes.forEach(n => {
1194
- const sizeVal = (n.size || n.value || 1);
1195
- const r = 6 + (sizeVal / 2);
1196
- ctx.beginPath();
1197
- ctx.fillStyle = n.color || '#60a5fa';
1198
- ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
1199
- ctx.fill();
1200
-
1201
- ctx.fillStyle = '#e2e8f0';
1202
- ctx.font = '11px sans-serif';
1203
- ctx.textAlign = 'center';
1204
- ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
1205
- });
1206
- }
1207
-
1208
- draw();
1209
- </script>
1210
- </body>
1211
- </html>`;
1212
- }
1213
-
1214
- // src/scoring.ts
1215
- var DEFAULT_TOOL_WEIGHTS = {
1216
- ["pattern-detect" /* PatternDetect */]: 22,
1217
- ["context-analyzer" /* ContextAnalyzer */]: 19,
1218
- ["naming-consistency" /* NamingConsistency */]: 14,
1219
- ["ai-signal-clarity" /* AiSignalClarity */]: 11,
1220
- ["agent-grounding" /* AgentGrounding */]: 10,
1221
- ["testability-index" /* TestabilityIndex */]: 10,
1222
- ["doc-drift" /* DocDrift */]: 8,
1223
- ["dependency-health" /* DependencyHealth */]: 6,
1224
- ["change-amplification" /* ChangeAmplification */]: 8
1225
- };
1226
- var TOOL_NAME_MAP = {
1227
- patterns: "pattern-detect" /* PatternDetect */,
1228
- "pattern-detect": "pattern-detect" /* PatternDetect */,
1229
- context: "context-analyzer" /* ContextAnalyzer */,
1230
- "context-analyzer": "context-analyzer" /* ContextAnalyzer */,
1231
- consistency: "naming-consistency" /* NamingConsistency */,
1232
- "naming-consistency": "naming-consistency" /* NamingConsistency */,
1233
- "ai-signal": "ai-signal-clarity" /* AiSignalClarity */,
1234
- "ai-signal-clarity": "ai-signal-clarity" /* AiSignalClarity */,
1235
- grounding: "agent-grounding" /* AgentGrounding */,
1236
- "agent-grounding": "agent-grounding" /* AgentGrounding */,
1237
- testability: "testability-index" /* TestabilityIndex */,
1238
- "testability-index": "testability-index" /* TestabilityIndex */,
1239
- "doc-drift": "doc-drift" /* DocDrift */,
1240
- "deps-health": "dependency-health" /* DependencyHealth */,
1241
- "dependency-health": "dependency-health" /* DependencyHealth */,
1242
- "change-amp": "change-amplification" /* ChangeAmplification */,
1243
- "change-amplification": "change-amplification" /* ChangeAmplification */
1244
- };
1245
- var CONTEXT_TIER_THRESHOLDS = {
1246
- compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
1247
- standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
1248
- extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
1249
- frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
1250
- };
1251
- var SIZE_ADJUSTED_THRESHOLDS = {
1252
- xs: 80,
1253
- // < 50 files
1254
- small: 75,
1255
- // 50-200 files
1256
- medium: 70,
1257
- // 200-500 files
1258
- large: 65,
1259
- // 500-2000 files
1260
- enterprise: 58
1261
- // 2000+ files
1262
- };
1263
- function getProjectSizeTier(fileCount) {
1264
- if (fileCount < 50) return "xs";
1265
- if (fileCount < 200) return "small";
1266
- if (fileCount < 500) return "medium";
1267
- if (fileCount < 2e3) return "large";
1268
- return "enterprise";
1269
- }
1270
- function getRecommendedThreshold(fileCount, modelTier = "standard") {
1271
- const sizeTier = getProjectSizeTier(fileCount);
1272
- const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
1273
- const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
1274
- return base + modelBonus;
1275
- }
1276
- function normalizeToolName(shortName) {
1277
- return TOOL_NAME_MAP[shortName.toLowerCase()] || shortName;
1278
- }
1279
- function getToolWeight(toolName, toolConfig, cliOverride) {
1280
- if (cliOverride !== void 0) return cliOverride;
1281
- if (toolConfig?.scoreWeight !== void 0) return toolConfig.scoreWeight;
1282
- return DEFAULT_TOOL_WEIGHTS[toolName] || 5;
1283
- }
1284
- function parseWeightString(weightStr) {
1285
- const weights = /* @__PURE__ */ new Map();
1286
- if (!weightStr) return weights;
1287
- const pairs = weightStr.split(",");
1288
- for (const pair of pairs) {
1289
- const [toolShortName, weightStr2] = pair.split(":");
1290
- if (toolShortName && weightStr2) {
1291
- const toolName = normalizeToolName(toolShortName.trim());
1292
- const weight = parseInt(weightStr2.trim(), 10);
1293
- if (!isNaN(weight) && weight > 0) {
1294
- weights.set(toolName, weight);
1295
- }
1296
- }
1297
- }
1298
- return weights;
1299
- }
1300
- function calculateOverallScore(toolOutputs, config, cliWeights) {
1301
- if (toolOutputs.size === 0) {
1302
- throw new Error("No tool outputs provided for scoring");
1303
- }
1304
- const weights = /* @__PURE__ */ new Map();
1305
- for (const [toolName] of toolOutputs.entries()) {
1306
- const cliWeight = cliWeights?.get(toolName);
1307
- const configWeight = config?.tools?.[toolName]?.scoreWeight;
1308
- const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
1309
- weights.set(toolName, weight);
1310
- }
1311
- let weightedSum = 0;
1312
- let totalWeight = 0;
1313
- const breakdown = [];
1314
- const toolsUsed = [];
1315
- const calculationWeights = {};
1316
- for (const [toolName, output] of toolOutputs.entries()) {
1317
- const weight = weights.get(toolName) || 5;
1318
- weightedSum += output.score * weight;
1319
- totalWeight += weight;
1320
- toolsUsed.push(toolName);
1321
- calculationWeights[toolName] = weight;
1322
- breakdown.push(output);
1323
- }
1324
- const overall = Math.round(weightedSum / totalWeight);
1325
- const rating = getRating(overall);
1326
- const formulaParts = Array.from(toolOutputs.entries()).map(
1327
- ([name, output]) => {
1328
- const w = weights.get(name) || 5;
1329
- return `(${output.score} \xD7 ${w})`;
1330
- }
1331
- );
1332
- const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
1333
- return {
1334
- overall,
1335
- rating,
1336
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1337
- toolsUsed,
1338
- breakdown,
1339
- calculation: {
1340
- formula: formulaStr,
1341
- weights: calculationWeights,
1342
- normalized: formulaStr
1343
- }
1344
- };
1345
- }
1346
- function getRating(score) {
1347
- if (score >= 90) return "Excellent";
1348
- if (score >= 75) return "Good";
1349
- if (score >= 60) return "Fair";
1350
- if (score >= 40) return "Needs Work";
1351
- return "Critical";
1352
- }
1353
- function getRatingWithContext(score, fileCount, modelTier = "standard") {
1354
- const threshold = getRecommendedThreshold(fileCount, modelTier);
1355
- const normalized = score - threshold + 70;
1356
- return getRating(normalized);
1357
- }
1358
- function getRatingDisplay(rating) {
1359
- switch (rating) {
1360
- case "Excellent":
1361
- return { emoji: "\u2705", color: "green" };
1362
- case "Good":
1363
- return { emoji: "\u{1F44D}", color: "blue" };
1364
- case "Fair":
1365
- return { emoji: "\u26A0\uFE0F", color: "yellow" };
1366
- case "Needs Work":
1367
- return { emoji: "\u{1F528}", color: "orange" };
1368
- case "Critical":
1369
- return { emoji: "\u274C", color: "red" };
1370
- }
1371
- }
1372
- function formatScore(result) {
1373
- const { emoji } = getRatingDisplay(result.rating);
1374
- return `${result.overall}/100 (${result.rating}) ${emoji}`;
1375
- }
1376
- function formatToolScore(output) {
1377
- let result = ` Score: ${output.score}/100
1378
-
1379
- `;
1380
- if (output.factors && output.factors.length > 0) {
1381
- result += ` Factors:
1382
- `;
1383
- output.factors.forEach((factor) => {
1384
- const impactSign = factor.impact > 0 ? "+" : "";
1385
- result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
1386
- `;
1387
- });
1388
- result += "\n";
1389
- }
1390
- if (output.recommendations && output.recommendations.length > 0) {
1391
- result += ` Recommendations:
1392
- `;
1393
- output.recommendations.forEach((rec, i) => {
1394
- const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
1395
- result += ` ${i + 1}. ${priorityIcon} ${rec.action}
1396
- `;
1397
- result += ` Impact: +${rec.estimatedImpact} points
1398
-
1399
- `;
1400
- });
1401
- }
1402
- return result;
1403
- }
1404
-
1405
- // src/business/pricing-models.ts
1406
- var MODEL_PRICING_PRESETS = {
1407
- "gpt-5.3": {
1408
- name: "GPT-5.3",
1409
- pricePer1KInputTokens: 2e-3,
1410
- pricePer1KOutputTokens: 8e-3,
1411
- contextTier: "frontier",
1412
- typicalQueriesPerDevPerDay: 100
1413
- },
1414
- "claude-4.6": {
1415
- name: "Claude 4.6",
1416
- pricePer1KInputTokens: 15e-4,
1417
- pricePer1KOutputTokens: 75e-4,
1418
- contextTier: "frontier",
1419
- typicalQueriesPerDevPerDay: 100
1420
- },
1421
- "gemini-3.1": {
1422
- name: "Gemini 3.1 Pro",
1423
- pricePer1KInputTokens: 8e-4,
1424
- pricePer1KOutputTokens: 3e-3,
1425
- contextTier: "frontier",
1426
- typicalQueriesPerDevPerDay: 120
1427
- },
1428
- "gpt-4o": {
1429
- name: "GPT-4o (legacy)",
1430
- pricePer1KInputTokens: 5e-3,
1431
- pricePer1KOutputTokens: 0.015,
1432
- contextTier: "extended",
1433
- typicalQueriesPerDevPerDay: 60
1434
- },
1435
- "claude-3-5-sonnet": {
1436
- name: "Claude 3.5 Sonnet (legacy)",
1437
- pricePer1KInputTokens: 3e-3,
1438
- pricePer1KOutputTokens: 0.015,
1439
- contextTier: "extended",
1440
- typicalQueriesPerDevPerDay: 80
1441
- },
1442
- "gemini-1-5-pro": {
1443
- name: "Gemini 1.5 Pro (legacy)",
1444
- pricePer1KInputTokens: 125e-5,
1445
- pricePer1KOutputTokens: 5e-3,
1446
- contextTier: "frontier",
1447
- typicalQueriesPerDevPerDay: 80
1448
- },
1449
- copilot: {
1450
- name: "GitHub Copilot (subscription)",
1451
- pricePer1KInputTokens: 8e-5,
1452
- pricePer1KOutputTokens: 8e-5,
1453
- contextTier: "frontier",
1454
- typicalQueriesPerDevPerDay: 150
1455
- },
1456
- "cursor-pro": {
1457
- name: "Cursor Pro (subscription)",
1458
- pricePer1KInputTokens: 8e-5,
1459
- pricePer1KOutputTokens: 8e-5,
1460
- contextTier: "frontier",
1461
- typicalQueriesPerDevPerDay: 200
1462
- }
1463
- };
1464
- function getModelPreset(modelId) {
1465
- return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
1466
- }
1467
-
1468
- // src/business/cost-metrics.ts
1469
- var DEFAULT_COST_CONFIG = {
1470
- pricePer1KTokens: 5e-3,
1471
- queriesPerDevPerDay: 60,
1472
- developerCount: 5,
1473
- daysPerMonth: 30
1474
- };
1475
- function calculateMonthlyCost(tokenWaste, config = {}) {
1476
- const budget = calculateTokenBudget({
1477
- totalContextTokens: tokenWaste * 2.5,
1478
- wastedTokens: {
1479
- duplication: tokenWaste * 0.7,
1480
- fragmentation: tokenWaste * 0.3,
1481
- chattiness: 0
1482
- }
1483
- });
1484
- const preset = getModelPreset("claude-4.6");
1485
- return estimateCostFromBudget(budget, preset, config);
1486
- }
1487
- function calculateTokenBudget(params) {
1488
- const { totalContextTokens, wastedTokens } = params;
1489
- const estimatedResponseTokens = params.estimatedResponseTokens ?? totalContextTokens * 0.2;
1490
- const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
1491
- const efficiencyRatio = Math.max(
1492
- 0,
1493
- Math.min(
1494
- 1,
1495
- (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
1496
- )
1497
- );
1498
- return {
1499
- totalContextTokens: Math.round(totalContextTokens),
1500
- estimatedResponseTokens: Math.round(estimatedResponseTokens),
1501
- wastedTokens: {
1502
- total: Math.round(totalWaste),
1503
- bySource: {
1504
- duplication: Math.round(wastedTokens.duplication),
1505
- fragmentation: Math.round(wastedTokens.fragmentation),
1506
- chattiness: Math.round(wastedTokens.chattiness)
1507
- }
1508
- },
1509
- efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
1510
- potentialRetrievableTokens: Math.round(totalWaste * 0.8)
1511
- };
1512
- }
1513
- function estimateCostFromBudget(budget, model, config = {}) {
1514
- const cfg = { ...DEFAULT_COST_CONFIG, ...config };
1515
- const wastePerQuery = budget.wastedTokens.total;
1516
- const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
1517
- const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
1518
- const totalWeight = cfg.developerCount;
1519
- const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
1520
- const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
1521
- let confidence = 0.85;
1522
- if (model.contextTier === "frontier") confidence = 0.7;
1523
- const variance = 0.25;
1524
- const range = [
1525
- Math.round(baseCost * (1 - variance) * 100) / 100,
1526
- Math.round(baseCost * (1 + variance) * 100) / 100
1527
- ];
1528
- return {
1529
- total: Math.round(baseCost * 100) / 100,
1530
- range,
1531
- confidence
1532
- };
1533
- }
1534
-
1535
- // src/business/productivity-metrics.ts
1536
- var SEVERITY_TIME_ESTIMATES = {
1537
- ["critical" /* Critical */]: 4,
1538
- ["major" /* Major */]: 2,
1539
- ["minor" /* Minor */]: 0.5,
1540
- ["info" /* Info */]: 0.25
1541
- };
1542
- var DEFAULT_HOURLY_RATE = 75;
1543
- function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
1544
- const counts = {
1545
- ["critical" /* Critical */]: issues.filter((i) => i.severity === "critical" /* Critical */).length,
1546
- ["major" /* Major */]: issues.filter((i) => i.severity === "major" /* Major */).length,
1547
- ["minor" /* Minor */]: issues.filter((i) => i.severity === "minor" /* Minor */).length,
1548
- ["info" /* Info */]: issues.filter((i) => i.severity === "info" /* Info */).length
1549
- };
1550
- const hours = {
1551
- ["critical" /* Critical */]: counts["critical" /* Critical */] * SEVERITY_TIME_ESTIMATES["critical" /* Critical */],
1552
- ["major" /* Major */]: counts["major" /* Major */] * SEVERITY_TIME_ESTIMATES["major" /* Major */],
1553
- ["minor" /* Minor */]: counts["minor" /* Minor */] * SEVERITY_TIME_ESTIMATES["minor" /* Minor */],
1554
- ["info" /* Info */]: counts["info" /* Info */] * SEVERITY_TIME_ESTIMATES["info" /* Info */]
1555
- };
1556
- const totalHours = hours["critical" /* Critical */] + hours["major" /* Major */] + hours["minor" /* Minor */] + hours["info" /* Info */];
1557
- const totalCost = totalHours * hourlyRate;
1558
- return {
1559
- totalHours: Math.round(totalHours * 10) / 10,
1560
- hourlyRate,
1561
- totalCost: Math.round(totalCost),
1562
- bySeverity: {
1563
- ["critical" /* Critical */]: {
1564
- hours: Math.round(hours["critical" /* Critical */] * 10) / 10,
1565
- cost: Math.round(hours["critical" /* Critical */] * hourlyRate)
1566
- },
1567
- ["major" /* Major */]: {
1568
- hours: Math.round(hours["major" /* Major */] * 10) / 10,
1569
- cost: Math.round(hours["major" /* Major */] * hourlyRate)
1570
- },
1571
- ["minor" /* Minor */]: {
1572
- hours: Math.round(hours["minor" /* Minor */] * 10) / 10,
1573
- cost: Math.round(hours["minor" /* Minor */] * hourlyRate)
1574
- },
1575
- ["info" /* Info */]: {
1576
- hours: Math.round(hours["info" /* Info */] * 10) / 10,
1577
- cost: Math.round(hours["info" /* Info */] * hourlyRate)
1578
- }
1262
+ parseRegex(code, filePath) {
1263
+ try {
1264
+ const imports = this.extractImportsRegex(code, filePath);
1265
+ const exports2 = this.extractExportsRegex(code, filePath);
1266
+ return {
1267
+ exports: exports2,
1268
+ imports,
1269
+ language: "python" /* Python */,
1270
+ warnings: [
1271
+ "Python parsing is currently using regex-based extraction as tree-sitter wasm was not available."
1272
+ ]
1273
+ };
1274
+ } catch (error) {
1275
+ throw new ParseError(
1276
+ `Failed to parse Python file ${filePath}: ${error.message}`,
1277
+ filePath
1278
+ );
1579
1279
  }
1580
- };
1581
- }
1582
- function predictAcceptanceRate(toolOutputs) {
1583
- const factors = [];
1584
- const baseRate = 0.3;
1585
- const patterns = toolOutputs.get("pattern-detect");
1586
- if (patterns) {
1587
- factors.push({
1588
- name: "Semantic Duplication",
1589
- impact: Math.round((patterns.score - 50) * 3e-3 * 100)
1590
- });
1591
1280
  }
1592
- const context = toolOutputs.get("context-analyzer");
1593
- if (context) {
1594
- factors.push({
1595
- name: "Context Efficiency",
1596
- impact: Math.round((context.score - 50) * 4e-3 * 100)
1597
- });
1281
+ getNamingConventions() {
1282
+ return {
1283
+ variablePattern: /^[a-z_][a-z0-9_]*$/,
1284
+ functionPattern: /^[a-z_][a-z0-9_]*$/,
1285
+ classPattern: /^[A-Z][a-zA-Z0-9]*$/,
1286
+ constantPattern: /^[A-Z][A-Z0-9_]*$/,
1287
+ exceptions: [
1288
+ "__init__",
1289
+ "__str__",
1290
+ "__repr__",
1291
+ "__name__",
1292
+ "__main__",
1293
+ "__file__",
1294
+ "__doc__",
1295
+ "__all__",
1296
+ "__version__",
1297
+ "__author__",
1298
+ "__dict__",
1299
+ "__class__",
1300
+ "__module__",
1301
+ "__bases__"
1302
+ ]
1303
+ };
1598
1304
  }
1599
- const consistency = toolOutputs.get("consistency");
1600
- if (consistency) {
1601
- factors.push({
1602
- name: "Code Consistency",
1603
- impact: Math.round((consistency.score - 50) * 2e-3 * 100)
1305
+ canHandle(filePath) {
1306
+ return filePath.toLowerCase().endsWith(".py");
1307
+ }
1308
+ extractImportsRegex(code, _filePath) {
1309
+ const imports = [];
1310
+ const lines = code.split("\n");
1311
+ const importRegex = /^\s*import\s+([a-zA-Z0-9_., ]+)/;
1312
+ const fromImportRegex = /^\s*from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/;
1313
+ lines.forEach((line, idx) => {
1314
+ if (line.trim().startsWith("#")) return;
1315
+ const importMatch = line.match(importRegex);
1316
+ if (importMatch) {
1317
+ const modules = importMatch[1].split(",").map((m) => m.trim().split(" as ")[0]);
1318
+ modules.forEach((module2) => {
1319
+ imports.push({
1320
+ source: module2,
1321
+ specifiers: [module2],
1322
+ loc: {
1323
+ start: { line: idx + 1, column: 0 },
1324
+ end: { line: idx + 1, column: line.length }
1325
+ }
1326
+ });
1327
+ });
1328
+ return;
1329
+ }
1330
+ const fromMatch = line.match(fromImportRegex);
1331
+ if (fromMatch) {
1332
+ const module2 = fromMatch[1];
1333
+ const imports_str = fromMatch[2];
1334
+ if (imports_str.trim() === "*") {
1335
+ imports.push({
1336
+ source: module2,
1337
+ specifiers: ["*"],
1338
+ loc: {
1339
+ start: { line: idx + 1, column: 0 },
1340
+ end: { line: idx + 1, column: line.length }
1341
+ }
1342
+ });
1343
+ return;
1344
+ }
1345
+ const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
1346
+ imports.push({
1347
+ source: module2,
1348
+ specifiers,
1349
+ loc: {
1350
+ start: { line: idx + 1, column: 0 },
1351
+ end: { line: idx + 1, column: line.length }
1352
+ }
1353
+ });
1354
+ }
1604
1355
  });
1356
+ return imports;
1605
1357
  }
1606
- const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
1607
- if (aiSignalClarity) {
1608
- factors.push({
1609
- name: "AI Signal Clarity",
1610
- impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
1358
+ extractExportsRegex(code, _filePath) {
1359
+ const exports2 = [];
1360
+ const lines = code.split("\n");
1361
+ const functionRegex = /^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/;
1362
+ const classRegex = /^class\s+([a-zA-Z_][a-zA-Z0-9_]*)/;
1363
+ const allRegex = /__all__\s*=\s*\[([^\]]+)\]/;
1364
+ let inClass = false;
1365
+ let classIndent = 0;
1366
+ lines.forEach((line, idx) => {
1367
+ const indent = line.search(/\S/);
1368
+ if (line.match(classRegex)) {
1369
+ inClass = true;
1370
+ classIndent = indent;
1371
+ } else if (inClass && indent <= classIndent && line.trim()) {
1372
+ inClass = false;
1373
+ }
1374
+ if (inClass) {
1375
+ const classMatch = line.match(classRegex);
1376
+ if (classMatch) {
1377
+ exports2.push({
1378
+ name: classMatch[1],
1379
+ type: "class",
1380
+ loc: {
1381
+ start: { line: idx + 1, column: indent },
1382
+ end: { line: idx + 1, column: line.length }
1383
+ }
1384
+ });
1385
+ }
1386
+ return;
1387
+ }
1388
+ const funcMatch = line.match(functionRegex);
1389
+ if (funcMatch && indent === 0) {
1390
+ const name = funcMatch[1];
1391
+ if (!name.startsWith("_") || name.startsWith("__")) {
1392
+ exports2.push({
1393
+ name,
1394
+ type: "function",
1395
+ loc: {
1396
+ start: { line: idx + 1, column: 0 },
1397
+ end: { line: idx + 1, column: line.length }
1398
+ }
1399
+ });
1400
+ }
1401
+ }
1402
+ const allMatch = line.match(allRegex);
1403
+ if (allMatch) {
1404
+ const names = allMatch[1].split(",").map((n) => n.trim().replace(/['"]/g, ""));
1405
+ names.forEach((name) => {
1406
+ if (name && !exports2.find((e) => e.name === name)) {
1407
+ exports2.push({
1408
+ name,
1409
+ type: "variable",
1410
+ loc: {
1411
+ start: { line: idx + 1, column: 0 },
1412
+ end: { line: idx + 1, column: line.length }
1413
+ }
1414
+ });
1415
+ }
1416
+ });
1417
+ }
1611
1418
  });
1419
+ return exports2;
1612
1420
  }
1613
- const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
1614
- const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
1615
- let confidence = 0.35;
1616
- if (toolOutputs.size >= 4) confidence = 0.75;
1617
- else if (toolOutputs.size >= 3) confidence = 0.65;
1618
- else if (toolOutputs.size >= 2) confidence = 0.5;
1619
- return { rate: Math.round(rate * 100) / 100, confidence, factors };
1620
- }
1421
+ };
1621
1422
 
1622
- // src/business/risk-metrics.ts
1623
- function calculateKnowledgeConcentration(params) {
1624
- const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
1625
- const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
1626
- const score = Math.round(
1627
- Math.min(
1628
- 100,
1629
- concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
1630
- )
1631
- );
1632
- let rating;
1633
- if (score < 30) rating = "low";
1634
- else if (score < 50) rating = "moderate";
1635
- else if (score < 75) rating = "high";
1636
- else rating = "critical";
1637
- const recommendations = [];
1638
- if (singleAuthorFiles > 0)
1639
- recommendations.push(
1640
- `Distribute knowledge for ${singleAuthorFiles} single-author files.`
1641
- );
1642
- if (orphanFiles > 0)
1643
- recommendations.push(
1644
- `Link ${orphanFiles} orphan files to the rest of the codebase.`
1423
+ // src/parsers/parser-factory.ts
1424
+ var ParserFactory = class _ParserFactory {
1425
+ constructor() {
1426
+ this.parsers = /* @__PURE__ */ new Map();
1427
+ this.extensionMap = new Map(
1428
+ Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
1645
1429
  );
1646
- return {
1647
- score,
1648
- rating,
1649
- recommendations,
1650
- analysis: {
1651
- uniqueConceptFiles,
1652
- totalFiles,
1653
- concentrationRatio,
1654
- singleAuthorFiles,
1655
- orphanFiles
1656
- }
1657
- };
1658
- }
1659
- function calculateDebtInterest(principal, monthlyGrowthRate) {
1660
- const monthlyRate = monthlyGrowthRate;
1661
- const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
1662
- const monthlyCost = principal * monthlyRate;
1663
- return {
1664
- monthlyRate,
1665
- annualRate,
1666
- principal,
1667
- monthlyCost,
1668
- projections: {
1669
- months6: principal * Math.pow(1 + monthlyRate, 6),
1670
- months12: principal * Math.pow(1 + monthlyRate, 12),
1671
- months24: principal * Math.pow(1 + monthlyRate, 24)
1672
- }
1673
- };
1674
- }
1675
-
1676
- // src/business/comprehension-metrics.ts
1677
- function calculateTechnicalValueChain(params) {
1678
- const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
1679
- const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
1680
- return {
1681
- score: Math.round(Math.max(0, Math.min(100, score))),
1682
- density: businessLogicDensity,
1683
- complexity: dataAccessComplexity,
1684
- surface: apiSurfaceArea
1685
- };
1686
- }
1687
- function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
1688
- const tierMap = {
1689
- compact: "compact",
1690
- standard: "standard",
1691
- extended: "extended",
1692
- frontier: "frontier",
1693
- easy: "frontier",
1694
- // Map legacy 'easy' to 'frontier'
1695
- moderate: "standard",
1696
- difficult: "compact"
1697
- };
1698
- const tier = tierMap[modelTier] || "frontier";
1699
- const threshold = CONTEXT_TIER_THRESHOLDS[tier];
1700
- const budgetRatio = contextBudget / threshold.idealTokens;
1701
- const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
1702
- const finalScore = Math.round(Math.max(0, Math.min(100, score)));
1703
- let rating;
1704
- if (finalScore < 20) rating = "trivial";
1705
- else if (finalScore < 40) rating = "easy";
1706
- else if (finalScore < 60) rating = "moderate";
1707
- else if (finalScore < 85) rating = "difficult";
1708
- else rating = "expert";
1709
- return {
1710
- score: finalScore,
1711
- rating,
1712
- factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
1713
- };
1714
- }
1715
-
1716
- // src/business-metrics.ts
1717
- function calculateBusinessROI(params) {
1718
- const model = getModelPreset(params.modelId || "claude-4.6");
1719
- const devCount = params.developerCount || 5;
1720
- const budget = calculateTokenBudget({
1721
- totalContextTokens: params.tokenWaste * 2.5,
1722
- wastedTokens: {
1723
- duplication: params.tokenWaste * 0.7,
1724
- fragmentation: params.tokenWaste * 0.3,
1725
- chattiness: 0
1430
+ this.registerParser(new TypeScriptParser());
1431
+ this.registerParser(new PythonParser());
1432
+ }
1433
+ /**
1434
+ * Get singleton instance
1435
+ */
1436
+ static getInstance() {
1437
+ if (!_ParserFactory.instance) {
1438
+ _ParserFactory.instance = new _ParserFactory();
1726
1439
  }
1727
- });
1728
- const cost = estimateCostFromBudget(budget, model, {
1729
- developerCount: devCount
1730
- });
1731
- const productivity = calculateProductivityImpact(params.issues);
1732
- const monthlySavings = cost.total;
1733
- const productivityGainHours = productivity.totalHours;
1734
- const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
1735
- return {
1736
- monthlySavings: Math.round(monthlySavings),
1737
- productivityGainHours: Math.round(productivityGainHours),
1738
- annualValue: Math.round(annualValue)
1739
- };
1740
- }
1741
- function formatCost(cost) {
1742
- if (cost < 1) {
1743
- return `$${cost.toFixed(2)}`;
1744
- } else if (cost < 1e3) {
1745
- return `$${cost.toFixed(0)}`;
1746
- } else {
1747
- return `$${(cost / 1e3).toFixed(1)}k`;
1440
+ return _ParserFactory.instance;
1441
+ }
1442
+ /**
1443
+ * Register a language parser
1444
+ */
1445
+ registerParser(parser) {
1446
+ this.parsers.set(parser.language, parser);
1447
+ parser.extensions.forEach((ext) => {
1448
+ const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
1449
+ this.extensionMap.set(ext, lang);
1450
+ this.parsers.set(lang, parser);
1451
+ });
1452
+ }
1453
+ /**
1454
+ * Get parser for a specific language
1455
+ */
1456
+ getParserForLanguage(language) {
1457
+ return this.parsers.get(language) || null;
1458
+ }
1459
+ /**
1460
+ * Get parser for a file based on its extension
1461
+ */
1462
+ getParserForFile(filePath) {
1463
+ const ext = this.getFileExtension(filePath);
1464
+ const language = this.extensionMap.get(ext);
1465
+ if (!language) {
1466
+ return null;
1467
+ }
1468
+ return this.parsers.get(language) || null;
1748
1469
  }
1749
- }
1750
- function formatHours(hours) {
1751
- if (hours < 1) {
1752
- return `${Math.round(hours * 60)}min`;
1753
- } else if (hours < 8) {
1754
- return `${hours.toFixed(1)}h`;
1755
- } else if (hours < 40) {
1756
- return `${Math.round(hours)}h`;
1757
- } else {
1758
- return `${(hours / 40).toFixed(1)} weeks`;
1470
+ /**
1471
+ * Check if a file is supported
1472
+ */
1473
+ isSupported(filePath) {
1474
+ return this.getParserForFile(filePath) !== null;
1475
+ }
1476
+ /**
1477
+ * Get all registered languages
1478
+ */
1479
+ getSupportedLanguages() {
1480
+ return Array.from(this.parsers.keys());
1481
+ }
1482
+ /**
1483
+ * Get all supported file extensions
1484
+ */
1485
+ getSupportedExtensions() {
1486
+ return Array.from(this.extensionMap.keys());
1487
+ }
1488
+ /**
1489
+ * Get language for a file
1490
+ */
1491
+ getLanguageForFile(filePath) {
1492
+ const ext = this.getFileExtension(filePath);
1493
+ return this.extensionMap.get(ext) || null;
1494
+ }
1495
+ /**
1496
+ * Extract file extension (with dot)
1497
+ */
1498
+ getFileExtension(filePath) {
1499
+ const match = filePath.match(/\.[^.]+$/);
1500
+ return match ? match[0].toLowerCase() : "";
1501
+ }
1502
+ /**
1503
+ * Reset factory (useful for testing)
1504
+ */
1505
+ static reset() {
1506
+ _ParserFactory.instance = new _ParserFactory();
1507
+ }
1508
+ /**
1509
+ * Initialize all registered parsers
1510
+ */
1511
+ async initializeAll() {
1512
+ const promises = Array.from(this.parsers.values()).map(
1513
+ (p) => p.initialize()
1514
+ );
1515
+ await Promise.all(promises);
1759
1516
  }
1517
+ };
1518
+ function getParser(filePath) {
1519
+ return ParserFactory.getInstance().getParserForFile(filePath);
1760
1520
  }
1761
- function formatAcceptanceRate(rate) {
1762
- return `${Math.round(rate * 100)}%`;
1521
+ async function initializeParsers() {
1522
+ await ParserFactory.getInstance().initializeAll();
1763
1523
  }
1764
- function generateValueChain(params) {
1765
- const { issueType, count, severity } = params;
1766
- const impacts = {
1767
- "duplicate-pattern": {
1768
- ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
1769
- dev: "Developers must manually resolve conflicts between suggested variants.",
1770
- risk: "high"
1771
- },
1772
- "context-fragmentation": {
1773
- ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
1774
- dev: "Slower AI responses and increased need for manual context pinning.",
1775
- risk: "critical"
1776
- },
1777
- "naming-inconsistency": {
1778
- ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
1779
- dev: "Increased cognitive load for new devs during onboarding.",
1780
- risk: "moderate"
1781
- }
1782
- };
1783
- const impact = impacts[issueType] || {
1784
- ai: "Reduced suggestion quality.",
1785
- dev: "Slowed development velocity.",
1786
- risk: "moderate"
1787
- };
1788
- const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
1789
- return {
1790
- issueType,
1791
- technicalMetric: "Issue Count",
1792
- technicalValue: count,
1793
- aiImpact: {
1794
- description: impact.ai,
1795
- scoreImpact: severity === "critical" ? -15 : -5
1796
- },
1797
- developerImpact: {
1798
- description: impact.dev,
1799
- productivityLoss
1800
- },
1801
- businessOutcome: {
1802
- directCost: count * 12,
1803
- opportunityCost: productivityLoss * 15e3,
1804
- riskLevel: impact.risk
1805
- }
1806
- };
1524
+ function isFileSupported(filePath) {
1525
+ return ParserFactory.getInstance().isSupported(filePath);
1526
+ }
1527
+ function getSupportedLanguages() {
1528
+ return ParserFactory.getInstance().getSupportedLanguages();
1807
1529
  }
1808
1530
 
1809
- // src/parsers/typescript-parser.ts
1810
- var import_typescript_estree2 = require("@typescript-eslint/typescript-estree");
1811
- var TypeScriptParser = class {
1812
- constructor() {
1813
- this.language = "typescript" /* TypeScript */;
1814
- this.extensions = [".ts", ".tsx", ".js", ".jsx"];
1815
- }
1816
- parse(code, filePath) {
1531
+ // src/utils/ast-parser.ts
1532
+ function parseFileExports(code, filePath) {
1533
+ const parser = getParser(filePath);
1534
+ if (parser && parser.language === "python" /* Python */) {
1817
1535
  try {
1818
- const isJavaScript = filePath.match(/\.jsx?$/i);
1819
- const ast = (0, import_typescript_estree2.parse)(code, {
1820
- loc: true,
1821
- range: true,
1822
- jsx: filePath.match(/\.[jt]sx$/i) !== null,
1823
- filePath,
1824
- sourceType: "module",
1825
- ecmaVersion: "latest"
1826
- });
1827
- const imports = this.extractImports(ast);
1828
- const exports2 = this.extractExports(ast, imports);
1536
+ const result = parser.parse(code, filePath);
1829
1537
  return {
1830
- exports: exports2,
1831
- imports,
1832
- language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
1833
- warnings: []
1538
+ exports: result.exports.map((e) => ({
1539
+ name: e.name,
1540
+ type: e.type,
1541
+ imports: e.imports || [],
1542
+ dependencies: e.dependencies || [],
1543
+ typeReferences: e.typeReferences || [],
1544
+ loc: e.loc ? {
1545
+ start: { line: e.loc.start.line, column: e.loc.start.column },
1546
+ end: { line: e.loc.end.line, column: e.loc.end.column }
1547
+ } : void 0
1548
+ })),
1549
+ imports: result.imports.map((i) => ({
1550
+ source: i.source,
1551
+ specifiers: i.specifiers,
1552
+ isTypeOnly: i.isTypeOnly || false
1553
+ }))
1834
1554
  };
1835
- } catch (error) {
1836
- const err = error;
1837
- throw new ParseError(
1838
- `Failed to parse ${filePath}: ${err.message}`,
1839
- filePath
1840
- );
1555
+ } catch (e) {
1556
+ return { exports: [], imports: [] };
1841
1557
  }
1842
1558
  }
1843
- getNamingConventions() {
1844
- return {
1845
- // camelCase for variables and functions
1846
- variablePattern: /^[a-z][a-zA-Z0-9]*$/,
1847
- functionPattern: /^[a-z][a-zA-Z0-9]*$/,
1848
- // PascalCase for classes
1849
- classPattern: /^[A-Z][a-zA-Z0-9]*$/,
1850
- // UPPER_CASE for constants
1851
- constantPattern: /^[A-Z][A-Z0-9_]*$/,
1852
- // Common exceptions (React hooks, etc.)
1853
- exceptions: ["__filename", "__dirname", "__esModule"]
1854
- };
1559
+ try {
1560
+ const ast = (0, import_typescript_estree2.parse)(code, {
1561
+ loc: true,
1562
+ range: true,
1563
+ jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
1564
+ filePath
1565
+ });
1566
+ const imports = extractFileImports(ast);
1567
+ const exports2 = extractExportsWithDependencies(ast, imports);
1568
+ return { exports: exports2, imports };
1569
+ } catch (error) {
1570
+ return { exports: [], imports: [] };
1571
+ }
1572
+ }
1573
+ function extractFileImports(ast) {
1574
+ const imports = [];
1575
+ for (const node of ast.body) {
1576
+ if (node.type === "ImportDeclaration") {
1577
+ const source = node.source.value;
1578
+ const specifiers = [];
1579
+ const isTypeOnly = node.importKind === "type";
1580
+ for (const spec of node.specifiers) {
1581
+ if (spec.type === "ImportSpecifier") {
1582
+ const imported = spec.imported;
1583
+ const importName = imported.type === "Identifier" ? imported.name : imported.value;
1584
+ specifiers.push(importName);
1585
+ } else if (spec.type === "ImportDefaultSpecifier") {
1586
+ specifiers.push("default");
1587
+ } else if (spec.type === "ImportNamespaceSpecifier") {
1588
+ specifiers.push("*");
1589
+ }
1590
+ }
1591
+ imports.push({ source, specifiers, isTypeOnly });
1592
+ }
1593
+ }
1594
+ return imports;
1595
+ }
1596
+ function extractExportsWithDependencies(ast, fileImports) {
1597
+ const exports2 = [];
1598
+ const importedNames = new Set(fileImports.flatMap((imp) => imp.specifiers));
1599
+ for (const node of ast.body) {
1600
+ if (node.type === "ExportNamedDeclaration") {
1601
+ if (node.declaration) {
1602
+ const exportNodes = extractFromDeclaration(node.declaration);
1603
+ for (const exp of exportNodes) {
1604
+ const usedImports = findUsedImports(node.declaration, importedNames);
1605
+ const typeReferences = extractTypeReferences(node.declaration);
1606
+ exports2.push({
1607
+ ...exp,
1608
+ imports: usedImports,
1609
+ dependencies: [],
1610
+ typeReferences,
1611
+ loc: node.loc
1612
+ });
1613
+ }
1614
+ }
1615
+ } else if (node.type === "ExportDefaultDeclaration") {
1616
+ const usedImports = findUsedImports(node.declaration, importedNames);
1617
+ const typeReferences = extractTypeReferences(node.declaration);
1618
+ exports2.push({
1619
+ name: "default",
1620
+ type: "default",
1621
+ imports: usedImports,
1622
+ dependencies: [],
1623
+ typeReferences,
1624
+ loc: node.loc
1625
+ });
1626
+ }
1627
+ }
1628
+ return exports2;
1629
+ }
1630
+ function extractFromDeclaration(declaration) {
1631
+ const results = [];
1632
+ if (declaration.type === "FunctionDeclaration" && "id" in declaration && declaration.id) {
1633
+ results.push({ name: declaration.id.name, type: "function" });
1634
+ } else if (declaration.type === "ClassDeclaration" && "id" in declaration && declaration.id) {
1635
+ results.push({ name: declaration.id.name, type: "class" });
1636
+ } else if (declaration.type === "VariableDeclaration") {
1637
+ for (const declarator of declaration.declarations) {
1638
+ if (declarator.id.type === "Identifier") {
1639
+ results.push({ name: declarator.id.name, type: "const" });
1640
+ }
1641
+ }
1642
+ } else if (declaration.type === "TSTypeAliasDeclaration") {
1643
+ results.push({ name: declaration.id.name, type: "type" });
1644
+ } else if (declaration.type === "TSInterfaceDeclaration") {
1645
+ results.push({ name: declaration.id.name, type: "interface" });
1646
+ }
1647
+ return results;
1648
+ }
1649
+ function findUsedImports(node, importedNames) {
1650
+ const usedImports = /* @__PURE__ */ new Set();
1651
+ function visit(n) {
1652
+ if (n.type === "Identifier" && importedNames.has(n.name)) {
1653
+ usedImports.add(n.name);
1654
+ }
1655
+ for (const key in n) {
1656
+ const value = n[key];
1657
+ if (value && typeof value === "object") {
1658
+ if (Array.isArray(value)) {
1659
+ value.forEach((child) => {
1660
+ if (child && typeof child === "object" && "type" in child) {
1661
+ visit(child);
1662
+ }
1663
+ });
1664
+ } else if ("type" in value) {
1665
+ visit(value);
1666
+ }
1667
+ }
1668
+ }
1855
1669
  }
1856
- canHandle(filePath) {
1857
- return this.extensions.some((ext) => filePath.toLowerCase().endsWith(ext));
1670
+ visit(node);
1671
+ return Array.from(usedImports);
1672
+ }
1673
+ function calculateImportSimilarity(export1, export2) {
1674
+ if (export1.imports.length === 0 && export2.imports.length === 0) {
1675
+ return 1;
1858
1676
  }
1859
- extractImports(ast) {
1860
- const imports = [];
1861
- for (const node of ast.body) {
1862
- if (node.type === "ImportDeclaration") {
1863
- const specifiers = [];
1864
- let isTypeOnly = false;
1865
- if (node.importKind === "type") {
1866
- isTypeOnly = true;
1867
- }
1868
- for (const spec of node.specifiers) {
1869
- if (spec.type === "ImportSpecifier") {
1870
- const imported = spec.imported;
1871
- const name = imported.type === "Identifier" ? imported.name : imported.value;
1872
- specifiers.push(name);
1873
- } else if (spec.type === "ImportDefaultSpecifier") {
1874
- specifiers.push("default");
1875
- } else if (spec.type === "ImportNamespaceSpecifier") {
1876
- specifiers.push("*");
1677
+ const set1 = new Set(export1.imports);
1678
+ const set2 = new Set(export2.imports);
1679
+ const intersection = new Set([...set1].filter((x) => set2.has(x)));
1680
+ const union = /* @__PURE__ */ new Set([...set1, ...set2]);
1681
+ return intersection.size / union.size;
1682
+ }
1683
+ function extractTypeReferences(node) {
1684
+ const types = /* @__PURE__ */ new Set();
1685
+ function visit(n) {
1686
+ if (!n || typeof n !== "object") return;
1687
+ if (n.type === "TSTypeReference" && n.typeName) {
1688
+ if (n.typeName.type === "Identifier") {
1689
+ types.add(n.typeName.name);
1690
+ } else if (n.typeName.type === "TSQualifiedName") {
1691
+ let current = n.typeName;
1692
+ while (current.type === "TSQualifiedName") {
1693
+ if (current.right?.type === "Identifier") {
1694
+ types.add(current.right.name);
1877
1695
  }
1696
+ current = current.left;
1697
+ }
1698
+ if (current.type === "Identifier") {
1699
+ types.add(current.name);
1878
1700
  }
1879
- imports.push({
1880
- source: node.source.value,
1881
- specifiers,
1882
- isTypeOnly,
1883
- loc: node.loc ? {
1884
- start: {
1885
- line: node.loc.start.line,
1886
- column: node.loc.start.column
1887
- },
1888
- end: { line: node.loc.end.line, column: node.loc.end.column }
1889
- } : void 0
1890
- });
1891
1701
  }
1892
1702
  }
1893
- return imports;
1894
- }
1895
- extractExports(ast, imports) {
1896
- const exports2 = [];
1897
- const importedNames = new Set(
1898
- imports.flatMap(
1899
- (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
1900
- )
1901
- );
1902
- for (const node of ast.body) {
1903
- if (node.type === "ExportNamedDeclaration" && node.declaration) {
1904
- const extracted = this.extractFromDeclaration(
1905
- node.declaration,
1906
- importedNames
1907
- );
1908
- exports2.push(...extracted);
1909
- } else if (node.type === "ExportDefaultDeclaration") {
1910
- let name = "default";
1911
- let type = "default";
1912
- if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
1913
- name = node.declaration.id.name;
1914
- type = "function";
1915
- } else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
1916
- name = node.declaration.id.name;
1917
- type = "class";
1918
- }
1919
- exports2.push({
1920
- name,
1921
- type,
1922
- loc: node.loc ? {
1923
- start: {
1924
- line: node.loc.start.line,
1925
- column: node.loc.start.column
1926
- },
1927
- end: { line: node.loc.end.line, column: node.loc.end.column }
1928
- } : void 0
1929
- });
1703
+ if (n.type === "TSInterfaceHeritage" && n.expression) {
1704
+ if (n.expression.type === "Identifier") {
1705
+ types.add(n.expression.name);
1706
+ }
1707
+ }
1708
+ for (const key of Object.keys(n)) {
1709
+ const value = n[key];
1710
+ if (Array.isArray(value)) {
1711
+ value.forEach(visit);
1712
+ } else if (value && typeof value === "object") {
1713
+ visit(value);
1930
1714
  }
1931
1715
  }
1932
- return exports2;
1933
1716
  }
1934
- extractFromDeclaration(declaration, importedNames) {
1935
- const exports2 = [];
1936
- if (declaration.type === "FunctionDeclaration" && declaration.id) {
1937
- exports2.push({
1938
- name: declaration.id.name,
1939
- type: "function",
1940
- parameters: declaration.params.map(
1941
- (p) => p.type === "Identifier" ? p.name : "unknown"
1942
- ),
1943
- loc: declaration.loc ? {
1944
- start: {
1945
- line: declaration.loc.start.line,
1946
- column: declaration.loc.start.column
1947
- },
1948
- end: {
1949
- line: declaration.loc.end.line,
1950
- column: declaration.loc.end.column
1717
+ visit(node);
1718
+ return Array.from(types);
1719
+ }
1720
+ function parseCode(code, language) {
1721
+ return null;
1722
+ }
1723
+ function extractFunctions(ast) {
1724
+ return [];
1725
+ }
1726
+ function extractImports(ast) {
1727
+ return [];
1728
+ }
1729
+
1730
+ // src/utils/metrics.ts
1731
+ function estimateTokens(text) {
1732
+ return Math.ceil(text.length / 4);
1733
+ }
1734
+
1735
+ // src/utils/config.ts
1736
+ var import_fs3 = require("fs");
1737
+ var import_path3 = require("path");
1738
+ var import_url = require("url");
1739
+ var CONFIG_FILES = [
1740
+ "aiready.json",
1741
+ "aiready.config.json",
1742
+ ".aiready.json",
1743
+ ".aireadyrc.json",
1744
+ "aiready.config.js",
1745
+ ".aireadyrc.js"
1746
+ ];
1747
+ async function loadConfig(rootDir) {
1748
+ let currentDir = (0, import_path3.resolve)(rootDir);
1749
+ while (true) {
1750
+ for (const configFile of CONFIG_FILES) {
1751
+ const configPath = (0, import_path3.join)(currentDir, configFile);
1752
+ if ((0, import_fs3.existsSync)(configPath)) {
1753
+ try {
1754
+ let config;
1755
+ if (configFile.endsWith(".js")) {
1756
+ const fileUrl = (0, import_url.pathToFileURL)(configPath).href;
1757
+ const module2 = await import(`${fileUrl}?t=${Date.now()}`);
1758
+ config = module2.default || module2;
1759
+ } else {
1760
+ const content = (0, import_fs3.readFileSync)(configPath, "utf-8");
1761
+ config = JSON.parse(content);
1951
1762
  }
1952
- } : void 0
1953
- });
1954
- } else if (declaration.type === "ClassDeclaration" && declaration.id) {
1955
- exports2.push({
1956
- name: declaration.id.name,
1957
- type: "class",
1958
- loc: declaration.loc ? {
1959
- start: {
1960
- line: declaration.loc.start.line,
1961
- column: declaration.loc.start.column
1962
- },
1963
- end: {
1964
- line: declaration.loc.end.line,
1965
- column: declaration.loc.end.column
1763
+ if (typeof config !== "object" || config === null) {
1764
+ throw new Error("Config must be an object");
1966
1765
  }
1967
- } : void 0
1968
- });
1969
- } else if (declaration.type === "VariableDeclaration") {
1970
- for (const declarator of declaration.declarations) {
1971
- if (declarator.id.type === "Identifier") {
1972
- exports2.push({
1973
- name: declarator.id.name,
1974
- type: "const",
1975
- loc: declarator.loc ? {
1976
- start: {
1977
- line: declarator.loc.start.line,
1978
- column: declarator.loc.start.column
1979
- },
1980
- end: {
1981
- line: declarator.loc.end.line,
1982
- column: declarator.loc.end.column
1983
- }
1984
- } : void 0
1985
- });
1766
+ return config;
1767
+ } catch (error) {
1768
+ const errorMessage = error instanceof Error ? error.message : String(error);
1769
+ const e = new Error(
1770
+ `Failed to load config from ${configPath}: ${errorMessage}`
1771
+ );
1772
+ try {
1773
+ e.cause = error instanceof Error ? error : void 0;
1774
+ } catch {
1775
+ }
1776
+ throw e;
1986
1777
  }
1987
1778
  }
1988
- } else if (declaration.type === "TSTypeAliasDeclaration") {
1989
- exports2.push({
1990
- name: declaration.id.name,
1991
- type: "type",
1992
- loc: declaration.loc ? {
1993
- start: {
1994
- line: declaration.loc.start.line,
1995
- column: declaration.loc.start.column
1996
- },
1997
- end: {
1998
- line: declaration.loc.end.line,
1999
- column: declaration.loc.end.column
2000
- }
2001
- } : void 0
2002
- });
2003
- } else if (declaration.type === "TSInterfaceDeclaration") {
2004
- exports2.push({
2005
- name: declaration.id.name,
2006
- type: "interface",
2007
- loc: declaration.loc ? {
2008
- start: {
2009
- line: declaration.loc.start.line,
2010
- column: declaration.loc.start.column
2011
- },
2012
- end: {
2013
- line: declaration.loc.end.line,
2014
- column: declaration.loc.end.column
2015
- }
2016
- } : void 0
2017
- });
2018
1779
  }
2019
- return exports2;
1780
+ const parent = (0, import_path3.dirname)(currentDir);
1781
+ if (parent === currentDir) {
1782
+ break;
1783
+ }
1784
+ currentDir = parent;
2020
1785
  }
2021
- };
2022
-
2023
- // src/parsers/python-parser.ts
2024
- var PythonParser = class {
2025
- constructor() {
2026
- this.language = "python" /* Python */;
2027
- this.extensions = [".py"];
2028
- this.parser = null;
2029
- this.initialized = false;
1786
+ return null;
1787
+ }
1788
+ function mergeConfigWithDefaults(userConfig, defaults) {
1789
+ if (!userConfig) return defaults;
1790
+ const result = { ...defaults };
1791
+ if (userConfig.scan) {
1792
+ if (userConfig.scan.include) result.include = userConfig.scan.include;
1793
+ if (userConfig.scan.exclude) result.exclude = userConfig.scan.exclude;
2030
1794
  }
2031
- /**
2032
- * Initialize the tree-sitter parser
2033
- * This is async because tree-sitter WASM needs to be loaded
2034
- */
2035
- async initialize() {
2036
- if (this.initialized) return;
2037
- try {
2038
- this.initialized = true;
2039
- } catch (error) {
2040
- throw new Error(
2041
- `Failed to initialize Python parser: ${error.message}`
2042
- );
1795
+ const toolOverrides = userConfig.tools && !Array.isArray(userConfig.tools) && typeof userConfig.tools === "object" ? userConfig.tools : userConfig.toolConfigs;
1796
+ if (toolOverrides) {
1797
+ if (!result.toolConfigs) result.toolConfigs = {};
1798
+ for (const [toolName, toolConfig] of Object.entries(toolOverrides)) {
1799
+ if (typeof toolConfig === "object" && toolConfig !== null) {
1800
+ result[toolName] = { ...result[toolName], ...toolConfig };
1801
+ result.toolConfigs[toolName] = {
1802
+ ...result.toolConfigs[toolName],
1803
+ ...toolConfig
1804
+ };
1805
+ }
2043
1806
  }
2044
1807
  }
2045
- parse(code, filePath) {
2046
- try {
2047
- const imports = this.extractImportsRegex(code, filePath);
2048
- const exports2 = this.extractExportsRegex(code, filePath);
2049
- return {
2050
- exports: exports2,
2051
- imports,
2052
- language: "python" /* Python */,
2053
- warnings: [
2054
- "Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."
2055
- ]
2056
- };
2057
- } catch (error) {
2058
- throw new ParseError(
2059
- `Failed to parse Python file ${filePath}: ${error.message}`,
2060
- filePath
2061
- );
1808
+ if (userConfig.output) {
1809
+ result.output = { ...result.output, ...userConfig.output };
1810
+ }
1811
+ return result;
1812
+ }
1813
+
1814
+ // src/utils/visualization.ts
1815
+ function generateHTML(graph) {
1816
+ const payload = JSON.stringify(graph, null, 2);
1817
+ return `<!doctype html>
1818
+ <html>
1819
+ <head>
1820
+ <meta charset="utf-8" />
1821
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1822
+ <title>AIReady Visualization</title>
1823
+ <style>
1824
+ html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
1825
+ #container { display:flex; height:100vh }
1826
+ #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
1827
+ #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
1828
+ canvas { background: #0b1220; border-radius:8px }
1829
+ .stat { margin-bottom:12px }
1830
+ </style>
1831
+ </head>
1832
+ <body>
1833
+ <div id="container">
1834
+ <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
1835
+ <div id="panel">
1836
+ <h2>AIReady Visualization</h2>
1837
+ <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
1838
+ <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
1839
+ <div class="stat"><strong>Legend</strong></div>
1840
+ <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
1841
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
1842
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
1843
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
1844
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
1845
+ <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
1846
+ <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
1847
+ <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
1848
+ </div>
1849
+ </div>
1850
+ </div>
1851
+
1852
+ <script>
1853
+ const graphData = ${payload};
1854
+ document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
1855
+ document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
1856
+
1857
+ const canvas = document.getElementById('canvas');
1858
+ const ctx = canvas.getContext('2d');
1859
+
1860
+ const nodes = graphData.nodes.map((n, i) => ({
1861
+ ...n,
1862
+ x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
1863
+ y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
1864
+ }));
1865
+
1866
+ function draw() {
1867
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1868
+
1869
+ graphData.edges.forEach(edge => {
1870
+ const s = nodes.find(n => n.id === edge.source);
1871
+ const t = nodes.find(n => n.id === edge.target);
1872
+ if (!s || !t) return;
1873
+ if (edge.type === 'related') return;
1874
+ if (edge.type === 'similarity') {
1875
+ ctx.strokeStyle = '#fb7e81';
1876
+ ctx.lineWidth = 1.2;
1877
+ } else if (edge.type === 'dependency') {
1878
+ ctx.strokeStyle = '#84c1ff';
1879
+ ctx.lineWidth = 1.0;
1880
+ } else if (edge.type === 'reference') {
1881
+ ctx.strokeStyle = '#ffa500';
1882
+ ctx.lineWidth = 0.9;
1883
+ } else {
1884
+ ctx.strokeStyle = '#334155';
1885
+ ctx.lineWidth = 0.8;
1886
+ }
1887
+ ctx.beginPath();
1888
+ ctx.moveTo(s.x, s.y);
1889
+ ctx.lineTo(t.x, t.y);
1890
+ ctx.stroke();
1891
+ });
1892
+
1893
+ const groups = {};
1894
+ nodes.forEach(n => {
1895
+ const g = n.group || '__default';
1896
+ if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
1897
+ groups[g].minX = Math.min(groups[g].minX, n.x);
1898
+ groups[g].minY = Math.min(groups[g].minY, n.y);
1899
+ groups[g].maxX = Math.max(groups[g].maxX, n.x);
1900
+ groups[g].maxY = Math.max(groups[g].maxY, n.y);
1901
+ });
1902
+
1903
+ const groupRelations = {};
1904
+ graphData.edges.forEach(edge => {
1905
+ const sNode = nodes.find(n => n.id === edge.source);
1906
+ const tNode = nodes.find(n => n.id === edge.target);
1907
+ if (!sNode || !tNode) return;
1908
+ const g1 = sNode.group || '__default';
1909
+ const g2 = tNode.group || '__default';
1910
+ if (g1 === g2) return;
1911
+ const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
1912
+ groupRelations[key] = (groupRelations[key] || 0) + 1;
1913
+ });
1914
+
1915
+ Object.keys(groupRelations).forEach(k => {
1916
+ const count = groupRelations[k];
1917
+ const [ga, gb] = k.split('::');
1918
+ if (!groups[ga] || !groups[gb]) return;
1919
+ const ax = (groups[ga].minX + groups[ga].maxX) / 2;
1920
+ const ay = (groups[ga].minY + groups[ga].maxY) / 2;
1921
+ const bx = (groups[gb].minX + groups[gb].maxX) / 2;
1922
+ const by = (groups[gb].minY + groups[gb].maxY) / 2;
1923
+ ctx.beginPath();
1924
+ ctx.strokeStyle = 'rgba(148,163,184,0.25)';
1925
+ ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
1926
+ ctx.moveTo(ax, ay);
1927
+ ctx.lineTo(bx, by);
1928
+ ctx.stroke();
1929
+ });
1930
+
1931
+ Object.keys(groups).forEach(g => {
1932
+ if (g === '__default') return;
1933
+ const box = groups[g];
1934
+ const pad = 16;
1935
+ const x = box.minX - pad;
1936
+ const y = box.minY - pad;
1937
+ const w = (box.maxX - box.minX) + pad * 2;
1938
+ const h = (box.maxY - box.minY) + pad * 2;
1939
+ ctx.save();
1940
+ ctx.fillStyle = 'rgba(30,64,175,0.04)';
1941
+ ctx.strokeStyle = 'rgba(30,64,175,0.12)';
1942
+ ctx.lineWidth = 1.2;
1943
+ const r = 8;
1944
+ ctx.beginPath();
1945
+ ctx.moveTo(x + r, y);
1946
+ ctx.arcTo(x + w, y, x + w, y + h, r);
1947
+ ctx.arcTo(x + w, y + h, x, y + h, r);
1948
+ ctx.arcTo(x, y + h, x, y, r);
1949
+ ctx.arcTo(x, y, x + w, y, r);
1950
+ ctx.closePath();
1951
+ ctx.fill();
1952
+ ctx.stroke();
1953
+ ctx.restore();
1954
+ ctx.fillStyle = '#94a3b8';
1955
+ ctx.font = '11px sans-serif';
1956
+ ctx.fillText(g, x + 8, y + 14);
1957
+ });
1958
+
1959
+ nodes.forEach(n => {
1960
+ const sizeVal = (n.size || n.value || 1);
1961
+ const r = 6 + (sizeVal / 2);
1962
+ ctx.beginPath();
1963
+ ctx.fillStyle = n.color || '#60a5fa';
1964
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
1965
+ ctx.fill();
1966
+
1967
+ ctx.fillStyle = '#e2e8f0';
1968
+ ctx.font = '11px sans-serif';
1969
+ ctx.textAlign = 'center';
1970
+ ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
1971
+ });
1972
+ }
1973
+
1974
+ draw();
1975
+ </script>
1976
+ </body>
1977
+ </html>`;
1978
+ }
1979
+
1980
+ // src/scoring.ts
1981
+ var DEFAULT_TOOL_WEIGHTS = {
1982
+ ["pattern-detect" /* PatternDetect */]: 22,
1983
+ ["context-analyzer" /* ContextAnalyzer */]: 19,
1984
+ ["naming-consistency" /* NamingConsistency */]: 14,
1985
+ ["ai-signal-clarity" /* AiSignalClarity */]: 11,
1986
+ ["agent-grounding" /* AgentGrounding */]: 10,
1987
+ ["testability-index" /* TestabilityIndex */]: 10,
1988
+ ["doc-drift" /* DocDrift */]: 8,
1989
+ ["dependency-health" /* DependencyHealth */]: 6,
1990
+ ["change-amplification" /* ChangeAmplification */]: 8
1991
+ };
1992
+ var TOOL_NAME_MAP = {
1993
+ patterns: "pattern-detect" /* PatternDetect */,
1994
+ "pattern-detect": "pattern-detect" /* PatternDetect */,
1995
+ context: "context-analyzer" /* ContextAnalyzer */,
1996
+ "context-analyzer": "context-analyzer" /* ContextAnalyzer */,
1997
+ consistency: "naming-consistency" /* NamingConsistency */,
1998
+ "naming-consistency": "naming-consistency" /* NamingConsistency */,
1999
+ "ai-signal": "ai-signal-clarity" /* AiSignalClarity */,
2000
+ "ai-signal-clarity": "ai-signal-clarity" /* AiSignalClarity */,
2001
+ grounding: "agent-grounding" /* AgentGrounding */,
2002
+ "agent-grounding": "agent-grounding" /* AgentGrounding */,
2003
+ testability: "testability-index" /* TestabilityIndex */,
2004
+ "testability-index": "testability-index" /* TestabilityIndex */,
2005
+ "doc-drift": "doc-drift" /* DocDrift */,
2006
+ "deps-health": "dependency-health" /* DependencyHealth */,
2007
+ "dependency-health": "dependency-health" /* DependencyHealth */,
2008
+ "change-amp": "change-amplification" /* ChangeAmplification */,
2009
+ "change-amplification": "change-amplification" /* ChangeAmplification */
2010
+ };
2011
+ var CONTEXT_TIER_THRESHOLDS = {
2012
+ compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
2013
+ standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
2014
+ extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
2015
+ frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
2016
+ };
2017
+ var SIZE_ADJUSTED_THRESHOLDS = {
2018
+ xs: 80,
2019
+ // < 50 files
2020
+ small: 75,
2021
+ // 50-200 files
2022
+ medium: 70,
2023
+ // 200-500 files
2024
+ large: 65,
2025
+ // 500-2000 files
2026
+ enterprise: 58
2027
+ // 2000+ files
2028
+ };
2029
+ function getProjectSizeTier(fileCount) {
2030
+ if (fileCount < 50) return "xs";
2031
+ if (fileCount < 200) return "small";
2032
+ if (fileCount < 500) return "medium";
2033
+ if (fileCount < 2e3) return "large";
2034
+ return "enterprise";
2035
+ }
2036
+ function getRecommendedThreshold(fileCount, modelTier = "standard") {
2037
+ const sizeTier = getProjectSizeTier(fileCount);
2038
+ const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
2039
+ const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
2040
+ return base + modelBonus;
2041
+ }
2042
+ function normalizeToolName(shortName) {
2043
+ return TOOL_NAME_MAP[shortName.toLowerCase()] || shortName;
2044
+ }
2045
+ function getToolWeight(toolName, toolConfig, cliOverride) {
2046
+ if (cliOverride !== void 0) return cliOverride;
2047
+ if (toolConfig?.scoreWeight !== void 0) return toolConfig.scoreWeight;
2048
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 5;
2049
+ }
2050
+ function parseWeightString(weightStr) {
2051
+ const weights = /* @__PURE__ */ new Map();
2052
+ if (!weightStr) return weights;
2053
+ const pairs = weightStr.split(",");
2054
+ for (const pair of pairs) {
2055
+ const [toolShortName, weightStr2] = pair.split(":");
2056
+ if (toolShortName && weightStr2) {
2057
+ const toolName = normalizeToolName(toolShortName.trim());
2058
+ const weight = parseInt(weightStr2.trim(), 10);
2059
+ if (!isNaN(weight) && weight > 0) {
2060
+ weights.set(toolName, weight);
2061
+ }
2062
2062
  }
2063
2063
  }
2064
- getNamingConventions() {
2065
- return {
2066
- // snake_case for variables and functions
2067
- variablePattern: /^[a-z_][a-z0-9_]*$/,
2068
- functionPattern: /^[a-z_][a-z0-9_]*$/,
2069
- // PascalCase for classes
2070
- classPattern: /^[A-Z][a-zA-Z0-9]*$/,
2071
- // UPPER_CASE for constants
2072
- constantPattern: /^[A-Z][A-Z0-9_]*$/,
2073
- // Python special methods and common exceptions
2074
- exceptions: [
2075
- "__init__",
2076
- "__str__",
2077
- "__repr__",
2078
- "__name__",
2079
- "__main__",
2080
- "__file__",
2081
- "__doc__",
2082
- "__all__",
2083
- "__version__",
2084
- "__author__",
2085
- "__dict__",
2086
- "__class__",
2087
- "__module__",
2088
- "__bases__"
2089
- ]
2090
- };
2064
+ return weights;
2065
+ }
2066
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
2067
+ if (toolOutputs.size === 0) {
2068
+ throw new Error("No tool outputs provided for scoring");
2091
2069
  }
2092
- canHandle(filePath) {
2093
- return filePath.toLowerCase().endsWith(".py");
2070
+ const weights = /* @__PURE__ */ new Map();
2071
+ for (const [toolName] of toolOutputs.entries()) {
2072
+ const cliWeight = cliWeights?.get(toolName);
2073
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
2074
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
2075
+ weights.set(toolName, weight);
2094
2076
  }
2095
- /**
2096
- * Regex-based import extraction (temporary implementation)
2097
- */
2098
- extractImportsRegex(code, filePath) {
2099
- const imports = [];
2100
- const lines = code.split("\n");
2101
- const importRegex = /^\s*import\s+([a-zA-Z0-9_., ]+)/;
2102
- const fromImportRegex = /^\s*from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/;
2103
- lines.forEach((line, idx) => {
2104
- if (line.trim().startsWith("#")) return;
2105
- const importMatch = line.match(importRegex);
2106
- if (importMatch) {
2107
- const modules = importMatch[1].split(",").map((m) => m.trim().split(" as ")[0]);
2108
- modules.forEach((module2) => {
2109
- imports.push({
2110
- source: module2,
2111
- specifiers: [module2],
2112
- loc: {
2113
- start: { line: idx + 1, column: 0 },
2114
- end: { line: idx + 1, column: line.length }
2115
- }
2116
- });
2117
- });
2118
- return;
2119
- }
2120
- const fromMatch = line.match(fromImportRegex);
2121
- if (fromMatch) {
2122
- const module2 = fromMatch[1];
2123
- const imports_str = fromMatch[2];
2124
- if (imports_str.trim() === "*") {
2125
- imports.push({
2126
- source: module2,
2127
- specifiers: ["*"],
2128
- loc: {
2129
- start: { line: idx + 1, column: 0 },
2130
- end: { line: idx + 1, column: line.length }
2131
- }
2132
- });
2133
- return;
2134
- }
2135
- const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
2136
- imports.push({
2137
- source: module2,
2138
- specifiers,
2139
- loc: {
2140
- start: { line: idx + 1, column: 0 },
2141
- end: { line: idx + 1, column: line.length }
2142
- }
2143
- });
2144
- }
2145
- });
2146
- return imports;
2077
+ let weightedSum = 0;
2078
+ let totalWeight = 0;
2079
+ const breakdown = [];
2080
+ const toolsUsed = [];
2081
+ const calculationWeights = {};
2082
+ for (const [toolName, output] of toolOutputs.entries()) {
2083
+ const weight = weights.get(toolName) || 5;
2084
+ weightedSum += output.score * weight;
2085
+ totalWeight += weight;
2086
+ toolsUsed.push(toolName);
2087
+ calculationWeights[toolName] = weight;
2088
+ breakdown.push(output);
2147
2089
  }
2148
- /**
2149
- * Regex-based export extraction (temporary implementation)
2150
- *
2151
- * Python doesn't have explicit exports like JavaScript.
2152
- * We extract:
2153
- * - Functions defined at module level (def)
2154
- * - Classes defined at module level (class)
2155
- * - Variables in __all__ list
2156
- */
2157
- extractExportsRegex(code, filePath) {
2158
- const exports2 = [];
2159
- const lines = code.split("\n");
2160
- const functionRegex = /^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/;
2161
- const classRegex = /^class\s+([a-zA-Z_][a-zA-Z0-9_]*)/;
2162
- const allRegex = /__all__\s*=\s*\[([^\]]+)\]/;
2163
- let inClass = false;
2164
- let classIndent = 0;
2165
- lines.forEach((line, idx) => {
2166
- const indent = line.search(/\S/);
2167
- if (line.match(classRegex)) {
2168
- inClass = true;
2169
- classIndent = indent;
2170
- } else if (inClass && indent <= classIndent && line.trim()) {
2171
- inClass = false;
2172
- }
2173
- if (inClass) {
2174
- const classMatch = line.match(classRegex);
2175
- if (classMatch) {
2176
- exports2.push({
2177
- name: classMatch[1],
2178
- type: "class",
2179
- loc: {
2180
- start: { line: idx + 1, column: indent },
2181
- end: { line: idx + 1, column: line.length }
2182
- }
2183
- });
2184
- }
2185
- return;
2186
- }
2187
- const funcMatch = line.match(functionRegex);
2188
- if (funcMatch && indent === 0) {
2189
- const name = funcMatch[1];
2190
- if (!name.startsWith("_") || name.startsWith("__")) {
2191
- exports2.push({
2192
- name,
2193
- type: "function",
2194
- loc: {
2195
- start: { line: idx + 1, column: 0 },
2196
- end: { line: idx + 1, column: line.length }
2197
- }
2198
- });
2199
- }
2200
- }
2201
- const allMatch = line.match(allRegex);
2202
- if (allMatch) {
2203
- const names = allMatch[1].split(",").map((n) => n.trim().replace(/['"]/g, ""));
2204
- names.forEach((name) => {
2205
- if (name && !exports2.find((e) => e.name === name)) {
2206
- exports2.push({
2207
- name,
2208
- type: "variable",
2209
- loc: {
2210
- start: { line: idx + 1, column: 0 },
2211
- end: { line: idx + 1, column: line.length }
2212
- }
2213
- });
2214
- }
2215
- });
2216
- }
2217
- });
2218
- return exports2;
2090
+ const overall = Math.round(weightedSum / totalWeight);
2091
+ const rating = getRating(overall);
2092
+ const formulaParts = Array.from(toolOutputs.entries()).map(
2093
+ ([name, output]) => {
2094
+ const w = weights.get(name) || 5;
2095
+ return `(${output.score} \xD7 ${w})`;
2096
+ }
2097
+ );
2098
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
2099
+ return {
2100
+ overall,
2101
+ rating,
2102
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2103
+ toolsUsed,
2104
+ breakdown,
2105
+ calculation: {
2106
+ formula: formulaStr,
2107
+ weights: calculationWeights,
2108
+ normalized: formulaStr
2109
+ }
2110
+ };
2111
+ }
2112
+ function getRating(score) {
2113
+ if (score >= 90) return "Excellent";
2114
+ if (score >= 75) return "Good";
2115
+ if (score >= 60) return "Fair";
2116
+ if (score >= 40) return "Needs Work";
2117
+ return "Critical";
2118
+ }
2119
+ function getRatingWithContext(score, fileCount, modelTier = "standard") {
2120
+ const threshold = getRecommendedThreshold(fileCount, modelTier);
2121
+ const normalized = score - threshold + 70;
2122
+ return getRating(normalized);
2123
+ }
2124
+ function getRatingDisplay(rating) {
2125
+ switch (rating) {
2126
+ case "Excellent":
2127
+ return { emoji: "\u2705", color: "green" };
2128
+ case "Good":
2129
+ return { emoji: "\u{1F44D}", color: "blue" };
2130
+ case "Fair":
2131
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
2132
+ case "Needs Work":
2133
+ return { emoji: "\u{1F528}", color: "orange" };
2134
+ case "Critical":
2135
+ return { emoji: "\u274C", color: "red" };
2219
2136
  }
2220
- };
2137
+ }
2138
+ function formatScore(result) {
2139
+ const { emoji } = getRatingDisplay(result.rating);
2140
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
2141
+ }
2142
+ function formatToolScore(output) {
2143
+ let result = ` Score: ${output.score}/100
2221
2144
 
2222
- // src/parsers/parser-factory.ts
2223
- var ParserFactory = class _ParserFactory {
2224
- constructor() {
2225
- this.parsers = /* @__PURE__ */ new Map();
2226
- this.extensionMap = new Map(
2227
- Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
2228
- );
2229
- this.registerParser(new TypeScriptParser());
2230
- this.registerParser(new PythonParser());
2231
- }
2232
- /**
2233
- * Get singleton instance
2234
- */
2235
- static getInstance() {
2236
- if (!_ParserFactory.instance) {
2237
- _ParserFactory.instance = new _ParserFactory();
2238
- }
2239
- return _ParserFactory.instance;
2145
+ `;
2146
+ if (output.factors && output.factors.length > 0) {
2147
+ result += ` Factors:
2148
+ `;
2149
+ output.factors.forEach((factor) => {
2150
+ const impactSign = factor.impact > 0 ? "+" : "";
2151
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
2152
+ `;
2153
+ });
2154
+ result += "\n";
2240
2155
  }
2241
- /**
2242
- * Register a language parser
2243
- */
2244
- registerParser(parser) {
2245
- this.parsers.set(parser.language, parser);
2246
- parser.extensions.forEach((ext) => {
2247
- const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
2248
- this.extensionMap.set(ext, lang);
2249
- this.parsers.set(lang, parser);
2156
+ if (output.recommendations && output.recommendations.length > 0) {
2157
+ result += ` Recommendations:
2158
+ `;
2159
+ output.recommendations.forEach((rec, i) => {
2160
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
2161
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
2162
+ `;
2163
+ result += ` Impact: +${rec.estimatedImpact} points
2164
+
2165
+ `;
2250
2166
  });
2251
2167
  }
2252
- /**
2253
- * Get parser for a specific language
2254
- */
2255
- getParserForLanguage(language) {
2256
- return this.parsers.get(language) || null;
2168
+ return result;
2169
+ }
2170
+
2171
+ // src/business/pricing-models.ts
2172
+ var MODEL_PRICING_PRESETS = {
2173
+ "gpt-5.3": {
2174
+ name: "GPT-5.3",
2175
+ pricePer1KInputTokens: 2e-3,
2176
+ pricePer1KOutputTokens: 8e-3,
2177
+ contextTier: "frontier",
2178
+ typicalQueriesPerDevPerDay: 100
2179
+ },
2180
+ "claude-4.6": {
2181
+ name: "Claude 4.6",
2182
+ pricePer1KInputTokens: 15e-4,
2183
+ pricePer1KOutputTokens: 75e-4,
2184
+ contextTier: "frontier",
2185
+ typicalQueriesPerDevPerDay: 100
2186
+ },
2187
+ "gemini-3.1": {
2188
+ name: "Gemini 3.1 Pro",
2189
+ pricePer1KInputTokens: 8e-4,
2190
+ pricePer1KOutputTokens: 3e-3,
2191
+ contextTier: "frontier",
2192
+ typicalQueriesPerDevPerDay: 120
2193
+ },
2194
+ "gpt-4o": {
2195
+ name: "GPT-4o (legacy)",
2196
+ pricePer1KInputTokens: 5e-3,
2197
+ pricePer1KOutputTokens: 0.015,
2198
+ contextTier: "extended",
2199
+ typicalQueriesPerDevPerDay: 60
2200
+ },
2201
+ "claude-3-5-sonnet": {
2202
+ name: "Claude 3.5 Sonnet (legacy)",
2203
+ pricePer1KInputTokens: 3e-3,
2204
+ pricePer1KOutputTokens: 0.015,
2205
+ contextTier: "extended",
2206
+ typicalQueriesPerDevPerDay: 80
2207
+ },
2208
+ "gemini-1-5-pro": {
2209
+ name: "Gemini 1.5 Pro (legacy)",
2210
+ pricePer1KInputTokens: 125e-5,
2211
+ pricePer1KOutputTokens: 5e-3,
2212
+ contextTier: "frontier",
2213
+ typicalQueriesPerDevPerDay: 80
2214
+ },
2215
+ copilot: {
2216
+ name: "GitHub Copilot (subscription)",
2217
+ pricePer1KInputTokens: 8e-5,
2218
+ pricePer1KOutputTokens: 8e-5,
2219
+ contextTier: "frontier",
2220
+ typicalQueriesPerDevPerDay: 150
2221
+ },
2222
+ "cursor-pro": {
2223
+ name: "Cursor Pro (subscription)",
2224
+ pricePer1KInputTokens: 8e-5,
2225
+ pricePer1KOutputTokens: 8e-5,
2226
+ contextTier: "frontier",
2227
+ typicalQueriesPerDevPerDay: 200
2257
2228
  }
2258
- /**
2259
- * Get parser for a file based on its extension
2260
- */
2261
- getParserForFile(filePath) {
2262
- const ext = this.getFileExtension(filePath);
2263
- const language = this.extensionMap.get(ext);
2264
- if (!language) {
2265
- return null;
2229
+ };
2230
+ function getModelPreset(modelId) {
2231
+ return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
2232
+ }
2233
+
2234
+ // src/business/cost-metrics.ts
2235
+ var DEFAULT_COST_CONFIG = {
2236
+ pricePer1KTokens: 5e-3,
2237
+ queriesPerDevPerDay: 60,
2238
+ developerCount: 5,
2239
+ daysPerMonth: 30
2240
+ };
2241
+ function calculateMonthlyCost(tokenWaste, config = {}) {
2242
+ const budget = calculateTokenBudget({
2243
+ totalContextTokens: tokenWaste * 2.5,
2244
+ wastedTokens: {
2245
+ duplication: tokenWaste * 0.7,
2246
+ fragmentation: tokenWaste * 0.3,
2247
+ chattiness: 0
2266
2248
  }
2267
- return this.parsers.get(language) || null;
2268
- }
2269
- /**
2270
- * Check if a file is supported
2271
- */
2272
- isSupported(filePath) {
2273
- return this.getParserForFile(filePath) !== null;
2249
+ });
2250
+ const preset = getModelPreset("claude-4.6");
2251
+ return estimateCostFromBudget(budget, preset, config);
2252
+ }
2253
+ function calculateTokenBudget(params) {
2254
+ const { totalContextTokens, wastedTokens } = params;
2255
+ const estimatedResponseTokens = params.estimatedResponseTokens ?? totalContextTokens * 0.2;
2256
+ const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
2257
+ const efficiencyRatio = Math.max(
2258
+ 0,
2259
+ Math.min(
2260
+ 1,
2261
+ (totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
2262
+ )
2263
+ );
2264
+ return {
2265
+ totalContextTokens: Math.round(totalContextTokens),
2266
+ estimatedResponseTokens: Math.round(estimatedResponseTokens),
2267
+ wastedTokens: {
2268
+ total: Math.round(totalWaste),
2269
+ bySource: {
2270
+ duplication: Math.round(wastedTokens.duplication),
2271
+ fragmentation: Math.round(wastedTokens.fragmentation),
2272
+ chattiness: Math.round(wastedTokens.chattiness)
2273
+ }
2274
+ },
2275
+ efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
2276
+ potentialRetrievableTokens: Math.round(totalWaste * 0.8)
2277
+ };
2278
+ }
2279
+ function estimateCostFromBudget(budget, model, config = {}) {
2280
+ const cfg = { ...DEFAULT_COST_CONFIG, ...config };
2281
+ const wastePerQuery = budget.wastedTokens.total;
2282
+ const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
2283
+ const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
2284
+ const totalWeight = cfg.developerCount;
2285
+ const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
2286
+ const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
2287
+ let confidence = 0.85;
2288
+ if (model.contextTier === "frontier") confidence = 0.7;
2289
+ const variance = 0.25;
2290
+ const range = [
2291
+ Math.round(baseCost * (1 - variance) * 100) / 100,
2292
+ Math.round(baseCost * (1 + variance) * 100) / 100
2293
+ ];
2294
+ return {
2295
+ total: Math.round(baseCost * 100) / 100,
2296
+ range,
2297
+ confidence
2298
+ };
2299
+ }
2300
+
2301
+ // src/business/productivity-metrics.ts
2302
+ var SEVERITY_TIME_ESTIMATES = {
2303
+ ["critical" /* Critical */]: 4,
2304
+ ["major" /* Major */]: 2,
2305
+ ["minor" /* Minor */]: 0.5,
2306
+ ["info" /* Info */]: 0.25
2307
+ };
2308
+ var DEFAULT_HOURLY_RATE = 75;
2309
+ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
2310
+ const counts = {
2311
+ ["critical" /* Critical */]: issues.filter((i) => i.severity === "critical" /* Critical */).length,
2312
+ ["major" /* Major */]: issues.filter((i) => i.severity === "major" /* Major */).length,
2313
+ ["minor" /* Minor */]: issues.filter((i) => i.severity === "minor" /* Minor */).length,
2314
+ ["info" /* Info */]: issues.filter((i) => i.severity === "info" /* Info */).length
2315
+ };
2316
+ const hours = {
2317
+ ["critical" /* Critical */]: counts["critical" /* Critical */] * SEVERITY_TIME_ESTIMATES["critical" /* Critical */],
2318
+ ["major" /* Major */]: counts["major" /* Major */] * SEVERITY_TIME_ESTIMATES["major" /* Major */],
2319
+ ["minor" /* Minor */]: counts["minor" /* Minor */] * SEVERITY_TIME_ESTIMATES["minor" /* Minor */],
2320
+ ["info" /* Info */]: counts["info" /* Info */] * SEVERITY_TIME_ESTIMATES["info" /* Info */]
2321
+ };
2322
+ const totalHours = hours["critical" /* Critical */] + hours["major" /* Major */] + hours["minor" /* Minor */] + hours["info" /* Info */];
2323
+ const totalCost = totalHours * hourlyRate;
2324
+ return {
2325
+ totalHours: Math.round(totalHours * 10) / 10,
2326
+ hourlyRate,
2327
+ totalCost: Math.round(totalCost),
2328
+ bySeverity: {
2329
+ ["critical" /* Critical */]: {
2330
+ hours: Math.round(hours["critical" /* Critical */] * 10) / 10,
2331
+ cost: Math.round(hours["critical" /* Critical */] * hourlyRate)
2332
+ },
2333
+ ["major" /* Major */]: {
2334
+ hours: Math.round(hours["major" /* Major */] * 10) / 10,
2335
+ cost: Math.round(hours["major" /* Major */] * hourlyRate)
2336
+ },
2337
+ ["minor" /* Minor */]: {
2338
+ hours: Math.round(hours["minor" /* Minor */] * 10) / 10,
2339
+ cost: Math.round(hours["minor" /* Minor */] * hourlyRate)
2340
+ },
2341
+ ["info" /* Info */]: {
2342
+ hours: Math.round(hours["info" /* Info */] * 10) / 10,
2343
+ cost: Math.round(hours["info" /* Info */] * hourlyRate)
2344
+ }
2345
+ }
2346
+ };
2347
+ }
2348
+ function predictAcceptanceRate(toolOutputs) {
2349
+ const factors = [];
2350
+ const baseRate = 0.3;
2351
+ const patterns = toolOutputs.get("pattern-detect");
2352
+ if (patterns) {
2353
+ factors.push({
2354
+ name: "Semantic Duplication",
2355
+ impact: Math.round((patterns.score - 50) * 3e-3 * 100)
2356
+ });
2274
2357
  }
2275
- /**
2276
- * Get all registered languages
2277
- */
2278
- getSupportedLanguages() {
2279
- return Array.from(this.parsers.keys());
2358
+ const context = toolOutputs.get("context-analyzer");
2359
+ if (context) {
2360
+ factors.push({
2361
+ name: "Context Efficiency",
2362
+ impact: Math.round((context.score - 50) * 4e-3 * 100)
2363
+ });
2280
2364
  }
2281
- /**
2282
- * Get all supported file extensions
2283
- */
2284
- getSupportedExtensions() {
2285
- return Array.from(this.extensionMap.keys());
2365
+ const consistency = toolOutputs.get("consistency");
2366
+ if (consistency) {
2367
+ factors.push({
2368
+ name: "Code Consistency",
2369
+ impact: Math.round((consistency.score - 50) * 2e-3 * 100)
2370
+ });
2286
2371
  }
2287
- /**
2288
- * Get language for a file
2289
- */
2290
- getLanguageForFile(filePath) {
2291
- const ext = this.getFileExtension(filePath);
2292
- return this.extensionMap.get(ext) || null;
2372
+ const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
2373
+ if (aiSignalClarity) {
2374
+ factors.push({
2375
+ name: "AI Signal Clarity",
2376
+ impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
2377
+ });
2293
2378
  }
2294
- /**
2295
- * Extract file extension (with dot)
2296
- */
2297
- getFileExtension(filePath) {
2298
- const match = filePath.match(/\.[^.]+$/);
2299
- return match ? match[0].toLowerCase() : "";
2379
+ const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
2380
+ const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
2381
+ let confidence = 0.35;
2382
+ if (toolOutputs.size >= 4) confidence = 0.75;
2383
+ else if (toolOutputs.size >= 3) confidence = 0.65;
2384
+ else if (toolOutputs.size >= 2) confidence = 0.5;
2385
+ return { rate: Math.round(rate * 100) / 100, confidence, factors };
2386
+ }
2387
+
2388
+ // src/business/risk-metrics.ts
2389
+ function calculateKnowledgeConcentration(params) {
2390
+ const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
2391
+ const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
2392
+ const score = Math.round(
2393
+ Math.min(
2394
+ 100,
2395
+ concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
2396
+ )
2397
+ );
2398
+ let rating;
2399
+ if (score < 30) rating = "low";
2400
+ else if (score < 50) rating = "moderate";
2401
+ else if (score < 75) rating = "high";
2402
+ else rating = "critical";
2403
+ const recommendations = [];
2404
+ if (singleAuthorFiles > 0)
2405
+ recommendations.push(
2406
+ `Distribute knowledge for ${singleAuthorFiles} single-author files.`
2407
+ );
2408
+ if (orphanFiles > 0)
2409
+ recommendations.push(
2410
+ `Link ${orphanFiles} orphan files to the rest of the codebase.`
2411
+ );
2412
+ return {
2413
+ score,
2414
+ rating,
2415
+ recommendations,
2416
+ analysis: {
2417
+ uniqueConceptFiles,
2418
+ totalFiles,
2419
+ concentrationRatio,
2420
+ singleAuthorFiles,
2421
+ orphanFiles
2422
+ }
2423
+ };
2424
+ }
2425
+ function calculateDebtInterest(principal, monthlyGrowthRate) {
2426
+ const monthlyRate = monthlyGrowthRate;
2427
+ const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
2428
+ const monthlyCost = principal * monthlyRate;
2429
+ return {
2430
+ monthlyRate,
2431
+ annualRate,
2432
+ principal,
2433
+ monthlyCost,
2434
+ projections: {
2435
+ months6: principal * Math.pow(1 + monthlyRate, 6),
2436
+ months12: principal * Math.pow(1 + monthlyRate, 12),
2437
+ months24: principal * Math.pow(1 + monthlyRate, 24)
2438
+ }
2439
+ };
2440
+ }
2441
+
2442
+ // src/business/comprehension-metrics.ts
2443
+ function calculateTechnicalValueChain(params) {
2444
+ const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
2445
+ const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
2446
+ return {
2447
+ score: Math.round(Math.max(0, Math.min(100, score))),
2448
+ density: businessLogicDensity,
2449
+ complexity: dataAccessComplexity,
2450
+ surface: apiSurfaceArea
2451
+ };
2452
+ }
2453
+ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
2454
+ const tierMap = {
2455
+ compact: "compact",
2456
+ standard: "standard",
2457
+ extended: "extended",
2458
+ frontier: "frontier",
2459
+ easy: "frontier",
2460
+ // Map legacy 'easy' to 'frontier'
2461
+ moderate: "standard",
2462
+ difficult: "compact"
2463
+ };
2464
+ const tier = tierMap[modelTier] || "frontier";
2465
+ const threshold = CONTEXT_TIER_THRESHOLDS[tier];
2466
+ const budgetRatio = contextBudget / threshold.idealTokens;
2467
+ const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
2468
+ const finalScore = Math.round(Math.max(0, Math.min(100, score)));
2469
+ let rating;
2470
+ if (finalScore < 20) rating = "trivial";
2471
+ else if (finalScore < 40) rating = "easy";
2472
+ else if (finalScore < 60) rating = "moderate";
2473
+ else if (finalScore < 85) rating = "difficult";
2474
+ else rating = "expert";
2475
+ return {
2476
+ score: finalScore,
2477
+ rating,
2478
+ factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
2479
+ };
2480
+ }
2481
+
2482
+ // src/business-metrics.ts
2483
+ function calculateBusinessROI(params) {
2484
+ const model = getModelPreset(params.modelId || "claude-4.6");
2485
+ const devCount = params.developerCount || 5;
2486
+ const budget = calculateTokenBudget({
2487
+ totalContextTokens: params.tokenWaste * 2.5,
2488
+ wastedTokens: {
2489
+ duplication: params.tokenWaste * 0.7,
2490
+ fragmentation: params.tokenWaste * 0.3,
2491
+ chattiness: 0
2492
+ }
2493
+ });
2494
+ const cost = estimateCostFromBudget(budget, model, {
2495
+ developerCount: devCount
2496
+ });
2497
+ const productivity = calculateProductivityImpact(params.issues);
2498
+ const monthlySavings = cost.total;
2499
+ const productivityGainHours = productivity.totalHours;
2500
+ const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
2501
+ return {
2502
+ monthlySavings: Math.round(monthlySavings),
2503
+ productivityGainHours: Math.round(productivityGainHours),
2504
+ annualValue: Math.round(annualValue)
2505
+ };
2506
+ }
2507
+ function formatCost(cost) {
2508
+ if (cost < 1) {
2509
+ return `$${cost.toFixed(2)}`;
2510
+ } else if (cost < 1e3) {
2511
+ return `$${cost.toFixed(0)}`;
2512
+ } else {
2513
+ return `$${(cost / 1e3).toFixed(1)}k`;
2300
2514
  }
2301
- /**
2302
- * Reset factory (useful for testing)
2303
- */
2304
- static reset() {
2305
- _ParserFactory.instance = new _ParserFactory();
2515
+ }
2516
+ function formatHours(hours) {
2517
+ if (hours < 1) {
2518
+ return `${Math.round(hours * 60)}min`;
2519
+ } else if (hours < 8) {
2520
+ return `${hours.toFixed(1)}h`;
2521
+ } else if (hours < 40) {
2522
+ return `${Math.round(hours)}h`;
2523
+ } else {
2524
+ return `${(hours / 40).toFixed(1)} weeks`;
2306
2525
  }
2307
- };
2308
- function getParser(filePath) {
2309
- return ParserFactory.getInstance().getParserForFile(filePath);
2310
2526
  }
2311
- function isFileSupported(filePath) {
2312
- return ParserFactory.getInstance().isSupported(filePath);
2527
+ function formatAcceptanceRate(rate) {
2528
+ return `${Math.round(rate * 100)}%`;
2313
2529
  }
2314
- function getSupportedLanguages() {
2315
- return ParserFactory.getInstance().getSupportedLanguages();
2530
+ function generateValueChain(params) {
2531
+ const { issueType, count, severity } = params;
2532
+ const impacts = {
2533
+ "duplicate-pattern": {
2534
+ ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
2535
+ dev: "Developers must manually resolve conflicts between suggested variants.",
2536
+ risk: "high"
2537
+ },
2538
+ "context-fragmentation": {
2539
+ ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
2540
+ dev: "Slower AI responses and increased need for manual context pinning.",
2541
+ risk: "critical"
2542
+ },
2543
+ "naming-inconsistency": {
2544
+ ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
2545
+ dev: "Increased cognitive load for new devs during onboarding.",
2546
+ risk: "moderate"
2547
+ }
2548
+ };
2549
+ const impact = impacts[issueType] || {
2550
+ ai: "Reduced suggestion quality.",
2551
+ dev: "Slowed development velocity.",
2552
+ risk: "moderate"
2553
+ };
2554
+ const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
2555
+ return {
2556
+ issueType,
2557
+ technicalMetric: "Issue Count",
2558
+ technicalValue: count,
2559
+ aiImpact: {
2560
+ description: impact.ai,
2561
+ scoreImpact: severity === "critical" ? -15 : -5
2562
+ },
2563
+ developerImpact: {
2564
+ description: impact.dev,
2565
+ productivityLoss
2566
+ },
2567
+ businessOutcome: {
2568
+ directCost: count * 12,
2569
+ opportunityCost: productivityLoss * 15e3,
2570
+ riskLevel: impact.risk
2571
+ }
2572
+ };
2316
2573
  }
2317
2574
 
2318
2575
  // src/metrics/remediation-utils.ts
@@ -3394,6 +3651,7 @@ function getRepoMetadata(directory) {
3394
3651
  getToolWeight,
3395
3652
  handleCLIError,
3396
3653
  handleJSONOutput,
3654
+ initializeParsers,
3397
3655
  isFileSupported,
3398
3656
  isSourceFile,
3399
3657
  loadConfig,