@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/client.d.mts +4 -0
- package/dist/client.d.ts +4 -0
- package/dist/index.d.mts +15 -25
- package/dist/index.d.ts +15 -25
- package/dist/index.js +1705 -1447
- package/dist/index.mjs +1310 -1053
- package/package.json +3 -1
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__ */ ((
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
905
|
-
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
933
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
|
|
998
|
-
|
|
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
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
-
|
|
1043
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
-
|
|
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/
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
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
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
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
|
|
1762
|
-
|
|
1521
|
+
async function initializeParsers() {
|
|
1522
|
+
await ParserFactory.getInstance().initializeAll();
|
|
1763
1523
|
}
|
|
1764
|
-
function
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
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/
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
|
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:
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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 (
|
|
1836
|
-
|
|
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
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
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
|
-
|
|
1857
|
-
|
|
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
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
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
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
)
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
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
|
-
|
|
1780
|
+
const parent = (0, import_path3.dirname)(currentDir);
|
|
1781
|
+
if (parent === currentDir) {
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
currentDir = parent;
|
|
2020
1785
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
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
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
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
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
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
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
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
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
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
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
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
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
|
2312
|
-
return
|
|
2527
|
+
function formatAcceptanceRate(rate) {
|
|
2528
|
+
return `${Math.round(rate * 100)}%`;
|
|
2313
2529
|
}
|
|
2314
|
-
function
|
|
2315
|
-
|
|
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,
|