@aiready/core 0.21.13 → 0.21.14
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.mjs
CHANGED
|
@@ -467,904 +467,477 @@ function getSeverityColor(severity, chalk) {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
// src/utils/ast-parser.ts
|
|
470
|
+
import { parse as parse2 } from "@typescript-eslint/typescript-estree";
|
|
471
|
+
|
|
472
|
+
// src/parsers/typescript-parser.ts
|
|
470
473
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
range: true,
|
|
476
|
-
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
477
|
-
filePath
|
|
478
|
-
});
|
|
479
|
-
const imports = extractFileImports(ast);
|
|
480
|
-
const exports = extractExportsWithDependencies(ast, imports);
|
|
481
|
-
return { exports, imports };
|
|
482
|
-
} catch (error) {
|
|
483
|
-
return { exports: [], imports: [] };
|
|
474
|
+
var TypeScriptParser = class {
|
|
475
|
+
constructor() {
|
|
476
|
+
this.language = "typescript" /* TypeScript */;
|
|
477
|
+
this.extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
484
478
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const imports = [];
|
|
488
|
-
for (const node of ast.body) {
|
|
489
|
-
if (node.type === "ImportDeclaration") {
|
|
490
|
-
const source = node.source.value;
|
|
491
|
-
const specifiers = [];
|
|
492
|
-
const isTypeOnly = node.importKind === "type";
|
|
493
|
-
for (const spec of node.specifiers) {
|
|
494
|
-
if (spec.type === "ImportSpecifier") {
|
|
495
|
-
const imported = spec.imported;
|
|
496
|
-
const importName = imported.type === "Identifier" ? imported.name : imported.value;
|
|
497
|
-
specifiers.push(importName);
|
|
498
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
499
|
-
specifiers.push("default");
|
|
500
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
501
|
-
specifiers.push("*");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
imports.push({ source, specifiers, isTypeOnly });
|
|
505
|
-
}
|
|
479
|
+
async initialize() {
|
|
480
|
+
return Promise.resolve();
|
|
506
481
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const usedImports = findUsedImports(node.declaration, importedNames);
|
|
518
|
-
const typeReferences = extractTypeReferences(node.declaration);
|
|
519
|
-
exports.push({
|
|
520
|
-
...exp,
|
|
521
|
-
imports: usedImports,
|
|
522
|
-
dependencies: [],
|
|
523
|
-
typeReferences,
|
|
524
|
-
loc: node.loc
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
} else if (node.type === "ExportDefaultDeclaration") {
|
|
529
|
-
const usedImports = findUsedImports(node.declaration, importedNames);
|
|
530
|
-
const typeReferences = extractTypeReferences(node.declaration);
|
|
531
|
-
exports.push({
|
|
532
|
-
name: "default",
|
|
533
|
-
type: "default",
|
|
534
|
-
imports: usedImports,
|
|
535
|
-
dependencies: [],
|
|
536
|
-
typeReferences,
|
|
537
|
-
loc: node.loc
|
|
482
|
+
parse(code, filePath) {
|
|
483
|
+
try {
|
|
484
|
+
const isJavaScript = filePath.match(/\.jsx?$/i);
|
|
485
|
+
const ast = parse(code, {
|
|
486
|
+
loc: true,
|
|
487
|
+
range: true,
|
|
488
|
+
jsx: filePath.match(/\.[jt]sx$/i) !== null,
|
|
489
|
+
filePath,
|
|
490
|
+
sourceType: "module",
|
|
491
|
+
ecmaVersion: "latest"
|
|
538
492
|
});
|
|
493
|
+
const imports = this.extractImports(ast);
|
|
494
|
+
const exports = this.extractExports(ast, imports);
|
|
495
|
+
return {
|
|
496
|
+
exports,
|
|
497
|
+
imports,
|
|
498
|
+
language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
|
|
499
|
+
warnings: []
|
|
500
|
+
};
|
|
501
|
+
} catch (error) {
|
|
502
|
+
const err = error;
|
|
503
|
+
throw new ParseError(
|
|
504
|
+
`Failed to parse ${filePath}: ${err.message}`,
|
|
505
|
+
filePath
|
|
506
|
+
);
|
|
539
507
|
}
|
|
540
508
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
556
|
-
results.push({ name: declaration.id.name, type: "type" });
|
|
557
|
-
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
558
|
-
results.push({ name: declaration.id.name, type: "interface" });
|
|
559
|
-
}
|
|
560
|
-
return results;
|
|
561
|
-
}
|
|
562
|
-
function findUsedImports(node, importedNames) {
|
|
563
|
-
const usedImports = /* @__PURE__ */ new Set();
|
|
564
|
-
function visit(n) {
|
|
565
|
-
if (n.type === "Identifier" && importedNames.has(n.name)) {
|
|
566
|
-
usedImports.add(n.name);
|
|
567
|
-
}
|
|
568
|
-
for (const key in n) {
|
|
569
|
-
const value = n[key];
|
|
570
|
-
if (value && typeof value === "object") {
|
|
571
|
-
if (Array.isArray(value)) {
|
|
572
|
-
value.forEach((child) => {
|
|
573
|
-
if (child && typeof child === "object" && "type" in child) {
|
|
574
|
-
visit(child);
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
} else if ("type" in value) {
|
|
578
|
-
visit(value);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
509
|
+
getNamingConventions() {
|
|
510
|
+
return {
|
|
511
|
+
// camelCase for variables and functions
|
|
512
|
+
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
513
|
+
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
514
|
+
// PascalCase for classes
|
|
515
|
+
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
516
|
+
// UPPER_CASE for constants
|
|
517
|
+
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
518
|
+
// Common exceptions (React hooks, etc.)
|
|
519
|
+
exceptions: ["__filename", "__dirname", "__esModule"]
|
|
520
|
+
};
|
|
582
521
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
function calculateImportSimilarity(export1, export2) {
|
|
587
|
-
if (export1.imports.length === 0 && export2.imports.length === 0) {
|
|
588
|
-
return 1;
|
|
522
|
+
canHandle(filePath) {
|
|
523
|
+
return this.extensions.some((ext) => filePath.toLowerCase().endsWith(ext));
|
|
589
524
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
function visit(n) {
|
|
599
|
-
if (!n || typeof n !== "object") return;
|
|
600
|
-
if (n.type === "TSTypeReference" && n.typeName) {
|
|
601
|
-
if (n.typeName.type === "Identifier") {
|
|
602
|
-
types.add(n.typeName.name);
|
|
603
|
-
} else if (n.typeName.type === "TSQualifiedName") {
|
|
604
|
-
let current = n.typeName;
|
|
605
|
-
while (current.type === "TSQualifiedName") {
|
|
606
|
-
if (current.right?.type === "Identifier") {
|
|
607
|
-
types.add(current.right.name);
|
|
608
|
-
}
|
|
609
|
-
current = current.left;
|
|
525
|
+
extractImports(ast) {
|
|
526
|
+
const imports = [];
|
|
527
|
+
for (const node of ast.body) {
|
|
528
|
+
if (node.type === "ImportDeclaration") {
|
|
529
|
+
const specifiers = [];
|
|
530
|
+
let isTypeOnly = false;
|
|
531
|
+
if (node.importKind === "type") {
|
|
532
|
+
isTypeOnly = true;
|
|
610
533
|
}
|
|
611
|
-
|
|
612
|
-
|
|
534
|
+
for (const spec of node.specifiers) {
|
|
535
|
+
if (spec.type === "ImportSpecifier") {
|
|
536
|
+
const imported = spec.imported;
|
|
537
|
+
const name = imported.type === "Identifier" ? imported.name : imported.value;
|
|
538
|
+
specifiers.push(name);
|
|
539
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
540
|
+
specifiers.push("default");
|
|
541
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
542
|
+
specifiers.push("*");
|
|
543
|
+
}
|
|
613
544
|
}
|
|
545
|
+
imports.push({
|
|
546
|
+
source: node.source.value,
|
|
547
|
+
specifiers,
|
|
548
|
+
isTypeOnly,
|
|
549
|
+
loc: node.loc ? {
|
|
550
|
+
start: {
|
|
551
|
+
line: node.loc.start.line,
|
|
552
|
+
column: node.loc.start.column
|
|
553
|
+
},
|
|
554
|
+
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
555
|
+
} : void 0
|
|
556
|
+
});
|
|
614
557
|
}
|
|
615
558
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
559
|
+
return imports;
|
|
560
|
+
}
|
|
561
|
+
extractExports(ast, imports) {
|
|
562
|
+
const exports = [];
|
|
563
|
+
const importedNames = new Set(
|
|
564
|
+
imports.flatMap(
|
|
565
|
+
(imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
for (const node of ast.body) {
|
|
569
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
570
|
+
const extracted = this.extractFromDeclaration(
|
|
571
|
+
node.declaration,
|
|
572
|
+
importedNames
|
|
573
|
+
);
|
|
574
|
+
exports.push(...extracted);
|
|
575
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
576
|
+
let name = "default";
|
|
577
|
+
let type = "default";
|
|
578
|
+
if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
579
|
+
name = node.declaration.id.name;
|
|
580
|
+
type = "function";
|
|
581
|
+
} else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
|
|
582
|
+
name = node.declaration.id.name;
|
|
583
|
+
type = "class";
|
|
584
|
+
}
|
|
585
|
+
exports.push({
|
|
586
|
+
name,
|
|
587
|
+
type,
|
|
588
|
+
loc: node.loc ? {
|
|
589
|
+
start: {
|
|
590
|
+
line: node.loc.start.line,
|
|
591
|
+
column: node.loc.start.column
|
|
592
|
+
},
|
|
593
|
+
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
594
|
+
} : void 0
|
|
595
|
+
});
|
|
627
596
|
}
|
|
628
597
|
}
|
|
598
|
+
return exports;
|
|
629
599
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// src/utils/config.ts
|
|
649
|
-
import { readFileSync, existsSync as existsSync3 } from "fs";
|
|
650
|
-
import { join as join3, resolve, dirname as dirname3 } from "path";
|
|
651
|
-
import { pathToFileURL } from "url";
|
|
652
|
-
var CONFIG_FILES = [
|
|
653
|
-
"aiready.json",
|
|
654
|
-
"aiready.config.json",
|
|
655
|
-
".aiready.json",
|
|
656
|
-
".aireadyrc.json",
|
|
657
|
-
"aiready.config.js",
|
|
658
|
-
".aireadyrc.js"
|
|
659
|
-
];
|
|
660
|
-
async function loadConfig(rootDir) {
|
|
661
|
-
let currentDir = resolve(rootDir);
|
|
662
|
-
while (true) {
|
|
663
|
-
for (const configFile of CONFIG_FILES) {
|
|
664
|
-
const configPath = join3(currentDir, configFile);
|
|
665
|
-
if (existsSync3(configPath)) {
|
|
666
|
-
try {
|
|
667
|
-
let config;
|
|
668
|
-
if (configFile.endsWith(".js")) {
|
|
669
|
-
const fileUrl = pathToFileURL(configPath).href;
|
|
670
|
-
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
671
|
-
config = module.default || module;
|
|
672
|
-
} else {
|
|
673
|
-
const content = readFileSync(configPath, "utf-8");
|
|
674
|
-
config = JSON.parse(content);
|
|
600
|
+
extractFromDeclaration(declaration, importedNames) {
|
|
601
|
+
const exports = [];
|
|
602
|
+
if (declaration.type === "FunctionDeclaration" && declaration.id) {
|
|
603
|
+
exports.push({
|
|
604
|
+
name: declaration.id.name,
|
|
605
|
+
type: "function",
|
|
606
|
+
parameters: declaration.params.map(
|
|
607
|
+
(p) => p.type === "Identifier" ? p.name : "unknown"
|
|
608
|
+
),
|
|
609
|
+
loc: declaration.loc ? {
|
|
610
|
+
start: {
|
|
611
|
+
line: declaration.loc.start.line,
|
|
612
|
+
column: declaration.loc.start.column
|
|
613
|
+
},
|
|
614
|
+
end: {
|
|
615
|
+
line: declaration.loc.end.line,
|
|
616
|
+
column: declaration.loc.end.column
|
|
675
617
|
}
|
|
676
|
-
|
|
677
|
-
|
|
618
|
+
} : void 0
|
|
619
|
+
});
|
|
620
|
+
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
621
|
+
exports.push({
|
|
622
|
+
name: declaration.id.name,
|
|
623
|
+
type: "class",
|
|
624
|
+
loc: declaration.loc ? {
|
|
625
|
+
start: {
|
|
626
|
+
line: declaration.loc.start.line,
|
|
627
|
+
column: declaration.loc.start.column
|
|
628
|
+
},
|
|
629
|
+
end: {
|
|
630
|
+
line: declaration.loc.end.line,
|
|
631
|
+
column: declaration.loc.end.column
|
|
678
632
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
633
|
+
} : void 0
|
|
634
|
+
});
|
|
635
|
+
} else if (declaration.type === "VariableDeclaration") {
|
|
636
|
+
for (const declarator of declaration.declarations) {
|
|
637
|
+
if (declarator.id.type === "Identifier") {
|
|
638
|
+
exports.push({
|
|
639
|
+
name: declarator.id.name,
|
|
640
|
+
type: "const",
|
|
641
|
+
loc: declarator.loc ? {
|
|
642
|
+
start: {
|
|
643
|
+
line: declarator.loc.start.line,
|
|
644
|
+
column: declarator.loc.start.column
|
|
645
|
+
},
|
|
646
|
+
end: {
|
|
647
|
+
line: declarator.loc.end.line,
|
|
648
|
+
column: declarator.loc.end.column
|
|
649
|
+
}
|
|
650
|
+
} : void 0
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
655
|
+
exports.push({
|
|
656
|
+
name: declaration.id.name,
|
|
657
|
+
type: "type",
|
|
658
|
+
loc: declaration.loc ? {
|
|
659
|
+
start: {
|
|
660
|
+
line: declaration.loc.start.line,
|
|
661
|
+
column: declaration.loc.start.column
|
|
662
|
+
},
|
|
663
|
+
end: {
|
|
664
|
+
line: declaration.loc.end.line,
|
|
665
|
+
column: declaration.loc.end.column
|
|
688
666
|
}
|
|
689
|
-
|
|
667
|
+
} : void 0
|
|
668
|
+
});
|
|
669
|
+
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
670
|
+
exports.push({
|
|
671
|
+
name: declaration.id.name,
|
|
672
|
+
type: "interface",
|
|
673
|
+
loc: declaration.loc ? {
|
|
674
|
+
start: {
|
|
675
|
+
line: declaration.loc.start.line,
|
|
676
|
+
column: declaration.loc.start.column
|
|
677
|
+
},
|
|
678
|
+
end: {
|
|
679
|
+
line: declaration.loc.end.line,
|
|
680
|
+
column: declaration.loc.end.column
|
|
681
|
+
}
|
|
682
|
+
} : void 0
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
return exports;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// src/parsers/python-parser.ts
|
|
690
|
+
import * as Parser from "web-tree-sitter";
|
|
691
|
+
import * as path from "path";
|
|
692
|
+
import * as fs from "fs";
|
|
693
|
+
var PythonParser = class {
|
|
694
|
+
constructor() {
|
|
695
|
+
this.language = "python" /* Python */;
|
|
696
|
+
this.extensions = [".py"];
|
|
697
|
+
this.parser = null;
|
|
698
|
+
this.initialized = false;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Initialize the tree-sitter parser
|
|
702
|
+
*/
|
|
703
|
+
async initialize() {
|
|
704
|
+
if (this.initialized) return;
|
|
705
|
+
try {
|
|
706
|
+
await Parser.Parser.init();
|
|
707
|
+
this.parser = new Parser.Parser();
|
|
708
|
+
const possiblePaths = [
|
|
709
|
+
path.join(
|
|
710
|
+
process.cwd(),
|
|
711
|
+
"node_modules/tree-sitter-python/tree-sitter-python.wasm"
|
|
712
|
+
),
|
|
713
|
+
path.join(
|
|
714
|
+
__dirname,
|
|
715
|
+
"../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
|
|
716
|
+
),
|
|
717
|
+
path.join(
|
|
718
|
+
__dirname,
|
|
719
|
+
"../../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
|
|
720
|
+
),
|
|
721
|
+
path.join(
|
|
722
|
+
__dirname,
|
|
723
|
+
"../../../../node_modules/tree-sitter-python/tree-sitter-python.wasm"
|
|
724
|
+
),
|
|
725
|
+
path.join(__dirname, "../assets/tree-sitter-python.wasm")
|
|
726
|
+
];
|
|
727
|
+
let wasmPath = "";
|
|
728
|
+
for (const p of possiblePaths) {
|
|
729
|
+
if (fs.existsSync(p)) {
|
|
730
|
+
wasmPath = p;
|
|
731
|
+
break;
|
|
690
732
|
}
|
|
691
733
|
}
|
|
734
|
+
if (!wasmPath) {
|
|
735
|
+
console.warn(
|
|
736
|
+
"Python WASM not found in common locations, attempting fallback regex parser"
|
|
737
|
+
);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const Python = await Parser.Language.load(wasmPath);
|
|
741
|
+
this.parser.setLanguage(Python);
|
|
742
|
+
this.initialized = true;
|
|
743
|
+
} catch (error) {
|
|
744
|
+
console.error(
|
|
745
|
+
`Failed to initialize tree-sitter-python: ${error.message}`
|
|
746
|
+
);
|
|
692
747
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
748
|
+
}
|
|
749
|
+
parse(code, filePath) {
|
|
750
|
+
if (!this.initialized || !this.parser) {
|
|
751
|
+
return this.parseRegex(code, filePath);
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
const tree = this.parser.parse(code);
|
|
755
|
+
if (!tree) throw new Error("Parser.parse(code) returned null");
|
|
756
|
+
const rootNode = tree.rootNode;
|
|
757
|
+
const imports = this.extractImportsAST(rootNode);
|
|
758
|
+
const exports = this.extractExportsAST(rootNode);
|
|
759
|
+
return {
|
|
760
|
+
exports,
|
|
761
|
+
imports,
|
|
762
|
+
language: "python" /* Python */,
|
|
763
|
+
warnings: []
|
|
764
|
+
};
|
|
765
|
+
} catch (error) {
|
|
766
|
+
console.warn(
|
|
767
|
+
`AST parsing failed for ${filePath}, falling back to regex: ${error.message}`
|
|
768
|
+
);
|
|
769
|
+
return this.parseRegex(code, filePath);
|
|
696
770
|
}
|
|
697
|
-
currentDir = parent;
|
|
698
771
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
772
|
+
extractImportsAST(rootNode) {
|
|
773
|
+
const imports = [];
|
|
774
|
+
const processImportNode = (node) => {
|
|
775
|
+
if (node.type === "import_statement") {
|
|
776
|
+
for (const child of node.children) {
|
|
777
|
+
if (child.type === "dotted_name") {
|
|
778
|
+
const source = child.text;
|
|
779
|
+
imports.push({
|
|
780
|
+
source,
|
|
781
|
+
specifiers: [source],
|
|
782
|
+
loc: {
|
|
783
|
+
start: {
|
|
784
|
+
line: child.startPosition.row + 1,
|
|
785
|
+
column: child.startPosition.column
|
|
786
|
+
},
|
|
787
|
+
end: {
|
|
788
|
+
line: child.endPosition.row + 1,
|
|
789
|
+
column: child.endPosition.column
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
} else if (child.type === "aliased_import") {
|
|
794
|
+
const nameNode = child.childForFieldName("name");
|
|
795
|
+
if (nameNode) {
|
|
796
|
+
const source = nameNode.text;
|
|
797
|
+
imports.push({
|
|
798
|
+
source,
|
|
799
|
+
specifiers: [source],
|
|
800
|
+
loc: {
|
|
801
|
+
start: {
|
|
802
|
+
line: child.startPosition.row + 1,
|
|
803
|
+
column: child.startPosition.column
|
|
804
|
+
},
|
|
805
|
+
end: {
|
|
806
|
+
line: child.endPosition.row + 1,
|
|
807
|
+
column: child.endPosition.column
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
} else if (node.type === "import_from_statement") {
|
|
815
|
+
const moduleNameNode = node.childForFieldName("module_name");
|
|
816
|
+
if (moduleNameNode) {
|
|
817
|
+
const source = moduleNameNode.text;
|
|
818
|
+
const specifiers = [];
|
|
819
|
+
for (const child of node.children) {
|
|
820
|
+
if (child.type === "dotted_name" && child !== moduleNameNode) {
|
|
821
|
+
specifiers.push(child.text);
|
|
822
|
+
} else if (child.type === "aliased_import") {
|
|
823
|
+
const nameNode = child.childForFieldName("name");
|
|
824
|
+
if (nameNode) specifiers.push(nameNode.text);
|
|
825
|
+
} else if (child.type === "wildcard_import") {
|
|
826
|
+
specifiers.push("*");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (specifiers.length > 0) {
|
|
830
|
+
imports.push({
|
|
831
|
+
source,
|
|
832
|
+
specifiers,
|
|
833
|
+
loc: {
|
|
834
|
+
start: {
|
|
835
|
+
line: node.startPosition.row + 1,
|
|
836
|
+
column: node.startPosition.column
|
|
837
|
+
},
|
|
838
|
+
end: {
|
|
839
|
+
line: node.endPosition.row + 1,
|
|
840
|
+
column: node.endPosition.column
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
for (const node of rootNode.children) {
|
|
849
|
+
processImportNode(node);
|
|
850
|
+
}
|
|
851
|
+
return imports;
|
|
707
852
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
853
|
+
extractExportsAST(rootNode) {
|
|
854
|
+
const exports = [];
|
|
855
|
+
for (const node of rootNode.children) {
|
|
856
|
+
if (node.type === "function_definition") {
|
|
857
|
+
const nameNode = node.childForFieldName("name");
|
|
858
|
+
if (nameNode) {
|
|
859
|
+
const name = nameNode.text;
|
|
860
|
+
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
861
|
+
if (!isPrivate) {
|
|
862
|
+
exports.push({
|
|
863
|
+
name,
|
|
864
|
+
type: "function",
|
|
865
|
+
loc: {
|
|
866
|
+
start: {
|
|
867
|
+
line: node.startPosition.row + 1,
|
|
868
|
+
column: node.startPosition.column
|
|
869
|
+
},
|
|
870
|
+
end: {
|
|
871
|
+
line: node.endPosition.row + 1,
|
|
872
|
+
column: node.endPosition.column
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
parameters: this.extractParameters(node)
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} else if (node.type === "class_definition") {
|
|
880
|
+
const nameNode = node.childForFieldName("name");
|
|
881
|
+
if (nameNode) {
|
|
882
|
+
exports.push({
|
|
883
|
+
name: nameNode.text,
|
|
884
|
+
type: "class",
|
|
885
|
+
loc: {
|
|
886
|
+
start: {
|
|
887
|
+
line: node.startPosition.row + 1,
|
|
888
|
+
column: node.startPosition.column
|
|
889
|
+
},
|
|
890
|
+
end: {
|
|
891
|
+
line: node.endPosition.row + 1,
|
|
892
|
+
column: node.endPosition.column
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
} else if (node.type === "expression_statement") {
|
|
898
|
+
const assignment = node.firstChild;
|
|
899
|
+
if (assignment && assignment.type === "assignment") {
|
|
900
|
+
const left = assignment.childForFieldName("left");
|
|
901
|
+
if (left && left.type === "identifier") {
|
|
902
|
+
const name = left.text;
|
|
903
|
+
const isInternal = name === "__all__" || name === "__version__" || name === "__author__";
|
|
904
|
+
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
905
|
+
if (!isInternal && !isPrivate) {
|
|
906
|
+
exports.push({
|
|
907
|
+
name,
|
|
908
|
+
type: name === name.toUpperCase() ? "const" : "variable",
|
|
909
|
+
loc: {
|
|
910
|
+
start: {
|
|
911
|
+
line: node.startPosition.row + 1,
|
|
912
|
+
column: node.startPosition.column
|
|
913
|
+
},
|
|
914
|
+
end: {
|
|
915
|
+
line: node.endPosition.row + 1,
|
|
916
|
+
column: node.endPosition.column
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
718
923
|
}
|
|
719
924
|
}
|
|
925
|
+
return exports;
|
|
720
926
|
}
|
|
721
|
-
|
|
722
|
-
|
|
927
|
+
extractParameters(node) {
|
|
928
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
929
|
+
if (!paramsNode) return [];
|
|
930
|
+
return paramsNode.children.filter(
|
|
931
|
+
(c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter"
|
|
932
|
+
).map((c) => {
|
|
933
|
+
if (c.type === "identifier") return c.text;
|
|
934
|
+
if (c.type === "typed_parameter" || c.type === "default_parameter") {
|
|
935
|
+
return c.firstChild?.text || "unknown";
|
|
936
|
+
}
|
|
937
|
+
return "unknown";
|
|
938
|
+
});
|
|
723
939
|
}
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// src/business/pricing-models.ts
|
|
728
|
-
var MODEL_PRICING_PRESETS = {
|
|
729
|
-
"gpt-5.3": {
|
|
730
|
-
name: "GPT-5.3",
|
|
731
|
-
pricePer1KInputTokens: 2e-3,
|
|
732
|
-
pricePer1KOutputTokens: 8e-3,
|
|
733
|
-
contextTier: "frontier",
|
|
734
|
-
typicalQueriesPerDevPerDay: 100
|
|
735
|
-
},
|
|
736
|
-
"claude-4.6": {
|
|
737
|
-
name: "Claude 4.6",
|
|
738
|
-
pricePer1KInputTokens: 15e-4,
|
|
739
|
-
pricePer1KOutputTokens: 75e-4,
|
|
740
|
-
contextTier: "frontier",
|
|
741
|
-
typicalQueriesPerDevPerDay: 100
|
|
742
|
-
},
|
|
743
|
-
"gemini-3.1": {
|
|
744
|
-
name: "Gemini 3.1 Pro",
|
|
745
|
-
pricePer1KInputTokens: 8e-4,
|
|
746
|
-
pricePer1KOutputTokens: 3e-3,
|
|
747
|
-
contextTier: "frontier",
|
|
748
|
-
typicalQueriesPerDevPerDay: 120
|
|
749
|
-
},
|
|
750
|
-
"gpt-4o": {
|
|
751
|
-
name: "GPT-4o (legacy)",
|
|
752
|
-
pricePer1KInputTokens: 5e-3,
|
|
753
|
-
pricePer1KOutputTokens: 0.015,
|
|
754
|
-
contextTier: "extended",
|
|
755
|
-
typicalQueriesPerDevPerDay: 60
|
|
756
|
-
},
|
|
757
|
-
"claude-3-5-sonnet": {
|
|
758
|
-
name: "Claude 3.5 Sonnet (legacy)",
|
|
759
|
-
pricePer1KInputTokens: 3e-3,
|
|
760
|
-
pricePer1KOutputTokens: 0.015,
|
|
761
|
-
contextTier: "extended",
|
|
762
|
-
typicalQueriesPerDevPerDay: 80
|
|
763
|
-
},
|
|
764
|
-
"gemini-1-5-pro": {
|
|
765
|
-
name: "Gemini 1.5 Pro (legacy)",
|
|
766
|
-
pricePer1KInputTokens: 125e-5,
|
|
767
|
-
pricePer1KOutputTokens: 5e-3,
|
|
768
|
-
contextTier: "frontier",
|
|
769
|
-
typicalQueriesPerDevPerDay: 80
|
|
770
|
-
},
|
|
771
|
-
copilot: {
|
|
772
|
-
name: "GitHub Copilot (subscription)",
|
|
773
|
-
pricePer1KInputTokens: 8e-5,
|
|
774
|
-
pricePer1KOutputTokens: 8e-5,
|
|
775
|
-
contextTier: "frontier",
|
|
776
|
-
typicalQueriesPerDevPerDay: 150
|
|
777
|
-
},
|
|
778
|
-
"cursor-pro": {
|
|
779
|
-
name: "Cursor Pro (subscription)",
|
|
780
|
-
pricePer1KInputTokens: 8e-5,
|
|
781
|
-
pricePer1KOutputTokens: 8e-5,
|
|
782
|
-
contextTier: "frontier",
|
|
783
|
-
typicalQueriesPerDevPerDay: 200
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
function getModelPreset(modelId) {
|
|
787
|
-
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// src/business/cost-metrics.ts
|
|
791
|
-
var DEFAULT_COST_CONFIG = {
|
|
792
|
-
pricePer1KTokens: 5e-3,
|
|
793
|
-
queriesPerDevPerDay: 60,
|
|
794
|
-
developerCount: 5,
|
|
795
|
-
daysPerMonth: 30
|
|
796
|
-
};
|
|
797
|
-
function calculateMonthlyCost(tokenWaste, config = {}) {
|
|
798
|
-
const budget = calculateTokenBudget({
|
|
799
|
-
totalContextTokens: tokenWaste * 2.5,
|
|
800
|
-
wastedTokens: {
|
|
801
|
-
duplication: tokenWaste * 0.7,
|
|
802
|
-
fragmentation: tokenWaste * 0.3,
|
|
803
|
-
chattiness: 0
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
const preset = getModelPreset("claude-4.6");
|
|
807
|
-
return estimateCostFromBudget(budget, preset, config);
|
|
808
|
-
}
|
|
809
|
-
function calculateTokenBudget(params) {
|
|
810
|
-
const { totalContextTokens, wastedTokens } = params;
|
|
811
|
-
const estimatedResponseTokens = params.estimatedResponseTokens ?? totalContextTokens * 0.2;
|
|
812
|
-
const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
|
|
813
|
-
const efficiencyRatio = Math.max(
|
|
814
|
-
0,
|
|
815
|
-
Math.min(
|
|
816
|
-
1,
|
|
817
|
-
(totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
|
|
818
|
-
)
|
|
819
|
-
);
|
|
820
|
-
return {
|
|
821
|
-
totalContextTokens: Math.round(totalContextTokens),
|
|
822
|
-
estimatedResponseTokens: Math.round(estimatedResponseTokens),
|
|
823
|
-
wastedTokens: {
|
|
824
|
-
total: Math.round(totalWaste),
|
|
825
|
-
bySource: {
|
|
826
|
-
duplication: Math.round(wastedTokens.duplication),
|
|
827
|
-
fragmentation: Math.round(wastedTokens.fragmentation),
|
|
828
|
-
chattiness: Math.round(wastedTokens.chattiness)
|
|
829
|
-
}
|
|
830
|
-
},
|
|
831
|
-
efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
|
|
832
|
-
potentialRetrievableTokens: Math.round(totalWaste * 0.8)
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
function estimateCostFromBudget(budget, model, config = {}) {
|
|
836
|
-
const cfg = { ...DEFAULT_COST_CONFIG, ...config };
|
|
837
|
-
const wastePerQuery = budget.wastedTokens.total;
|
|
838
|
-
const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
|
|
839
|
-
const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
|
|
840
|
-
const totalWeight = cfg.developerCount;
|
|
841
|
-
const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
|
|
842
|
-
const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
|
|
843
|
-
let confidence = 0.85;
|
|
844
|
-
if (model.contextTier === "frontier") confidence = 0.7;
|
|
845
|
-
const variance = 0.25;
|
|
846
|
-
const range = [
|
|
847
|
-
Math.round(baseCost * (1 - variance) * 100) / 100,
|
|
848
|
-
Math.round(baseCost * (1 + variance) * 100) / 100
|
|
849
|
-
];
|
|
850
|
-
return {
|
|
851
|
-
total: Math.round(baseCost * 100) / 100,
|
|
852
|
-
range,
|
|
853
|
-
confidence
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// src/business/productivity-metrics.ts
|
|
858
|
-
var SEVERITY_TIME_ESTIMATES = {
|
|
859
|
-
["critical" /* Critical */]: 4,
|
|
860
|
-
["major" /* Major */]: 2,
|
|
861
|
-
["minor" /* Minor */]: 0.5,
|
|
862
|
-
["info" /* Info */]: 0.25
|
|
863
|
-
};
|
|
864
|
-
var DEFAULT_HOURLY_RATE = 75;
|
|
865
|
-
function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
866
|
-
const counts = {
|
|
867
|
-
["critical" /* Critical */]: issues.filter((i) => i.severity === "critical" /* Critical */).length,
|
|
868
|
-
["major" /* Major */]: issues.filter((i) => i.severity === "major" /* Major */).length,
|
|
869
|
-
["minor" /* Minor */]: issues.filter((i) => i.severity === "minor" /* Minor */).length,
|
|
870
|
-
["info" /* Info */]: issues.filter((i) => i.severity === "info" /* Info */).length
|
|
871
|
-
};
|
|
872
|
-
const hours = {
|
|
873
|
-
["critical" /* Critical */]: counts["critical" /* Critical */] * SEVERITY_TIME_ESTIMATES["critical" /* Critical */],
|
|
874
|
-
["major" /* Major */]: counts["major" /* Major */] * SEVERITY_TIME_ESTIMATES["major" /* Major */],
|
|
875
|
-
["minor" /* Minor */]: counts["minor" /* Minor */] * SEVERITY_TIME_ESTIMATES["minor" /* Minor */],
|
|
876
|
-
["info" /* Info */]: counts["info" /* Info */] * SEVERITY_TIME_ESTIMATES["info" /* Info */]
|
|
877
|
-
};
|
|
878
|
-
const totalHours = hours["critical" /* Critical */] + hours["major" /* Major */] + hours["minor" /* Minor */] + hours["info" /* Info */];
|
|
879
|
-
const totalCost = totalHours * hourlyRate;
|
|
880
|
-
return {
|
|
881
|
-
totalHours: Math.round(totalHours * 10) / 10,
|
|
882
|
-
hourlyRate,
|
|
883
|
-
totalCost: Math.round(totalCost),
|
|
884
|
-
bySeverity: {
|
|
885
|
-
["critical" /* Critical */]: {
|
|
886
|
-
hours: Math.round(hours["critical" /* Critical */] * 10) / 10,
|
|
887
|
-
cost: Math.round(hours["critical" /* Critical */] * hourlyRate)
|
|
888
|
-
},
|
|
889
|
-
["major" /* Major */]: {
|
|
890
|
-
hours: Math.round(hours["major" /* Major */] * 10) / 10,
|
|
891
|
-
cost: Math.round(hours["major" /* Major */] * hourlyRate)
|
|
892
|
-
},
|
|
893
|
-
["minor" /* Minor */]: {
|
|
894
|
-
hours: Math.round(hours["minor" /* Minor */] * 10) / 10,
|
|
895
|
-
cost: Math.round(hours["minor" /* Minor */] * hourlyRate)
|
|
896
|
-
},
|
|
897
|
-
["info" /* Info */]: {
|
|
898
|
-
hours: Math.round(hours["info" /* Info */] * 10) / 10,
|
|
899
|
-
cost: Math.round(hours["info" /* Info */] * hourlyRate)
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
function predictAcceptanceRate(toolOutputs) {
|
|
905
|
-
const factors = [];
|
|
906
|
-
const baseRate = 0.3;
|
|
907
|
-
const patterns = toolOutputs.get("pattern-detect");
|
|
908
|
-
if (patterns) {
|
|
909
|
-
factors.push({
|
|
910
|
-
name: "Semantic Duplication",
|
|
911
|
-
impact: Math.round((patterns.score - 50) * 3e-3 * 100)
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
const context = toolOutputs.get("context-analyzer");
|
|
915
|
-
if (context) {
|
|
916
|
-
factors.push({
|
|
917
|
-
name: "Context Efficiency",
|
|
918
|
-
impact: Math.round((context.score - 50) * 4e-3 * 100)
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
const consistency = toolOutputs.get("consistency");
|
|
922
|
-
if (consistency) {
|
|
923
|
-
factors.push({
|
|
924
|
-
name: "Code Consistency",
|
|
925
|
-
impact: Math.round((consistency.score - 50) * 2e-3 * 100)
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
|
|
929
|
-
if (aiSignalClarity) {
|
|
930
|
-
factors.push({
|
|
931
|
-
name: "AI Signal Clarity",
|
|
932
|
-
impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
936
|
-
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
937
|
-
let confidence = 0.35;
|
|
938
|
-
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
939
|
-
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
940
|
-
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
941
|
-
return { rate: Math.round(rate * 100) / 100, confidence, factors };
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/business/risk-metrics.ts
|
|
945
|
-
function calculateKnowledgeConcentration(params) {
|
|
946
|
-
const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
|
|
947
|
-
const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
|
|
948
|
-
const score = Math.round(
|
|
949
|
-
Math.min(
|
|
950
|
-
100,
|
|
951
|
-
concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
|
|
952
|
-
)
|
|
953
|
-
);
|
|
954
|
-
let rating;
|
|
955
|
-
if (score < 30) rating = "low";
|
|
956
|
-
else if (score < 50) rating = "moderate";
|
|
957
|
-
else if (score < 75) rating = "high";
|
|
958
|
-
else rating = "critical";
|
|
959
|
-
const recommendations = [];
|
|
960
|
-
if (singleAuthorFiles > 0)
|
|
961
|
-
recommendations.push(
|
|
962
|
-
`Distribute knowledge for ${singleAuthorFiles} single-author files.`
|
|
963
|
-
);
|
|
964
|
-
if (orphanFiles > 0)
|
|
965
|
-
recommendations.push(
|
|
966
|
-
`Link ${orphanFiles} orphan files to the rest of the codebase.`
|
|
967
|
-
);
|
|
968
|
-
return {
|
|
969
|
-
score,
|
|
970
|
-
rating,
|
|
971
|
-
recommendations,
|
|
972
|
-
analysis: {
|
|
973
|
-
uniqueConceptFiles,
|
|
974
|
-
totalFiles,
|
|
975
|
-
concentrationRatio,
|
|
976
|
-
singleAuthorFiles,
|
|
977
|
-
orphanFiles
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
function calculateDebtInterest(principal, monthlyGrowthRate) {
|
|
982
|
-
const monthlyRate = monthlyGrowthRate;
|
|
983
|
-
const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
|
|
984
|
-
const monthlyCost = principal * monthlyRate;
|
|
985
|
-
return {
|
|
986
|
-
monthlyRate,
|
|
987
|
-
annualRate,
|
|
988
|
-
principal,
|
|
989
|
-
monthlyCost,
|
|
990
|
-
projections: {
|
|
991
|
-
months6: principal * Math.pow(1 + monthlyRate, 6),
|
|
992
|
-
months12: principal * Math.pow(1 + monthlyRate, 12),
|
|
993
|
-
months24: principal * Math.pow(1 + monthlyRate, 24)
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// src/business/comprehension-metrics.ts
|
|
999
|
-
function calculateTechnicalValueChain(params) {
|
|
1000
|
-
const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
|
|
1001
|
-
const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
|
|
1002
|
-
return {
|
|
1003
|
-
score: Math.round(Math.max(0, Math.min(100, score))),
|
|
1004
|
-
density: businessLogicDensity,
|
|
1005
|
-
complexity: dataAccessComplexity,
|
|
1006
|
-
surface: apiSurfaceArea
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
|
|
1010
|
-
const tierMap = {
|
|
1011
|
-
compact: "compact",
|
|
1012
|
-
standard: "standard",
|
|
1013
|
-
extended: "extended",
|
|
1014
|
-
frontier: "frontier",
|
|
1015
|
-
easy: "frontier",
|
|
1016
|
-
// Map legacy 'easy' to 'frontier'
|
|
1017
|
-
moderate: "standard",
|
|
1018
|
-
difficult: "compact"
|
|
1019
|
-
};
|
|
1020
|
-
const tier = tierMap[modelTier] || "frontier";
|
|
1021
|
-
const threshold = CONTEXT_TIER_THRESHOLDS[tier];
|
|
1022
|
-
const budgetRatio = contextBudget / threshold.idealTokens;
|
|
1023
|
-
const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
|
|
1024
|
-
const finalScore = Math.round(Math.max(0, Math.min(100, score)));
|
|
1025
|
-
let rating;
|
|
1026
|
-
if (finalScore < 20) rating = "trivial";
|
|
1027
|
-
else if (finalScore < 40) rating = "easy";
|
|
1028
|
-
else if (finalScore < 60) rating = "moderate";
|
|
1029
|
-
else if (finalScore < 85) rating = "difficult";
|
|
1030
|
-
else rating = "expert";
|
|
1031
|
-
return {
|
|
1032
|
-
score: finalScore,
|
|
1033
|
-
rating,
|
|
1034
|
-
factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// src/business-metrics.ts
|
|
1039
|
-
function calculateBusinessROI(params) {
|
|
1040
|
-
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
1041
|
-
const devCount = params.developerCount || 5;
|
|
1042
|
-
const budget = calculateTokenBudget({
|
|
1043
|
-
totalContextTokens: params.tokenWaste * 2.5,
|
|
1044
|
-
wastedTokens: {
|
|
1045
|
-
duplication: params.tokenWaste * 0.7,
|
|
1046
|
-
fragmentation: params.tokenWaste * 0.3,
|
|
1047
|
-
chattiness: 0
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
const cost = estimateCostFromBudget(budget, model, {
|
|
1051
|
-
developerCount: devCount
|
|
1052
|
-
});
|
|
1053
|
-
const productivity = calculateProductivityImpact(params.issues);
|
|
1054
|
-
const monthlySavings = cost.total;
|
|
1055
|
-
const productivityGainHours = productivity.totalHours;
|
|
1056
|
-
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
1057
|
-
return {
|
|
1058
|
-
monthlySavings: Math.round(monthlySavings),
|
|
1059
|
-
productivityGainHours: Math.round(productivityGainHours),
|
|
1060
|
-
annualValue: Math.round(annualValue)
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
function formatCost(cost) {
|
|
1064
|
-
if (cost < 1) {
|
|
1065
|
-
return `$${cost.toFixed(2)}`;
|
|
1066
|
-
} else if (cost < 1e3) {
|
|
1067
|
-
return `$${cost.toFixed(0)}`;
|
|
1068
|
-
} else {
|
|
1069
|
-
return `$${(cost / 1e3).toFixed(1)}k`;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
function formatHours(hours) {
|
|
1073
|
-
if (hours < 1) {
|
|
1074
|
-
return `${Math.round(hours * 60)}min`;
|
|
1075
|
-
} else if (hours < 8) {
|
|
1076
|
-
return `${hours.toFixed(1)}h`;
|
|
1077
|
-
} else if (hours < 40) {
|
|
1078
|
-
return `${Math.round(hours)}h`;
|
|
1079
|
-
} else {
|
|
1080
|
-
return `${(hours / 40).toFixed(1)} weeks`;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
function formatAcceptanceRate(rate) {
|
|
1084
|
-
return `${Math.round(rate * 100)}%`;
|
|
1085
|
-
}
|
|
1086
|
-
function generateValueChain(params) {
|
|
1087
|
-
const { issueType, count, severity } = params;
|
|
1088
|
-
const impacts = {
|
|
1089
|
-
"duplicate-pattern": {
|
|
1090
|
-
ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
|
|
1091
|
-
dev: "Developers must manually resolve conflicts between suggested variants.",
|
|
1092
|
-
risk: "high"
|
|
1093
|
-
},
|
|
1094
|
-
"context-fragmentation": {
|
|
1095
|
-
ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
|
|
1096
|
-
dev: "Slower AI responses and increased need for manual context pinning.",
|
|
1097
|
-
risk: "critical"
|
|
1098
|
-
},
|
|
1099
|
-
"naming-inconsistency": {
|
|
1100
|
-
ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
|
|
1101
|
-
dev: "Increased cognitive load for new devs during onboarding.",
|
|
1102
|
-
risk: "moderate"
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
1105
|
-
const impact = impacts[issueType] || {
|
|
1106
|
-
ai: "Reduced suggestion quality.",
|
|
1107
|
-
dev: "Slowed development velocity.",
|
|
1108
|
-
risk: "moderate"
|
|
1109
|
-
};
|
|
1110
|
-
const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
|
|
1111
|
-
return {
|
|
1112
|
-
issueType,
|
|
1113
|
-
technicalMetric: "Issue Count",
|
|
1114
|
-
technicalValue: count,
|
|
1115
|
-
aiImpact: {
|
|
1116
|
-
description: impact.ai,
|
|
1117
|
-
scoreImpact: severity === "critical" ? -15 : -5
|
|
1118
|
-
},
|
|
1119
|
-
developerImpact: {
|
|
1120
|
-
description: impact.dev,
|
|
1121
|
-
productivityLoss
|
|
1122
|
-
},
|
|
1123
|
-
businessOutcome: {
|
|
1124
|
-
directCost: count * 12,
|
|
1125
|
-
opportunityCost: productivityLoss * 15e3,
|
|
1126
|
-
riskLevel: impact.risk
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// src/parsers/typescript-parser.ts
|
|
1132
|
-
import { parse as parse2 } from "@typescript-eslint/typescript-estree";
|
|
1133
|
-
var TypeScriptParser = class {
|
|
1134
|
-
constructor() {
|
|
1135
|
-
this.language = "typescript" /* TypeScript */;
|
|
1136
|
-
this.extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
1137
|
-
}
|
|
1138
|
-
parse(code, filePath) {
|
|
1139
|
-
try {
|
|
1140
|
-
const isJavaScript = filePath.match(/\.jsx?$/i);
|
|
1141
|
-
const ast = parse2(code, {
|
|
1142
|
-
loc: true,
|
|
1143
|
-
range: true,
|
|
1144
|
-
jsx: filePath.match(/\.[jt]sx$/i) !== null,
|
|
1145
|
-
filePath,
|
|
1146
|
-
sourceType: "module",
|
|
1147
|
-
ecmaVersion: "latest"
|
|
1148
|
-
});
|
|
1149
|
-
const imports = this.extractImports(ast);
|
|
1150
|
-
const exports = this.extractExports(ast, imports);
|
|
1151
|
-
return {
|
|
1152
|
-
exports,
|
|
1153
|
-
imports,
|
|
1154
|
-
language: isJavaScript ? "javascript" /* JavaScript */ : "typescript" /* TypeScript */,
|
|
1155
|
-
warnings: []
|
|
1156
|
-
};
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
const err = error;
|
|
1159
|
-
throw new ParseError(
|
|
1160
|
-
`Failed to parse ${filePath}: ${err.message}`,
|
|
1161
|
-
filePath
|
|
1162
|
-
);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
getNamingConventions() {
|
|
1166
|
-
return {
|
|
1167
|
-
// camelCase for variables and functions
|
|
1168
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
1169
|
-
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
1170
|
-
// PascalCase for classes
|
|
1171
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
1172
|
-
// UPPER_CASE for constants
|
|
1173
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
1174
|
-
// Common exceptions (React hooks, etc.)
|
|
1175
|
-
exceptions: ["__filename", "__dirname", "__esModule"]
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
canHandle(filePath) {
|
|
1179
|
-
return this.extensions.some((ext) => filePath.toLowerCase().endsWith(ext));
|
|
1180
|
-
}
|
|
1181
|
-
extractImports(ast) {
|
|
1182
|
-
const imports = [];
|
|
1183
|
-
for (const node of ast.body) {
|
|
1184
|
-
if (node.type === "ImportDeclaration") {
|
|
1185
|
-
const specifiers = [];
|
|
1186
|
-
let isTypeOnly = false;
|
|
1187
|
-
if (node.importKind === "type") {
|
|
1188
|
-
isTypeOnly = true;
|
|
1189
|
-
}
|
|
1190
|
-
for (const spec of node.specifiers) {
|
|
1191
|
-
if (spec.type === "ImportSpecifier") {
|
|
1192
|
-
const imported = spec.imported;
|
|
1193
|
-
const name = imported.type === "Identifier" ? imported.name : imported.value;
|
|
1194
|
-
specifiers.push(name);
|
|
1195
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
1196
|
-
specifiers.push("default");
|
|
1197
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
1198
|
-
specifiers.push("*");
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
imports.push({
|
|
1202
|
-
source: node.source.value,
|
|
1203
|
-
specifiers,
|
|
1204
|
-
isTypeOnly,
|
|
1205
|
-
loc: node.loc ? {
|
|
1206
|
-
start: {
|
|
1207
|
-
line: node.loc.start.line,
|
|
1208
|
-
column: node.loc.start.column
|
|
1209
|
-
},
|
|
1210
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
1211
|
-
} : void 0
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
return imports;
|
|
1216
|
-
}
|
|
1217
|
-
extractExports(ast, imports) {
|
|
1218
|
-
const exports = [];
|
|
1219
|
-
const importedNames = new Set(
|
|
1220
|
-
imports.flatMap(
|
|
1221
|
-
(imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
|
|
1222
|
-
)
|
|
1223
|
-
);
|
|
1224
|
-
for (const node of ast.body) {
|
|
1225
|
-
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
1226
|
-
const extracted = this.extractFromDeclaration(
|
|
1227
|
-
node.declaration,
|
|
1228
|
-
importedNames
|
|
1229
|
-
);
|
|
1230
|
-
exports.push(...extracted);
|
|
1231
|
-
} else if (node.type === "ExportDefaultDeclaration") {
|
|
1232
|
-
let name = "default";
|
|
1233
|
-
let type = "default";
|
|
1234
|
-
if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
1235
|
-
name = node.declaration.id.name;
|
|
1236
|
-
type = "function";
|
|
1237
|
-
} else if (node.declaration.type === "ClassDeclaration" && node.declaration.id) {
|
|
1238
|
-
name = node.declaration.id.name;
|
|
1239
|
-
type = "class";
|
|
1240
|
-
}
|
|
1241
|
-
exports.push({
|
|
1242
|
-
name,
|
|
1243
|
-
type,
|
|
1244
|
-
loc: node.loc ? {
|
|
1245
|
-
start: {
|
|
1246
|
-
line: node.loc.start.line,
|
|
1247
|
-
column: node.loc.start.column
|
|
1248
|
-
},
|
|
1249
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
1250
|
-
} : void 0
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
return exports;
|
|
1255
|
-
}
|
|
1256
|
-
extractFromDeclaration(declaration, importedNames) {
|
|
1257
|
-
const exports = [];
|
|
1258
|
-
if (declaration.type === "FunctionDeclaration" && declaration.id) {
|
|
1259
|
-
exports.push({
|
|
1260
|
-
name: declaration.id.name,
|
|
1261
|
-
type: "function",
|
|
1262
|
-
parameters: declaration.params.map(
|
|
1263
|
-
(p) => p.type === "Identifier" ? p.name : "unknown"
|
|
1264
|
-
),
|
|
1265
|
-
loc: declaration.loc ? {
|
|
1266
|
-
start: {
|
|
1267
|
-
line: declaration.loc.start.line,
|
|
1268
|
-
column: declaration.loc.start.column
|
|
1269
|
-
},
|
|
1270
|
-
end: {
|
|
1271
|
-
line: declaration.loc.end.line,
|
|
1272
|
-
column: declaration.loc.end.column
|
|
1273
|
-
}
|
|
1274
|
-
} : void 0
|
|
1275
|
-
});
|
|
1276
|
-
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
1277
|
-
exports.push({
|
|
1278
|
-
name: declaration.id.name,
|
|
1279
|
-
type: "class",
|
|
1280
|
-
loc: declaration.loc ? {
|
|
1281
|
-
start: {
|
|
1282
|
-
line: declaration.loc.start.line,
|
|
1283
|
-
column: declaration.loc.start.column
|
|
1284
|
-
},
|
|
1285
|
-
end: {
|
|
1286
|
-
line: declaration.loc.end.line,
|
|
1287
|
-
column: declaration.loc.end.column
|
|
1288
|
-
}
|
|
1289
|
-
} : void 0
|
|
1290
|
-
});
|
|
1291
|
-
} else if (declaration.type === "VariableDeclaration") {
|
|
1292
|
-
for (const declarator of declaration.declarations) {
|
|
1293
|
-
if (declarator.id.type === "Identifier") {
|
|
1294
|
-
exports.push({
|
|
1295
|
-
name: declarator.id.name,
|
|
1296
|
-
type: "const",
|
|
1297
|
-
loc: declarator.loc ? {
|
|
1298
|
-
start: {
|
|
1299
|
-
line: declarator.loc.start.line,
|
|
1300
|
-
column: declarator.loc.start.column
|
|
1301
|
-
},
|
|
1302
|
-
end: {
|
|
1303
|
-
line: declarator.loc.end.line,
|
|
1304
|
-
column: declarator.loc.end.column
|
|
1305
|
-
}
|
|
1306
|
-
} : void 0
|
|
1307
|
-
});
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
1311
|
-
exports.push({
|
|
1312
|
-
name: declaration.id.name,
|
|
1313
|
-
type: "type",
|
|
1314
|
-
loc: declaration.loc ? {
|
|
1315
|
-
start: {
|
|
1316
|
-
line: declaration.loc.start.line,
|
|
1317
|
-
column: declaration.loc.start.column
|
|
1318
|
-
},
|
|
1319
|
-
end: {
|
|
1320
|
-
line: declaration.loc.end.line,
|
|
1321
|
-
column: declaration.loc.end.column
|
|
1322
|
-
}
|
|
1323
|
-
} : void 0
|
|
1324
|
-
});
|
|
1325
|
-
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
1326
|
-
exports.push({
|
|
1327
|
-
name: declaration.id.name,
|
|
1328
|
-
type: "interface",
|
|
1329
|
-
loc: declaration.loc ? {
|
|
1330
|
-
start: {
|
|
1331
|
-
line: declaration.loc.start.line,
|
|
1332
|
-
column: declaration.loc.start.column
|
|
1333
|
-
},
|
|
1334
|
-
end: {
|
|
1335
|
-
line: declaration.loc.end.line,
|
|
1336
|
-
column: declaration.loc.end.column
|
|
1337
|
-
}
|
|
1338
|
-
} : void 0
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
return exports;
|
|
1342
|
-
}
|
|
1343
|
-
};
|
|
1344
|
-
|
|
1345
|
-
// src/parsers/python-parser.ts
|
|
1346
|
-
var PythonParser = class {
|
|
1347
|
-
constructor() {
|
|
1348
|
-
this.language = "python" /* Python */;
|
|
1349
|
-
this.extensions = [".py"];
|
|
1350
|
-
this.parser = null;
|
|
1351
|
-
this.initialized = false;
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Initialize the tree-sitter parser
|
|
1355
|
-
* This is async because tree-sitter WASM needs to be loaded
|
|
1356
|
-
*/
|
|
1357
|
-
async initialize() {
|
|
1358
|
-
if (this.initialized) return;
|
|
1359
|
-
try {
|
|
1360
|
-
this.initialized = true;
|
|
1361
|
-
} catch (error) {
|
|
1362
|
-
throw new Error(
|
|
1363
|
-
`Failed to initialize Python parser: ${error.message}`
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
parse(code, filePath) {
|
|
940
|
+
parseRegex(code, filePath) {
|
|
1368
941
|
try {
|
|
1369
942
|
const imports = this.extractImportsRegex(code, filePath);
|
|
1370
943
|
const exports = this.extractExportsRegex(code, filePath);
|
|
@@ -1373,7 +946,7 @@ var PythonParser = class {
|
|
|
1373
946
|
imports,
|
|
1374
947
|
language: "python" /* Python */,
|
|
1375
948
|
warnings: [
|
|
1376
|
-
"Python parsing is currently using regex-based extraction
|
|
949
|
+
"Python parsing is currently using regex-based extraction as tree-sitter wasm was not available."
|
|
1377
950
|
]
|
|
1378
951
|
};
|
|
1379
952
|
} catch (error) {
|
|
@@ -1385,14 +958,10 @@ var PythonParser = class {
|
|
|
1385
958
|
}
|
|
1386
959
|
getNamingConventions() {
|
|
1387
960
|
return {
|
|
1388
|
-
// snake_case for variables and functions
|
|
1389
961
|
variablePattern: /^[a-z_][a-z0-9_]*$/,
|
|
1390
962
|
functionPattern: /^[a-z_][a-z0-9_]*$/,
|
|
1391
|
-
// PascalCase for classes
|
|
1392
963
|
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
1393
|
-
// UPPER_CASE for constants
|
|
1394
964
|
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
1395
|
-
// Python special methods and common exceptions
|
|
1396
965
|
exceptions: [
|
|
1397
966
|
"__init__",
|
|
1398
967
|
"__str__",
|
|
@@ -1414,10 +983,7 @@ var PythonParser = class {
|
|
|
1414
983
|
canHandle(filePath) {
|
|
1415
984
|
return filePath.toLowerCase().endsWith(".py");
|
|
1416
985
|
}
|
|
1417
|
-
|
|
1418
|
-
* Regex-based import extraction (temporary implementation)
|
|
1419
|
-
*/
|
|
1420
|
-
extractImportsRegex(code, filePath) {
|
|
986
|
+
extractImportsRegex(code, _filePath) {
|
|
1421
987
|
const imports = [];
|
|
1422
988
|
const lines = code.split("\n");
|
|
1423
989
|
const importRegex = /^\s*import\s+([a-zA-Z0-9_., ]+)/;
|
|
@@ -1439,202 +1005,892 @@ var PythonParser = class {
|
|
|
1439
1005
|
});
|
|
1440
1006
|
return;
|
|
1441
1007
|
}
|
|
1442
|
-
const fromMatch = line.match(fromImportRegex);
|
|
1443
|
-
if (fromMatch) {
|
|
1444
|
-
const module = fromMatch[1];
|
|
1445
|
-
const imports_str = fromMatch[2];
|
|
1446
|
-
if (imports_str.trim() === "*") {
|
|
1447
|
-
imports.push({
|
|
1448
|
-
source: module,
|
|
1449
|
-
specifiers: ["*"],
|
|
1450
|
-
loc: {
|
|
1451
|
-
start: { line: idx + 1, column: 0 },
|
|
1452
|
-
end: { line: idx + 1, column: line.length }
|
|
1453
|
-
}
|
|
1008
|
+
const fromMatch = line.match(fromImportRegex);
|
|
1009
|
+
if (fromMatch) {
|
|
1010
|
+
const module = fromMatch[1];
|
|
1011
|
+
const imports_str = fromMatch[2];
|
|
1012
|
+
if (imports_str.trim() === "*") {
|
|
1013
|
+
imports.push({
|
|
1014
|
+
source: module,
|
|
1015
|
+
specifiers: ["*"],
|
|
1016
|
+
loc: {
|
|
1017
|
+
start: { line: idx + 1, column: 0 },
|
|
1018
|
+
end: { line: idx + 1, column: line.length }
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
|
|
1024
|
+
imports.push({
|
|
1025
|
+
source: module,
|
|
1026
|
+
specifiers,
|
|
1027
|
+
loc: {
|
|
1028
|
+
start: { line: idx + 1, column: 0 },
|
|
1029
|
+
end: { line: idx + 1, column: line.length }
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
return imports;
|
|
1035
|
+
}
|
|
1036
|
+
extractExportsRegex(code, _filePath) {
|
|
1037
|
+
const exports = [];
|
|
1038
|
+
const lines = code.split("\n");
|
|
1039
|
+
const functionRegex = /^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/;
|
|
1040
|
+
const classRegex = /^class\s+([a-zA-Z_][a-zA-Z0-9_]*)/;
|
|
1041
|
+
const allRegex = /__all__\s*=\s*\[([^\]]+)\]/;
|
|
1042
|
+
let inClass = false;
|
|
1043
|
+
let classIndent = 0;
|
|
1044
|
+
lines.forEach((line, idx) => {
|
|
1045
|
+
const indent = line.search(/\S/);
|
|
1046
|
+
if (line.match(classRegex)) {
|
|
1047
|
+
inClass = true;
|
|
1048
|
+
classIndent = indent;
|
|
1049
|
+
} else if (inClass && indent <= classIndent && line.trim()) {
|
|
1050
|
+
inClass = false;
|
|
1051
|
+
}
|
|
1052
|
+
if (inClass) {
|
|
1053
|
+
const classMatch = line.match(classRegex);
|
|
1054
|
+
if (classMatch) {
|
|
1055
|
+
exports.push({
|
|
1056
|
+
name: classMatch[1],
|
|
1057
|
+
type: "class",
|
|
1058
|
+
loc: {
|
|
1059
|
+
start: { line: idx + 1, column: indent },
|
|
1060
|
+
end: { line: idx + 1, column: line.length }
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const funcMatch = line.match(functionRegex);
|
|
1067
|
+
if (funcMatch && indent === 0) {
|
|
1068
|
+
const name = funcMatch[1];
|
|
1069
|
+
if (!name.startsWith("_") || name.startsWith("__")) {
|
|
1070
|
+
exports.push({
|
|
1071
|
+
name,
|
|
1072
|
+
type: "function",
|
|
1073
|
+
loc: {
|
|
1074
|
+
start: { line: idx + 1, column: 0 },
|
|
1075
|
+
end: { line: idx + 1, column: line.length }
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const allMatch = line.match(allRegex);
|
|
1081
|
+
if (allMatch) {
|
|
1082
|
+
const names = allMatch[1].split(",").map((n) => n.trim().replace(/['"]/g, ""));
|
|
1083
|
+
names.forEach((name) => {
|
|
1084
|
+
if (name && !exports.find((e) => e.name === name)) {
|
|
1085
|
+
exports.push({
|
|
1086
|
+
name,
|
|
1087
|
+
type: "variable",
|
|
1088
|
+
loc: {
|
|
1089
|
+
start: { line: idx + 1, column: 0 },
|
|
1090
|
+
end: { line: idx + 1, column: line.length }
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
return exports;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// src/parsers/parser-factory.ts
|
|
1102
|
+
var ParserFactory = class _ParserFactory {
|
|
1103
|
+
constructor() {
|
|
1104
|
+
this.parsers = /* @__PURE__ */ new Map();
|
|
1105
|
+
this.extensionMap = new Map(
|
|
1106
|
+
Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
|
|
1107
|
+
);
|
|
1108
|
+
this.registerParser(new TypeScriptParser());
|
|
1109
|
+
this.registerParser(new PythonParser());
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Get singleton instance
|
|
1113
|
+
*/
|
|
1114
|
+
static getInstance() {
|
|
1115
|
+
if (!_ParserFactory.instance) {
|
|
1116
|
+
_ParserFactory.instance = new _ParserFactory();
|
|
1117
|
+
}
|
|
1118
|
+
return _ParserFactory.instance;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Register a language parser
|
|
1122
|
+
*/
|
|
1123
|
+
registerParser(parser) {
|
|
1124
|
+
this.parsers.set(parser.language, parser);
|
|
1125
|
+
parser.extensions.forEach((ext) => {
|
|
1126
|
+
const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
|
|
1127
|
+
this.extensionMap.set(ext, lang);
|
|
1128
|
+
this.parsers.set(lang, parser);
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Get parser for a specific language
|
|
1133
|
+
*/
|
|
1134
|
+
getParserForLanguage(language) {
|
|
1135
|
+
return this.parsers.get(language) || null;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Get parser for a file based on its extension
|
|
1139
|
+
*/
|
|
1140
|
+
getParserForFile(filePath) {
|
|
1141
|
+
const ext = this.getFileExtension(filePath);
|
|
1142
|
+
const language = this.extensionMap.get(ext);
|
|
1143
|
+
if (!language) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
return this.parsers.get(language) || null;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Check if a file is supported
|
|
1150
|
+
*/
|
|
1151
|
+
isSupported(filePath) {
|
|
1152
|
+
return this.getParserForFile(filePath) !== null;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Get all registered languages
|
|
1156
|
+
*/
|
|
1157
|
+
getSupportedLanguages() {
|
|
1158
|
+
return Array.from(this.parsers.keys());
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Get all supported file extensions
|
|
1162
|
+
*/
|
|
1163
|
+
getSupportedExtensions() {
|
|
1164
|
+
return Array.from(this.extensionMap.keys());
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get language for a file
|
|
1168
|
+
*/
|
|
1169
|
+
getLanguageForFile(filePath) {
|
|
1170
|
+
const ext = this.getFileExtension(filePath);
|
|
1171
|
+
return this.extensionMap.get(ext) || null;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Extract file extension (with dot)
|
|
1175
|
+
*/
|
|
1176
|
+
getFileExtension(filePath) {
|
|
1177
|
+
const match = filePath.match(/\.[^.]+$/);
|
|
1178
|
+
return match ? match[0].toLowerCase() : "";
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Reset factory (useful for testing)
|
|
1182
|
+
*/
|
|
1183
|
+
static reset() {
|
|
1184
|
+
_ParserFactory.instance = new _ParserFactory();
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Initialize all registered parsers
|
|
1188
|
+
*/
|
|
1189
|
+
async initializeAll() {
|
|
1190
|
+
const promises = Array.from(this.parsers.values()).map(
|
|
1191
|
+
(p) => p.initialize()
|
|
1192
|
+
);
|
|
1193
|
+
await Promise.all(promises);
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
function getParser(filePath) {
|
|
1197
|
+
return ParserFactory.getInstance().getParserForFile(filePath);
|
|
1198
|
+
}
|
|
1199
|
+
async function initializeParsers() {
|
|
1200
|
+
await ParserFactory.getInstance().initializeAll();
|
|
1201
|
+
}
|
|
1202
|
+
function isFileSupported(filePath) {
|
|
1203
|
+
return ParserFactory.getInstance().isSupported(filePath);
|
|
1204
|
+
}
|
|
1205
|
+
function getSupportedLanguages() {
|
|
1206
|
+
return ParserFactory.getInstance().getSupportedLanguages();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/utils/ast-parser.ts
|
|
1210
|
+
function parseFileExports(code, filePath) {
|
|
1211
|
+
const parser = getParser(filePath);
|
|
1212
|
+
if (parser && parser.language === "python" /* Python */) {
|
|
1213
|
+
try {
|
|
1214
|
+
const result = parser.parse(code, filePath);
|
|
1215
|
+
return {
|
|
1216
|
+
exports: result.exports.map((e) => ({
|
|
1217
|
+
name: e.name,
|
|
1218
|
+
type: e.type,
|
|
1219
|
+
imports: e.imports || [],
|
|
1220
|
+
dependencies: e.dependencies || [],
|
|
1221
|
+
typeReferences: e.typeReferences || [],
|
|
1222
|
+
loc: e.loc ? {
|
|
1223
|
+
start: { line: e.loc.start.line, column: e.loc.start.column },
|
|
1224
|
+
end: { line: e.loc.end.line, column: e.loc.end.column }
|
|
1225
|
+
} : void 0
|
|
1226
|
+
})),
|
|
1227
|
+
imports: result.imports.map((i) => ({
|
|
1228
|
+
source: i.source,
|
|
1229
|
+
specifiers: i.specifiers,
|
|
1230
|
+
isTypeOnly: i.isTypeOnly || false
|
|
1231
|
+
}))
|
|
1232
|
+
};
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
return { exports: [], imports: [] };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
try {
|
|
1238
|
+
const ast = parse2(code, {
|
|
1239
|
+
loc: true,
|
|
1240
|
+
range: true,
|
|
1241
|
+
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
1242
|
+
filePath
|
|
1243
|
+
});
|
|
1244
|
+
const imports = extractFileImports(ast);
|
|
1245
|
+
const exports = extractExportsWithDependencies(ast, imports);
|
|
1246
|
+
return { exports, imports };
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
return { exports: [], imports: [] };
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function extractFileImports(ast) {
|
|
1252
|
+
const imports = [];
|
|
1253
|
+
for (const node of ast.body) {
|
|
1254
|
+
if (node.type === "ImportDeclaration") {
|
|
1255
|
+
const source = node.source.value;
|
|
1256
|
+
const specifiers = [];
|
|
1257
|
+
const isTypeOnly = node.importKind === "type";
|
|
1258
|
+
for (const spec of node.specifiers) {
|
|
1259
|
+
if (spec.type === "ImportSpecifier") {
|
|
1260
|
+
const imported = spec.imported;
|
|
1261
|
+
const importName = imported.type === "Identifier" ? imported.name : imported.value;
|
|
1262
|
+
specifiers.push(importName);
|
|
1263
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
1264
|
+
specifiers.push("default");
|
|
1265
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
1266
|
+
specifiers.push("*");
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
imports.push({ source, specifiers, isTypeOnly });
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return imports;
|
|
1273
|
+
}
|
|
1274
|
+
function extractExportsWithDependencies(ast, fileImports) {
|
|
1275
|
+
const exports = [];
|
|
1276
|
+
const importedNames = new Set(fileImports.flatMap((imp) => imp.specifiers));
|
|
1277
|
+
for (const node of ast.body) {
|
|
1278
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
1279
|
+
if (node.declaration) {
|
|
1280
|
+
const exportNodes = extractFromDeclaration(node.declaration);
|
|
1281
|
+
for (const exp of exportNodes) {
|
|
1282
|
+
const usedImports = findUsedImports(node.declaration, importedNames);
|
|
1283
|
+
const typeReferences = extractTypeReferences(node.declaration);
|
|
1284
|
+
exports.push({
|
|
1285
|
+
...exp,
|
|
1286
|
+
imports: usedImports,
|
|
1287
|
+
dependencies: [],
|
|
1288
|
+
typeReferences,
|
|
1289
|
+
loc: node.loc
|
|
1454
1290
|
});
|
|
1455
|
-
return;
|
|
1456
1291
|
}
|
|
1457
|
-
const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
|
|
1458
|
-
imports.push({
|
|
1459
|
-
source: module,
|
|
1460
|
-
specifiers,
|
|
1461
|
-
loc: {
|
|
1462
|
-
start: { line: idx + 1, column: 0 },
|
|
1463
|
-
end: { line: idx + 1, column: line.length }
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
1292
|
}
|
|
1467
|
-
})
|
|
1468
|
-
|
|
1293
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
1294
|
+
const usedImports = findUsedImports(node.declaration, importedNames);
|
|
1295
|
+
const typeReferences = extractTypeReferences(node.declaration);
|
|
1296
|
+
exports.push({
|
|
1297
|
+
name: "default",
|
|
1298
|
+
type: "default",
|
|
1299
|
+
imports: usedImports,
|
|
1300
|
+
dependencies: [],
|
|
1301
|
+
typeReferences,
|
|
1302
|
+
loc: node.loc
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1469
1305
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
const functionRegex = /^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/;
|
|
1483
|
-
const classRegex = /^class\s+([a-zA-Z_][a-zA-Z0-9_]*)/;
|
|
1484
|
-
const allRegex = /__all__\s*=\s*\[([^\]]+)\]/;
|
|
1485
|
-
let inClass = false;
|
|
1486
|
-
let classIndent = 0;
|
|
1487
|
-
lines.forEach((line, idx) => {
|
|
1488
|
-
const indent = line.search(/\S/);
|
|
1489
|
-
if (line.match(classRegex)) {
|
|
1490
|
-
inClass = true;
|
|
1491
|
-
classIndent = indent;
|
|
1492
|
-
} else if (inClass && indent <= classIndent && line.trim()) {
|
|
1493
|
-
inClass = false;
|
|
1306
|
+
return exports;
|
|
1307
|
+
}
|
|
1308
|
+
function extractFromDeclaration(declaration) {
|
|
1309
|
+
const results = [];
|
|
1310
|
+
if (declaration.type === "FunctionDeclaration" && "id" in declaration && declaration.id) {
|
|
1311
|
+
results.push({ name: declaration.id.name, type: "function" });
|
|
1312
|
+
} else if (declaration.type === "ClassDeclaration" && "id" in declaration && declaration.id) {
|
|
1313
|
+
results.push({ name: declaration.id.name, type: "class" });
|
|
1314
|
+
} else if (declaration.type === "VariableDeclaration") {
|
|
1315
|
+
for (const declarator of declaration.declarations) {
|
|
1316
|
+
if (declarator.id.type === "Identifier") {
|
|
1317
|
+
results.push({ name: declarator.id.name, type: "const" });
|
|
1494
1318
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1319
|
+
}
|
|
1320
|
+
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
1321
|
+
results.push({ name: declaration.id.name, type: "type" });
|
|
1322
|
+
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
1323
|
+
results.push({ name: declaration.id.name, type: "interface" });
|
|
1324
|
+
}
|
|
1325
|
+
return results;
|
|
1326
|
+
}
|
|
1327
|
+
function findUsedImports(node, importedNames) {
|
|
1328
|
+
const usedImports = /* @__PURE__ */ new Set();
|
|
1329
|
+
function visit(n) {
|
|
1330
|
+
if (n.type === "Identifier" && importedNames.has(n.name)) {
|
|
1331
|
+
usedImports.add(n.name);
|
|
1332
|
+
}
|
|
1333
|
+
for (const key in n) {
|
|
1334
|
+
const value = n[key];
|
|
1335
|
+
if (value && typeof value === "object") {
|
|
1336
|
+
if (Array.isArray(value)) {
|
|
1337
|
+
value.forEach((child) => {
|
|
1338
|
+
if (child && typeof child === "object" && "type" in child) {
|
|
1339
|
+
visit(child);
|
|
1504
1340
|
}
|
|
1505
1341
|
});
|
|
1342
|
+
} else if ("type" in value) {
|
|
1343
|
+
visit(value);
|
|
1506
1344
|
}
|
|
1507
|
-
return;
|
|
1508
1345
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
visit(node);
|
|
1349
|
+
return Array.from(usedImports);
|
|
1350
|
+
}
|
|
1351
|
+
function calculateImportSimilarity(export1, export2) {
|
|
1352
|
+
if (export1.imports.length === 0 && export2.imports.length === 0) {
|
|
1353
|
+
return 1;
|
|
1354
|
+
}
|
|
1355
|
+
const set1 = new Set(export1.imports);
|
|
1356
|
+
const set2 = new Set(export2.imports);
|
|
1357
|
+
const intersection = new Set([...set1].filter((x) => set2.has(x)));
|
|
1358
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
1359
|
+
return intersection.size / union.size;
|
|
1360
|
+
}
|
|
1361
|
+
function extractTypeReferences(node) {
|
|
1362
|
+
const types = /* @__PURE__ */ new Set();
|
|
1363
|
+
function visit(n) {
|
|
1364
|
+
if (!n || typeof n !== "object") return;
|
|
1365
|
+
if (n.type === "TSTypeReference" && n.typeName) {
|
|
1366
|
+
if (n.typeName.type === "Identifier") {
|
|
1367
|
+
types.add(n.typeName.name);
|
|
1368
|
+
} else if (n.typeName.type === "TSQualifiedName") {
|
|
1369
|
+
let current = n.typeName;
|
|
1370
|
+
while (current.type === "TSQualifiedName") {
|
|
1371
|
+
if (current.right?.type === "Identifier") {
|
|
1372
|
+
types.add(current.right.name);
|
|
1373
|
+
}
|
|
1374
|
+
current = current.left;
|
|
1375
|
+
}
|
|
1376
|
+
if (current.type === "Identifier") {
|
|
1377
|
+
types.add(current.name);
|
|
1521
1378
|
}
|
|
1522
1379
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1380
|
+
}
|
|
1381
|
+
if (n.type === "TSInterfaceHeritage" && n.expression) {
|
|
1382
|
+
if (n.expression.type === "Identifier") {
|
|
1383
|
+
types.add(n.expression.name);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
for (const key of Object.keys(n)) {
|
|
1387
|
+
const value = n[key];
|
|
1388
|
+
if (Array.isArray(value)) {
|
|
1389
|
+
value.forEach(visit);
|
|
1390
|
+
} else if (value && typeof value === "object") {
|
|
1391
|
+
visit(value);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
visit(node);
|
|
1396
|
+
return Array.from(types);
|
|
1397
|
+
}
|
|
1398
|
+
function parseCode(code, language) {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
function extractFunctions(ast) {
|
|
1402
|
+
return [];
|
|
1403
|
+
}
|
|
1404
|
+
function extractImports(ast) {
|
|
1405
|
+
return [];
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/utils/metrics.ts
|
|
1409
|
+
function estimateTokens(text) {
|
|
1410
|
+
return Math.ceil(text.length / 4);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/utils/config.ts
|
|
1414
|
+
import { readFileSync, existsSync as existsSync4 } from "fs";
|
|
1415
|
+
import { join as join4, resolve, dirname as dirname3 } from "path";
|
|
1416
|
+
import { pathToFileURL } from "url";
|
|
1417
|
+
var CONFIG_FILES = [
|
|
1418
|
+
"aiready.json",
|
|
1419
|
+
"aiready.config.json",
|
|
1420
|
+
".aiready.json",
|
|
1421
|
+
".aireadyrc.json",
|
|
1422
|
+
"aiready.config.js",
|
|
1423
|
+
".aireadyrc.js"
|
|
1424
|
+
];
|
|
1425
|
+
async function loadConfig(rootDir) {
|
|
1426
|
+
let currentDir = resolve(rootDir);
|
|
1427
|
+
while (true) {
|
|
1428
|
+
for (const configFile of CONFIG_FILES) {
|
|
1429
|
+
const configPath = join4(currentDir, configFile);
|
|
1430
|
+
if (existsSync4(configPath)) {
|
|
1431
|
+
try {
|
|
1432
|
+
let config;
|
|
1433
|
+
if (configFile.endsWith(".js")) {
|
|
1434
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
1435
|
+
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
1436
|
+
config = module.default || module;
|
|
1437
|
+
} else {
|
|
1438
|
+
const content = readFileSync(configPath, "utf-8");
|
|
1439
|
+
config = JSON.parse(content);
|
|
1440
|
+
}
|
|
1441
|
+
if (typeof config !== "object" || config === null) {
|
|
1442
|
+
throw new Error("Config must be an object");
|
|
1443
|
+
}
|
|
1444
|
+
return config;
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1447
|
+
const e = new Error(
|
|
1448
|
+
`Failed to load config from ${configPath}: ${errorMessage}`
|
|
1449
|
+
);
|
|
1450
|
+
try {
|
|
1451
|
+
e.cause = error instanceof Error ? error : void 0;
|
|
1452
|
+
} catch {
|
|
1536
1453
|
}
|
|
1537
|
-
|
|
1454
|
+
throw e;
|
|
1455
|
+
}
|
|
1538
1456
|
}
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1457
|
+
}
|
|
1458
|
+
const parent = dirname3(currentDir);
|
|
1459
|
+
if (parent === currentDir) {
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
currentDir = parent;
|
|
1541
1463
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
);
|
|
1551
|
-
this.registerParser(new TypeScriptParser());
|
|
1552
|
-
this.registerParser(new PythonParser());
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
function mergeConfigWithDefaults(userConfig, defaults) {
|
|
1467
|
+
if (!userConfig) return defaults;
|
|
1468
|
+
const result = { ...defaults };
|
|
1469
|
+
if (userConfig.scan) {
|
|
1470
|
+
if (userConfig.scan.include) result.include = userConfig.scan.include;
|
|
1471
|
+
if (userConfig.scan.exclude) result.exclude = userConfig.scan.exclude;
|
|
1553
1472
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1473
|
+
const toolOverrides = userConfig.tools && !Array.isArray(userConfig.tools) && typeof userConfig.tools === "object" ? userConfig.tools : userConfig.toolConfigs;
|
|
1474
|
+
if (toolOverrides) {
|
|
1475
|
+
if (!result.toolConfigs) result.toolConfigs = {};
|
|
1476
|
+
for (const [toolName, toolConfig] of Object.entries(toolOverrides)) {
|
|
1477
|
+
if (typeof toolConfig === "object" && toolConfig !== null) {
|
|
1478
|
+
result[toolName] = { ...result[toolName], ...toolConfig };
|
|
1479
|
+
result.toolConfigs[toolName] = {
|
|
1480
|
+
...result.toolConfigs[toolName],
|
|
1481
|
+
...toolConfig
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1560
1484
|
}
|
|
1561
|
-
return _ParserFactory.instance;
|
|
1562
1485
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
*/
|
|
1566
|
-
registerParser(parser) {
|
|
1567
|
-
this.parsers.set(parser.language, parser);
|
|
1568
|
-
parser.extensions.forEach((ext) => {
|
|
1569
|
-
const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
|
|
1570
|
-
this.extensionMap.set(ext, lang);
|
|
1571
|
-
this.parsers.set(lang, parser);
|
|
1572
|
-
});
|
|
1486
|
+
if (userConfig.output) {
|
|
1487
|
+
result.output = { ...result.output, ...userConfig.output };
|
|
1573
1488
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1489
|
+
return result;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/business/pricing-models.ts
|
|
1493
|
+
var MODEL_PRICING_PRESETS = {
|
|
1494
|
+
"gpt-5.3": {
|
|
1495
|
+
name: "GPT-5.3",
|
|
1496
|
+
pricePer1KInputTokens: 2e-3,
|
|
1497
|
+
pricePer1KOutputTokens: 8e-3,
|
|
1498
|
+
contextTier: "frontier",
|
|
1499
|
+
typicalQueriesPerDevPerDay: 100
|
|
1500
|
+
},
|
|
1501
|
+
"claude-4.6": {
|
|
1502
|
+
name: "Claude 4.6",
|
|
1503
|
+
pricePer1KInputTokens: 15e-4,
|
|
1504
|
+
pricePer1KOutputTokens: 75e-4,
|
|
1505
|
+
contextTier: "frontier",
|
|
1506
|
+
typicalQueriesPerDevPerDay: 100
|
|
1507
|
+
},
|
|
1508
|
+
"gemini-3.1": {
|
|
1509
|
+
name: "Gemini 3.1 Pro",
|
|
1510
|
+
pricePer1KInputTokens: 8e-4,
|
|
1511
|
+
pricePer1KOutputTokens: 3e-3,
|
|
1512
|
+
contextTier: "frontier",
|
|
1513
|
+
typicalQueriesPerDevPerDay: 120
|
|
1514
|
+
},
|
|
1515
|
+
"gpt-4o": {
|
|
1516
|
+
name: "GPT-4o (legacy)",
|
|
1517
|
+
pricePer1KInputTokens: 5e-3,
|
|
1518
|
+
pricePer1KOutputTokens: 0.015,
|
|
1519
|
+
contextTier: "extended",
|
|
1520
|
+
typicalQueriesPerDevPerDay: 60
|
|
1521
|
+
},
|
|
1522
|
+
"claude-3-5-sonnet": {
|
|
1523
|
+
name: "Claude 3.5 Sonnet (legacy)",
|
|
1524
|
+
pricePer1KInputTokens: 3e-3,
|
|
1525
|
+
pricePer1KOutputTokens: 0.015,
|
|
1526
|
+
contextTier: "extended",
|
|
1527
|
+
typicalQueriesPerDevPerDay: 80
|
|
1528
|
+
},
|
|
1529
|
+
"gemini-1-5-pro": {
|
|
1530
|
+
name: "Gemini 1.5 Pro (legacy)",
|
|
1531
|
+
pricePer1KInputTokens: 125e-5,
|
|
1532
|
+
pricePer1KOutputTokens: 5e-3,
|
|
1533
|
+
contextTier: "frontier",
|
|
1534
|
+
typicalQueriesPerDevPerDay: 80
|
|
1535
|
+
},
|
|
1536
|
+
copilot: {
|
|
1537
|
+
name: "GitHub Copilot (subscription)",
|
|
1538
|
+
pricePer1KInputTokens: 8e-5,
|
|
1539
|
+
pricePer1KOutputTokens: 8e-5,
|
|
1540
|
+
contextTier: "frontier",
|
|
1541
|
+
typicalQueriesPerDevPerDay: 150
|
|
1542
|
+
},
|
|
1543
|
+
"cursor-pro": {
|
|
1544
|
+
name: "Cursor Pro (subscription)",
|
|
1545
|
+
pricePer1KInputTokens: 8e-5,
|
|
1546
|
+
pricePer1KOutputTokens: 8e-5,
|
|
1547
|
+
contextTier: "frontier",
|
|
1548
|
+
typicalQueriesPerDevPerDay: 200
|
|
1579
1549
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1550
|
+
};
|
|
1551
|
+
function getModelPreset(modelId) {
|
|
1552
|
+
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/business/cost-metrics.ts
|
|
1556
|
+
var DEFAULT_COST_CONFIG = {
|
|
1557
|
+
pricePer1KTokens: 5e-3,
|
|
1558
|
+
queriesPerDevPerDay: 60,
|
|
1559
|
+
developerCount: 5,
|
|
1560
|
+
daysPerMonth: 30
|
|
1561
|
+
};
|
|
1562
|
+
function calculateMonthlyCost(tokenWaste, config = {}) {
|
|
1563
|
+
const budget = calculateTokenBudget({
|
|
1564
|
+
totalContextTokens: tokenWaste * 2.5,
|
|
1565
|
+
wastedTokens: {
|
|
1566
|
+
duplication: tokenWaste * 0.7,
|
|
1567
|
+
fragmentation: tokenWaste * 0.3,
|
|
1568
|
+
chattiness: 0
|
|
1588
1569
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1570
|
+
});
|
|
1571
|
+
const preset = getModelPreset("claude-4.6");
|
|
1572
|
+
return estimateCostFromBudget(budget, preset, config);
|
|
1573
|
+
}
|
|
1574
|
+
function calculateTokenBudget(params) {
|
|
1575
|
+
const { totalContextTokens, wastedTokens } = params;
|
|
1576
|
+
const estimatedResponseTokens = params.estimatedResponseTokens ?? totalContextTokens * 0.2;
|
|
1577
|
+
const totalWaste = wastedTokens.duplication + wastedTokens.fragmentation + wastedTokens.chattiness;
|
|
1578
|
+
const efficiencyRatio = Math.max(
|
|
1579
|
+
0,
|
|
1580
|
+
Math.min(
|
|
1581
|
+
1,
|
|
1582
|
+
(totalContextTokens - totalWaste) / Math.max(1, totalContextTokens)
|
|
1583
|
+
)
|
|
1584
|
+
);
|
|
1585
|
+
return {
|
|
1586
|
+
totalContextTokens: Math.round(totalContextTokens),
|
|
1587
|
+
estimatedResponseTokens: Math.round(estimatedResponseTokens),
|
|
1588
|
+
wastedTokens: {
|
|
1589
|
+
total: Math.round(totalWaste),
|
|
1590
|
+
bySource: {
|
|
1591
|
+
duplication: Math.round(wastedTokens.duplication),
|
|
1592
|
+
fragmentation: Math.round(wastedTokens.fragmentation),
|
|
1593
|
+
chattiness: Math.round(wastedTokens.chattiness)
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
|
|
1597
|
+
potentialRetrievableTokens: Math.round(totalWaste * 0.8)
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function estimateCostFromBudget(budget, model, config = {}) {
|
|
1601
|
+
const cfg = { ...DEFAULT_COST_CONFIG, ...config };
|
|
1602
|
+
const wastePerQuery = budget.wastedTokens.total;
|
|
1603
|
+
const tokensPerDay = wastePerQuery * cfg.queriesPerDevPerDay;
|
|
1604
|
+
const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
|
|
1605
|
+
const totalWeight = cfg.developerCount;
|
|
1606
|
+
const price = config.pricePer1KTokens ?? model.pricePer1KInputTokens;
|
|
1607
|
+
const baseCost = tokensPerMonth / 1e3 * price * totalWeight;
|
|
1608
|
+
let confidence = 0.85;
|
|
1609
|
+
if (model.contextTier === "frontier") confidence = 0.7;
|
|
1610
|
+
const variance = 0.25;
|
|
1611
|
+
const range = [
|
|
1612
|
+
Math.round(baseCost * (1 - variance) * 100) / 100,
|
|
1613
|
+
Math.round(baseCost * (1 + variance) * 100) / 100
|
|
1614
|
+
];
|
|
1615
|
+
return {
|
|
1616
|
+
total: Math.round(baseCost * 100) / 100,
|
|
1617
|
+
range,
|
|
1618
|
+
confidence
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/business/productivity-metrics.ts
|
|
1623
|
+
var SEVERITY_TIME_ESTIMATES = {
|
|
1624
|
+
["critical" /* Critical */]: 4,
|
|
1625
|
+
["major" /* Major */]: 2,
|
|
1626
|
+
["minor" /* Minor */]: 0.5,
|
|
1627
|
+
["info" /* Info */]: 0.25
|
|
1628
|
+
};
|
|
1629
|
+
var DEFAULT_HOURLY_RATE = 75;
|
|
1630
|
+
function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
1631
|
+
const counts = {
|
|
1632
|
+
["critical" /* Critical */]: issues.filter((i) => i.severity === "critical" /* Critical */).length,
|
|
1633
|
+
["major" /* Major */]: issues.filter((i) => i.severity === "major" /* Major */).length,
|
|
1634
|
+
["minor" /* Minor */]: issues.filter((i) => i.severity === "minor" /* Minor */).length,
|
|
1635
|
+
["info" /* Info */]: issues.filter((i) => i.severity === "info" /* Info */).length
|
|
1636
|
+
};
|
|
1637
|
+
const hours = {
|
|
1638
|
+
["critical" /* Critical */]: counts["critical" /* Critical */] * SEVERITY_TIME_ESTIMATES["critical" /* Critical */],
|
|
1639
|
+
["major" /* Major */]: counts["major" /* Major */] * SEVERITY_TIME_ESTIMATES["major" /* Major */],
|
|
1640
|
+
["minor" /* Minor */]: counts["minor" /* Minor */] * SEVERITY_TIME_ESTIMATES["minor" /* Minor */],
|
|
1641
|
+
["info" /* Info */]: counts["info" /* Info */] * SEVERITY_TIME_ESTIMATES["info" /* Info */]
|
|
1642
|
+
};
|
|
1643
|
+
const totalHours = hours["critical" /* Critical */] + hours["major" /* Major */] + hours["minor" /* Minor */] + hours["info" /* Info */];
|
|
1644
|
+
const totalCost = totalHours * hourlyRate;
|
|
1645
|
+
return {
|
|
1646
|
+
totalHours: Math.round(totalHours * 10) / 10,
|
|
1647
|
+
hourlyRate,
|
|
1648
|
+
totalCost: Math.round(totalCost),
|
|
1649
|
+
bySeverity: {
|
|
1650
|
+
["critical" /* Critical */]: {
|
|
1651
|
+
hours: Math.round(hours["critical" /* Critical */] * 10) / 10,
|
|
1652
|
+
cost: Math.round(hours["critical" /* Critical */] * hourlyRate)
|
|
1653
|
+
},
|
|
1654
|
+
["major" /* Major */]: {
|
|
1655
|
+
hours: Math.round(hours["major" /* Major */] * 10) / 10,
|
|
1656
|
+
cost: Math.round(hours["major" /* Major */] * hourlyRate)
|
|
1657
|
+
},
|
|
1658
|
+
["minor" /* Minor */]: {
|
|
1659
|
+
hours: Math.round(hours["minor" /* Minor */] * 10) / 10,
|
|
1660
|
+
cost: Math.round(hours["minor" /* Minor */] * hourlyRate)
|
|
1661
|
+
},
|
|
1662
|
+
["info" /* Info */]: {
|
|
1663
|
+
hours: Math.round(hours["info" /* Info */] * 10) / 10,
|
|
1664
|
+
cost: Math.round(hours["info" /* Info */] * hourlyRate)
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
function predictAcceptanceRate(toolOutputs) {
|
|
1670
|
+
const factors = [];
|
|
1671
|
+
const baseRate = 0.3;
|
|
1672
|
+
const patterns = toolOutputs.get("pattern-detect");
|
|
1673
|
+
if (patterns) {
|
|
1674
|
+
factors.push({
|
|
1675
|
+
name: "Semantic Duplication",
|
|
1676
|
+
impact: Math.round((patterns.score - 50) * 3e-3 * 100)
|
|
1677
|
+
});
|
|
1596
1678
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1679
|
+
const context = toolOutputs.get("context-analyzer");
|
|
1680
|
+
if (context) {
|
|
1681
|
+
factors.push({
|
|
1682
|
+
name: "Context Efficiency",
|
|
1683
|
+
impact: Math.round((context.score - 50) * 4e-3 * 100)
|
|
1684
|
+
});
|
|
1602
1685
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1686
|
+
const consistency = toolOutputs.get("consistency");
|
|
1687
|
+
if (consistency) {
|
|
1688
|
+
factors.push({
|
|
1689
|
+
name: "Code Consistency",
|
|
1690
|
+
impact: Math.round((consistency.score - 50) * 2e-3 * 100)
|
|
1691
|
+
});
|
|
1608
1692
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1693
|
+
const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
|
|
1694
|
+
if (aiSignalClarity) {
|
|
1695
|
+
factors.push({
|
|
1696
|
+
name: "AI Signal Clarity",
|
|
1697
|
+
impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
|
|
1698
|
+
});
|
|
1615
1699
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1700
|
+
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
1701
|
+
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
1702
|
+
let confidence = 0.35;
|
|
1703
|
+
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
1704
|
+
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
1705
|
+
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
1706
|
+
return { rate: Math.round(rate * 100) / 100, confidence, factors };
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/business/risk-metrics.ts
|
|
1710
|
+
function calculateKnowledgeConcentration(params) {
|
|
1711
|
+
const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
|
|
1712
|
+
const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
|
|
1713
|
+
const score = Math.round(
|
|
1714
|
+
Math.min(
|
|
1715
|
+
100,
|
|
1716
|
+
concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
|
|
1717
|
+
)
|
|
1718
|
+
);
|
|
1719
|
+
let rating;
|
|
1720
|
+
if (score < 30) rating = "low";
|
|
1721
|
+
else if (score < 50) rating = "moderate";
|
|
1722
|
+
else if (score < 75) rating = "high";
|
|
1723
|
+
else rating = "critical";
|
|
1724
|
+
const recommendations = [];
|
|
1725
|
+
if (singleAuthorFiles > 0)
|
|
1726
|
+
recommendations.push(
|
|
1727
|
+
`Distribute knowledge for ${singleAuthorFiles} single-author files.`
|
|
1728
|
+
);
|
|
1729
|
+
if (orphanFiles > 0)
|
|
1730
|
+
recommendations.push(
|
|
1731
|
+
`Link ${orphanFiles} orphan files to the rest of the codebase.`
|
|
1732
|
+
);
|
|
1733
|
+
return {
|
|
1734
|
+
score,
|
|
1735
|
+
rating,
|
|
1736
|
+
recommendations,
|
|
1737
|
+
analysis: {
|
|
1738
|
+
uniqueConceptFiles,
|
|
1739
|
+
totalFiles,
|
|
1740
|
+
concentrationRatio,
|
|
1741
|
+
singleAuthorFiles,
|
|
1742
|
+
orphanFiles
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function calculateDebtInterest(principal, monthlyGrowthRate) {
|
|
1747
|
+
const monthlyRate = monthlyGrowthRate;
|
|
1748
|
+
const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
|
|
1749
|
+
const monthlyCost = principal * monthlyRate;
|
|
1750
|
+
return {
|
|
1751
|
+
monthlyRate,
|
|
1752
|
+
annualRate,
|
|
1753
|
+
principal,
|
|
1754
|
+
monthlyCost,
|
|
1755
|
+
projections: {
|
|
1756
|
+
months6: principal * Math.pow(1 + monthlyRate, 6),
|
|
1757
|
+
months12: principal * Math.pow(1 + monthlyRate, 12),
|
|
1758
|
+
months24: principal * Math.pow(1 + monthlyRate, 24)
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/business/comprehension-metrics.ts
|
|
1764
|
+
function calculateTechnicalValueChain(params) {
|
|
1765
|
+
const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
|
|
1766
|
+
const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
|
|
1767
|
+
return {
|
|
1768
|
+
score: Math.round(Math.max(0, Math.min(100, score))),
|
|
1769
|
+
density: businessLogicDensity,
|
|
1770
|
+
complexity: dataAccessComplexity,
|
|
1771
|
+
surface: apiSurfaceArea
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
|
|
1775
|
+
const tierMap = {
|
|
1776
|
+
compact: "compact",
|
|
1777
|
+
standard: "standard",
|
|
1778
|
+
extended: "extended",
|
|
1779
|
+
frontier: "frontier",
|
|
1780
|
+
easy: "frontier",
|
|
1781
|
+
// Map legacy 'easy' to 'frontier'
|
|
1782
|
+
moderate: "standard",
|
|
1783
|
+
difficult: "compact"
|
|
1784
|
+
};
|
|
1785
|
+
const tier = tierMap[modelTier] || "frontier";
|
|
1786
|
+
const threshold = CONTEXT_TIER_THRESHOLDS[tier];
|
|
1787
|
+
const budgetRatio = contextBudget / threshold.idealTokens;
|
|
1788
|
+
const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
|
|
1789
|
+
const finalScore = Math.round(Math.max(0, Math.min(100, score)));
|
|
1790
|
+
let rating;
|
|
1791
|
+
if (finalScore < 20) rating = "trivial";
|
|
1792
|
+
else if (finalScore < 40) rating = "easy";
|
|
1793
|
+
else if (finalScore < 60) rating = "moderate";
|
|
1794
|
+
else if (finalScore < 85) rating = "difficult";
|
|
1795
|
+
else rating = "expert";
|
|
1796
|
+
return {
|
|
1797
|
+
score: finalScore,
|
|
1798
|
+
rating,
|
|
1799
|
+
factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// src/business-metrics.ts
|
|
1804
|
+
function calculateBusinessROI(params) {
|
|
1805
|
+
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
1806
|
+
const devCount = params.developerCount || 5;
|
|
1807
|
+
const budget = calculateTokenBudget({
|
|
1808
|
+
totalContextTokens: params.tokenWaste * 2.5,
|
|
1809
|
+
wastedTokens: {
|
|
1810
|
+
duplication: params.tokenWaste * 0.7,
|
|
1811
|
+
fragmentation: params.tokenWaste * 0.3,
|
|
1812
|
+
chattiness: 0
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
const cost = estimateCostFromBudget(budget, model, {
|
|
1816
|
+
developerCount: devCount
|
|
1817
|
+
});
|
|
1818
|
+
const productivity = calculateProductivityImpact(params.issues);
|
|
1819
|
+
const monthlySavings = cost.total;
|
|
1820
|
+
const productivityGainHours = productivity.totalHours;
|
|
1821
|
+
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
1822
|
+
return {
|
|
1823
|
+
monthlySavings: Math.round(monthlySavings),
|
|
1824
|
+
productivityGainHours: Math.round(productivityGainHours),
|
|
1825
|
+
annualValue: Math.round(annualValue)
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function formatCost(cost) {
|
|
1829
|
+
if (cost < 1) {
|
|
1830
|
+
return `$${cost.toFixed(2)}`;
|
|
1831
|
+
} else if (cost < 1e3) {
|
|
1832
|
+
return `$${cost.toFixed(0)}`;
|
|
1833
|
+
} else {
|
|
1834
|
+
return `$${(cost / 1e3).toFixed(1)}k`;
|
|
1622
1835
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1836
|
+
}
|
|
1837
|
+
function formatHours(hours) {
|
|
1838
|
+
if (hours < 1) {
|
|
1839
|
+
return `${Math.round(hours * 60)}min`;
|
|
1840
|
+
} else if (hours < 8) {
|
|
1841
|
+
return `${hours.toFixed(1)}h`;
|
|
1842
|
+
} else if (hours < 40) {
|
|
1843
|
+
return `${Math.round(hours)}h`;
|
|
1844
|
+
} else {
|
|
1845
|
+
return `${(hours / 40).toFixed(1)} weeks`;
|
|
1628
1846
|
}
|
|
1629
|
-
};
|
|
1630
|
-
function getParser(filePath) {
|
|
1631
|
-
return ParserFactory.getInstance().getParserForFile(filePath);
|
|
1632
1847
|
}
|
|
1633
|
-
function
|
|
1634
|
-
return
|
|
1848
|
+
function formatAcceptanceRate(rate) {
|
|
1849
|
+
return `${Math.round(rate * 100)}%`;
|
|
1635
1850
|
}
|
|
1636
|
-
function
|
|
1637
|
-
|
|
1851
|
+
function generateValueChain(params) {
|
|
1852
|
+
const { issueType, count, severity } = params;
|
|
1853
|
+
const impacts = {
|
|
1854
|
+
"duplicate-pattern": {
|
|
1855
|
+
ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
|
|
1856
|
+
dev: "Developers must manually resolve conflicts between suggested variants.",
|
|
1857
|
+
risk: "high"
|
|
1858
|
+
},
|
|
1859
|
+
"context-fragmentation": {
|
|
1860
|
+
ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
|
|
1861
|
+
dev: "Slower AI responses and increased need for manual context pinning.",
|
|
1862
|
+
risk: "critical"
|
|
1863
|
+
},
|
|
1864
|
+
"naming-inconsistency": {
|
|
1865
|
+
ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
|
|
1866
|
+
dev: "Increased cognitive load for new devs during onboarding.",
|
|
1867
|
+
risk: "moderate"
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
const impact = impacts[issueType] || {
|
|
1871
|
+
ai: "Reduced suggestion quality.",
|
|
1872
|
+
dev: "Slowed development velocity.",
|
|
1873
|
+
risk: "moderate"
|
|
1874
|
+
};
|
|
1875
|
+
const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
|
|
1876
|
+
return {
|
|
1877
|
+
issueType,
|
|
1878
|
+
technicalMetric: "Issue Count",
|
|
1879
|
+
technicalValue: count,
|
|
1880
|
+
aiImpact: {
|
|
1881
|
+
description: impact.ai,
|
|
1882
|
+
scoreImpact: severity === "critical" ? -15 : -5
|
|
1883
|
+
},
|
|
1884
|
+
developerImpact: {
|
|
1885
|
+
description: impact.dev,
|
|
1886
|
+
productivityLoss
|
|
1887
|
+
},
|
|
1888
|
+
businessOutcome: {
|
|
1889
|
+
directCost: count * 12,
|
|
1890
|
+
opportunityCost: productivityLoss * 15e3,
|
|
1891
|
+
riskLevel: impact.risk
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1638
1894
|
}
|
|
1639
1895
|
|
|
1640
1896
|
// src/metrics/remediation-utils.ts
|
|
@@ -2477,14 +2733,14 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2477
2733
|
}
|
|
2478
2734
|
|
|
2479
2735
|
// src/utils/history.ts
|
|
2480
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
2481
|
-
import { join as
|
|
2736
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
2737
|
+
import { join as join5, dirname as dirname4 } from "path";
|
|
2482
2738
|
function getHistoryPath(rootDir) {
|
|
2483
|
-
return
|
|
2739
|
+
return join5(rootDir, ".aiready", "history.json");
|
|
2484
2740
|
}
|
|
2485
2741
|
function loadScoreHistory(rootDir) {
|
|
2486
2742
|
const historyPath = getHistoryPath(rootDir);
|
|
2487
|
-
if (!
|
|
2743
|
+
if (!existsSync5(historyPath)) {
|
|
2488
2744
|
return [];
|
|
2489
2745
|
}
|
|
2490
2746
|
try {
|
|
@@ -2498,7 +2754,7 @@ function loadScoreHistory(rootDir) {
|
|
|
2498
2754
|
function saveScoreEntry(rootDir, entry) {
|
|
2499
2755
|
const historyPath = getHistoryPath(rootDir);
|
|
2500
2756
|
const historyDir = dirname4(historyPath);
|
|
2501
|
-
if (!
|
|
2757
|
+
if (!existsSync5(historyDir)) {
|
|
2502
2758
|
mkdirSync2(historyDir, { recursive: true });
|
|
2503
2759
|
}
|
|
2504
2760
|
const history = loadScoreHistory(rootDir);
|
|
@@ -2545,7 +2801,7 @@ function exportHistory(rootDir, format = "json") {
|
|
|
2545
2801
|
}
|
|
2546
2802
|
function clearHistory(rootDir) {
|
|
2547
2803
|
const historyPath = getHistoryPath(rootDir);
|
|
2548
|
-
if (
|
|
2804
|
+
if (existsSync5(historyPath)) {
|
|
2549
2805
|
writeFileSync2(historyPath, JSON.stringify([]));
|
|
2550
2806
|
}
|
|
2551
2807
|
}
|
|
@@ -2715,6 +2971,7 @@ export {
|
|
|
2715
2971
|
getToolWeight,
|
|
2716
2972
|
handleCLIError,
|
|
2717
2973
|
handleJSONOutput,
|
|
2974
|
+
initializeParsers,
|
|
2718
2975
|
isFileSupported,
|
|
2719
2976
|
isSourceFile,
|
|
2720
2977
|
loadConfig,
|