@aiready/core 0.23.19 → 0.23.21
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/chunk-2N7ISIKE.mjs +158 -0
- package/dist/chunk-ARUIZO7M.mjs +297 -0
- package/dist/chunk-CYC5EGEI.mjs +297 -0
- package/dist/{chunk-ZB3EHHAG.mjs → chunk-DBOPSRBC.mjs} +88 -26
- package/dist/chunk-EZ7ECLAZ.mjs +299 -0
- package/dist/chunk-FNPULWG7.mjs +248 -0
- package/dist/chunk-FZTFKZUQ.mjs +250 -0
- package/dist/chunk-GTS642BQ.mjs +262 -0
- package/dist/chunk-IXPY5J4K.mjs +248 -0
- package/dist/chunk-JJQLYW6Z.mjs +111 -0
- package/dist/chunk-L6BKANJC.mjs +130 -0
- package/dist/chunk-LXEO5PG3.mjs +292 -0
- package/dist/chunk-LZHO636W.mjs +501 -0
- package/dist/chunk-MTK2IIDZ.mjs +262 -0
- package/dist/chunk-QDCQETSI.mjs +262 -0
- package/dist/chunk-QZNY7B2N.mjs +248 -0
- package/dist/chunk-RCZSMGCX.mjs +250 -0
- package/dist/chunk-SWZOT67M.mjs +250 -0
- package/dist/chunk-U3IY2CFC.mjs +36 -0
- package/dist/chunk-UBCM5Y6R.mjs +275 -0
- package/dist/chunk-UTCRW3N7.mjs +301 -0
- package/dist/{chunk-RMH2TPAT.mjs → chunk-UYLH35LA.mjs} +88 -26
- package/dist/{chunk-TJXR2CHZ.mjs → chunk-WVNVC2PP.mjs} +266 -213
- package/dist/chunk-WYOW6O3P.mjs +114 -0
- package/dist/{chunk-CGOS2J6T.mjs → chunk-YRSSR4KN.mjs} +260 -217
- package/dist/client-2xbeKnrg.d.mts +1291 -0
- package/dist/client-2xbeKnrg.d.ts +1291 -0
- package/dist/client-4HLAGzFg.d.mts +1291 -0
- package/dist/client-4HLAGzFg.d.ts +1291 -0
- package/dist/client-B4TQwNa7.d.mts +1290 -0
- package/dist/client-B4TQwNa7.d.ts +1290 -0
- package/dist/client-Bdi4ty0v.d.mts +1294 -0
- package/dist/client-Bdi4ty0v.d.ts +1294 -0
- package/dist/client-BsKpUH3H.d.mts +1339 -0
- package/dist/client-BsKpUH3H.d.ts +1339 -0
- package/dist/client-Bv1zOaWF.d.mts +1291 -0
- package/dist/client-Bv1zOaWF.d.ts +1291 -0
- package/dist/client-Bz9YJMIX.d.mts +1290 -0
- package/dist/client-Bz9YJMIX.d.ts +1290 -0
- package/dist/client-CBpzm34X.d.mts +1291 -0
- package/dist/client-CBpzm34X.d.ts +1291 -0
- package/dist/client-CNu_tCZZ.d.mts +1305 -0
- package/dist/client-CNu_tCZZ.d.ts +1305 -0
- package/dist/client-CmEvxxQu.d.mts +1339 -0
- package/dist/client-CmEvxxQu.d.ts +1339 -0
- package/dist/client-DGMAxkZc.d.mts +1339 -0
- package/dist/client-DGMAxkZc.d.ts +1339 -0
- package/dist/client-DZq-CqcD.d.mts +1292 -0
- package/dist/client-DZq-CqcD.d.ts +1292 -0
- package/dist/{client-WVCAIWdJ.d.mts → client-DcqGfDTt.d.mts} +318 -226
- package/dist/{client-WVCAIWdJ.d.ts → client-DcqGfDTt.d.ts} +318 -226
- package/dist/{client-DLvFR2qA.d.mts → client-O8RvSRm0.d.mts} +89 -25
- package/dist/{client-DLvFR2qA.d.ts → client-O8RvSRm0.d.ts} +89 -25
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +53 -27
- package/dist/client.mjs +6 -6
- package/dist/csharp-parser-4ZKCSX5B.mjs +9 -0
- package/dist/csharp-parser-5HKICCRR.mjs +9 -0
- package/dist/csharp-parser-JCKXIAJW.mjs +9 -0
- package/dist/go-parser-J4KIH4RG.mjs +9 -0
- package/dist/go-parser-TKXL3DVH.mjs +9 -0
- package/dist/go-parser-XOM232XZ.mjs +9 -0
- package/dist/index.d.mts +332 -54
- package/dist/index.d.ts +332 -54
- package/dist/index.js +3930 -3064
- package/dist/index.mjs +933 -2036
- package/dist/java-parser-3KHXOXRQ.mjs +9 -0
- package/dist/java-parser-MASGS4WB.mjs +9 -0
- package/dist/java-parser-T5LXD63J.mjs +9 -0
- package/dist/python-parser-FNFK2473.mjs +8 -0
- package/dist/typescript-parser-2GGNRNB5.mjs +7 -0
- package/dist/typescript-parser-3ENJ6C7H.mjs +7 -0
- package/dist/typescript-parser-4GI7DPSW.mjs +7 -0
- package/dist/typescript-parser-4H3HUBO4.mjs +7 -0
- package/dist/typescript-parser-K63IVZMF.mjs +7 -0
- package/dist/typescript-parser-ZJKROMQG.mjs +7 -0
- package/package.json +1 -1
- package/dist/chunk-5SHLHMH7.mjs +0 -760
- package/dist/chunk-Q55AMEFV.mjs +0 -760
- package/dist/client-BEoUYNLp.d.mts +0 -1191
- package/dist/client-BEoUYNLp.d.ts +0 -1191
- package/dist/client-BrIMPk89.d.mts +0 -1214
- package/dist/client-BrIMPk89.d.ts +0 -1214
- package/dist/client-C5BuGX4F.d.mts +0 -1205
- package/dist/client-C5BuGX4F.d.ts +0 -1205
- package/dist/client-CKcjnPXt.d.mts +0 -1214
- package/dist/client-CKcjnPXt.d.ts +0 -1214
- package/dist/client-CLulBnie.d.mts +0 -1182
- package/dist/client-CLulBnie.d.ts +0 -1182
- package/dist/client-CQwvp8ep.d.mts +0 -1182
- package/dist/client-CQwvp8ep.d.ts +0 -1182
- package/dist/client-PFPdeo-z.d.mts +0 -1186
- package/dist/client-PFPdeo-z.d.ts +0 -1186
- package/dist/client-wk2fgk1q.d.mts +0 -1184
- package/dist/client-wk2fgk1q.d.ts +0 -1184
package/dist/index.mjs
CHANGED
|
@@ -12,8 +12,6 @@ import {
|
|
|
12
12
|
IssueSchema,
|
|
13
13
|
IssueType,
|
|
14
14
|
IssueTypeSchema,
|
|
15
|
-
LANGUAGE_EXTENSIONS,
|
|
16
|
-
Language,
|
|
17
15
|
LeadSchema,
|
|
18
16
|
LeadSource,
|
|
19
17
|
LeadSourceSchema,
|
|
@@ -23,7 +21,6 @@ import {
|
|
|
23
21
|
MetricsSchema,
|
|
24
22
|
ModelTier,
|
|
25
23
|
ModelTierSchema,
|
|
26
|
-
ParseError,
|
|
27
24
|
ReadinessRating,
|
|
28
25
|
RecommendationPriority,
|
|
29
26
|
SCORING_PROFILES,
|
|
@@ -41,16 +38,47 @@ import {
|
|
|
41
38
|
formatScore,
|
|
42
39
|
formatToolScore,
|
|
43
40
|
generateHTML,
|
|
41
|
+
getPriorityIcon,
|
|
44
42
|
getProjectSizeTier,
|
|
45
43
|
getRating,
|
|
46
44
|
getRatingDisplay,
|
|
45
|
+
getRatingEmoji,
|
|
46
|
+
getRatingLabel,
|
|
47
|
+
getRatingMetadata,
|
|
47
48
|
getRatingSlug,
|
|
48
49
|
getRatingWithContext,
|
|
49
50
|
getRecommendedThreshold,
|
|
51
|
+
getToolEmoji,
|
|
50
52
|
getToolWeight,
|
|
51
53
|
normalizeToolName,
|
|
52
54
|
parseWeightString
|
|
53
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-WVNVC2PP.mjs";
|
|
56
|
+
import {
|
|
57
|
+
TypeScriptParser
|
|
58
|
+
} from "./chunk-UTCRW3N7.mjs";
|
|
59
|
+
import {
|
|
60
|
+
PythonParser
|
|
61
|
+
} from "./chunk-LZHO636W.mjs";
|
|
62
|
+
import {
|
|
63
|
+
JavaParser
|
|
64
|
+
} from "./chunk-SWZOT67M.mjs";
|
|
65
|
+
import {
|
|
66
|
+
CSharpParser
|
|
67
|
+
} from "./chunk-QZNY7B2N.mjs";
|
|
68
|
+
import {
|
|
69
|
+
GoParser
|
|
70
|
+
} from "./chunk-GTS642BQ.mjs";
|
|
71
|
+
import "./chunk-L6BKANJC.mjs";
|
|
72
|
+
import {
|
|
73
|
+
getWasmPath,
|
|
74
|
+
initTreeSitter,
|
|
75
|
+
setupParser
|
|
76
|
+
} from "./chunk-2N7ISIKE.mjs";
|
|
77
|
+
import {
|
|
78
|
+
LANGUAGE_EXTENSIONS,
|
|
79
|
+
Language,
|
|
80
|
+
ParseError
|
|
81
|
+
} from "./chunk-U3IY2CFC.mjs";
|
|
54
82
|
|
|
55
83
|
// src/utils/normalization.ts
|
|
56
84
|
function normalizeIssue(raw) {
|
|
@@ -504,6 +532,30 @@ import {
|
|
|
504
532
|
} from "fs";
|
|
505
533
|
import { join as join2, dirname as dirname2, resolve as resolvePath } from "path";
|
|
506
534
|
import chalk from "chalk";
|
|
535
|
+
function ensureDir(path) {
|
|
536
|
+
const dir = dirname2(path);
|
|
537
|
+
if (!existsSync2(dir)) {
|
|
538
|
+
mkdirSync(dir, { recursive: true });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function normalizeSeverity(s) {
|
|
542
|
+
if (!s) return null;
|
|
543
|
+
const lower = s.toLowerCase();
|
|
544
|
+
if (["critical", "high-risk", "blind-risk"].includes(lower))
|
|
545
|
+
return "critical" /* Critical */;
|
|
546
|
+
if (["major", "moderate-risk"].includes(lower)) return "major" /* Major */;
|
|
547
|
+
if (["minor", "safe"].includes(lower)) return "minor" /* Minor */;
|
|
548
|
+
if (lower === "info") return "info" /* Info */;
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
function getFilesByPattern(dir, pattern) {
|
|
552
|
+
if (!existsSync2(dir)) return [];
|
|
553
|
+
try {
|
|
554
|
+
return readdirSync(dir).filter((f) => pattern.test(f));
|
|
555
|
+
} catch {
|
|
556
|
+
return [];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
507
559
|
function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
|
|
508
560
|
let outputPath;
|
|
509
561
|
if (userPath) {
|
|
@@ -519,10 +571,7 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
|
|
|
519
571
|
const aireadyDir = join2(baseDir, ".aiready");
|
|
520
572
|
outputPath = join2(aireadyDir, defaultFilename);
|
|
521
573
|
}
|
|
522
|
-
|
|
523
|
-
if (!existsSync2(parentDir)) {
|
|
524
|
-
mkdirSync(parentDir, { recursive: true });
|
|
525
|
-
}
|
|
574
|
+
ensureDir(outputPath);
|
|
526
575
|
return outputPath;
|
|
527
576
|
}
|
|
528
577
|
async function loadMergedConfig(directory, defaults, cliOptions) {
|
|
@@ -537,16 +586,24 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
|
|
|
537
586
|
}
|
|
538
587
|
function handleJSONOutput(data, outputFile, successMessage) {
|
|
539
588
|
if (outputFile) {
|
|
540
|
-
|
|
541
|
-
if (!existsSync2(dir)) {
|
|
542
|
-
mkdirSync(dir, { recursive: true });
|
|
543
|
-
}
|
|
589
|
+
ensureDir(outputFile);
|
|
544
590
|
writeFileSync(outputFile, JSON.stringify(data, null, 2));
|
|
545
591
|
console.log(successMessage || `\u2705 Results saved to ${outputFile}`);
|
|
546
592
|
} else {
|
|
547
593
|
console.log(JSON.stringify(data, null, 2));
|
|
548
594
|
}
|
|
549
595
|
}
|
|
596
|
+
function getTerminalDivider(color = chalk.cyan, maxWidth = 60) {
|
|
597
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
598
|
+
const dividerWidth = Math.min(maxWidth, terminalWidth - 2);
|
|
599
|
+
return color("\u2501".repeat(dividerWidth));
|
|
600
|
+
}
|
|
601
|
+
function printTerminalHeader(title, color = chalk.cyan) {
|
|
602
|
+
const divider = getTerminalDivider(color);
|
|
603
|
+
console.log(divider);
|
|
604
|
+
console.log(chalk.bold.white(` ${title.toUpperCase()}`));
|
|
605
|
+
console.log(divider + "\n");
|
|
606
|
+
}
|
|
550
607
|
function handleCLIError(error, commandName) {
|
|
551
608
|
console.error(`\u274C ${commandName} failed:`, error);
|
|
552
609
|
process.exit(1);
|
|
@@ -579,33 +636,30 @@ function emitProgress(processed, total, toolId, message, onProgress, throttleCou
|
|
|
579
636
|
}
|
|
580
637
|
}
|
|
581
638
|
function getSeverityColor(severity, chalkInstance = chalk) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
case "
|
|
585
|
-
case "blind-risk":
|
|
639
|
+
const normalized = normalizeSeverity(severity);
|
|
640
|
+
switch (normalized) {
|
|
641
|
+
case "critical" /* Critical */:
|
|
586
642
|
return chalkInstance.red;
|
|
587
|
-
case "major"
|
|
588
|
-
case "moderate-risk":
|
|
643
|
+
case "major" /* Major */:
|
|
589
644
|
return chalkInstance.yellow;
|
|
590
|
-
case "minor"
|
|
591
|
-
case "safe":
|
|
645
|
+
case "minor" /* Minor */:
|
|
592
646
|
return chalkInstance.green;
|
|
593
|
-
case "info"
|
|
647
|
+
case "info" /* Info */:
|
|
594
648
|
return chalkInstance.blue;
|
|
595
649
|
default:
|
|
596
650
|
return chalkInstance.white;
|
|
597
651
|
}
|
|
598
652
|
}
|
|
599
653
|
function getSeverityValue(s) {
|
|
600
|
-
|
|
601
|
-
switch (
|
|
602
|
-
case "critical"
|
|
654
|
+
const normalized = normalizeSeverity(s);
|
|
655
|
+
switch (normalized) {
|
|
656
|
+
case "critical" /* Critical */:
|
|
603
657
|
return 4;
|
|
604
|
-
case "major"
|
|
658
|
+
case "major" /* Major */:
|
|
605
659
|
return 3;
|
|
606
|
-
case "minor"
|
|
660
|
+
case "minor" /* Minor */:
|
|
607
661
|
return 2;
|
|
608
|
-
case "info"
|
|
662
|
+
case "info" /* Info */:
|
|
609
663
|
return 1;
|
|
610
664
|
default:
|
|
611
665
|
return 0;
|
|
@@ -640,24 +694,15 @@ function getSeverityEnum(s) {
|
|
|
640
694
|
return "major";
|
|
641
695
|
case 2:
|
|
642
696
|
return "minor";
|
|
643
|
-
case 1:
|
|
644
|
-
return "info";
|
|
645
697
|
default:
|
|
646
698
|
return "info";
|
|
647
699
|
}
|
|
648
700
|
}
|
|
649
701
|
function findLatestReport(dirPath) {
|
|
650
702
|
const aireadyDir = resolvePath(dirPath, ".aiready");
|
|
651
|
-
|
|
652
|
-
return null;
|
|
653
|
-
}
|
|
654
|
-
let files = readdirSync(aireadyDir).filter(
|
|
655
|
-
(f) => f.startsWith("aiready-report-") && f.endsWith(".json")
|
|
656
|
-
);
|
|
703
|
+
let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
|
|
657
704
|
if (files.length === 0) {
|
|
658
|
-
files =
|
|
659
|
-
(f) => f.startsWith("aiready-scan-") && f.endsWith(".json")
|
|
660
|
-
);
|
|
705
|
+
files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
|
|
661
706
|
}
|
|
662
707
|
if (files.length === 0) {
|
|
663
708
|
return null;
|
|
@@ -671,14 +716,8 @@ function findLatestReport(dirPath) {
|
|
|
671
716
|
}
|
|
672
717
|
function findLatestScanReport(scanReportsDir, reportFilePrefix) {
|
|
673
718
|
try {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const files = readdirSync(scanReportsDir);
|
|
677
|
-
if (files.length > 0) {
|
|
678
|
-
const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
|
|
679
|
-
reportFiles = files.filter((file) => prefixRegex.test(file));
|
|
680
|
-
}
|
|
681
|
-
}
|
|
719
|
+
const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
|
|
720
|
+
const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
|
|
682
721
|
if (reportFiles.length === 0) return null;
|
|
683
722
|
reportFiles.sort((a, b) => {
|
|
684
723
|
const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
|
|
@@ -692,6 +731,53 @@ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
|
|
|
692
731
|
}
|
|
693
732
|
}
|
|
694
733
|
|
|
734
|
+
// src/utils/cli-action-helpers.ts
|
|
735
|
+
import { resolve as resolvePath2 } from "path";
|
|
736
|
+
function getReportTimestamp() {
|
|
737
|
+
const now = /* @__PURE__ */ new Date();
|
|
738
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
739
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
740
|
+
}
|
|
741
|
+
function handleStandardJSONOutput({
|
|
742
|
+
outputData,
|
|
743
|
+
outputFile,
|
|
744
|
+
resolvedDir,
|
|
745
|
+
prefix = "aiready-report"
|
|
746
|
+
}) {
|
|
747
|
+
const outputPath = resolveOutputPath(
|
|
748
|
+
outputFile,
|
|
749
|
+
`${prefix}-${getReportTimestamp()}.json`,
|
|
750
|
+
resolvedDir
|
|
751
|
+
);
|
|
752
|
+
handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
753
|
+
}
|
|
754
|
+
function resolveOutputFormat(options, config) {
|
|
755
|
+
const format = options.output ?? config.output?.format ?? "console";
|
|
756
|
+
const file = options.outputFile ?? config.output?.file;
|
|
757
|
+
return { format, file };
|
|
758
|
+
}
|
|
759
|
+
async function prepareActionConfig(directory, defaults, cliOptions) {
|
|
760
|
+
const resolvedDir = resolvePath2(process.cwd(), directory ?? ".");
|
|
761
|
+
const finalOptions = await loadMergedConfig(
|
|
762
|
+
resolvedDir,
|
|
763
|
+
defaults,
|
|
764
|
+
cliOptions
|
|
765
|
+
);
|
|
766
|
+
return { resolvedDir, finalOptions };
|
|
767
|
+
}
|
|
768
|
+
function formatStandardReport(params) {
|
|
769
|
+
const { results, report, summary, elapsedTime, score } = params;
|
|
770
|
+
const baseData = report ? { ...report } : { results, summary };
|
|
771
|
+
return {
|
|
772
|
+
...baseData,
|
|
773
|
+
summary: {
|
|
774
|
+
...baseData.summary || summary,
|
|
775
|
+
executionTime: parseFloat(elapsedTime)
|
|
776
|
+
},
|
|
777
|
+
...score && { scoring: score }
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
695
781
|
// src/utils/provider-utils.ts
|
|
696
782
|
function groupIssuesByFile(issues) {
|
|
697
783
|
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
@@ -765,1828 +851,129 @@ function calculateImportSimilarity(export1, export2) {
|
|
|
765
851
|
}
|
|
766
852
|
|
|
767
853
|
// src/utils/dependency-analyzer.ts
|
|
768
|
-
import { parse as parse2 } from "@typescript-eslint/typescript-estree";
|
|
769
|
-
|
|
770
|
-
// src/parsers/typescript-parser.ts
|
|
771
854
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
772
|
-
|
|
855
|
+
|
|
856
|
+
// src/parsers/parser-factory.ts
|
|
857
|
+
var ParserFactory = class _ParserFactory {
|
|
858
|
+
/**
|
|
859
|
+
* Create a new ParserFactory instance
|
|
860
|
+
*/
|
|
773
861
|
constructor() {
|
|
774
|
-
this.
|
|
775
|
-
this.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
loc: true,
|
|
804
|
-
range: true,
|
|
805
|
-
tokens: true,
|
|
806
|
-
comment: true,
|
|
807
|
-
jsx: filePath.endsWith("x")
|
|
808
|
-
});
|
|
809
|
-
const imports = this.extractImports(ast);
|
|
810
|
-
const exports = this.extractExports(ast, code);
|
|
811
|
-
return {
|
|
812
|
-
exports,
|
|
813
|
-
imports,
|
|
814
|
-
language: this.language
|
|
815
|
-
};
|
|
816
|
-
} catch (error) {
|
|
817
|
-
throw new ParseError(error.message, filePath, {
|
|
818
|
-
line: error.lineNumber || 1,
|
|
819
|
-
column: error.column || 0
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
getNamingConventions() {
|
|
824
|
-
return {
|
|
825
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
826
|
-
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
827
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
828
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
829
|
-
typePattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
830
|
-
interfacePattern: /^I?[A-Z][a-zA-Z0-9]*$/
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
analyzeMetadata(node, code) {
|
|
834
|
-
if (!code) return {};
|
|
835
|
-
return {
|
|
836
|
-
isPure: this.isLikelyPure(node),
|
|
837
|
-
hasSideEffects: !this.isLikelyPure(node)
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
extractImports(ast) {
|
|
841
|
-
const imports = [];
|
|
842
|
-
for (const node of ast.body) {
|
|
843
|
-
if (node.type === "ImportDeclaration") {
|
|
844
|
-
const specifiers = [];
|
|
845
|
-
let isTypeOnly = false;
|
|
846
|
-
if (node.importKind === "type") {
|
|
847
|
-
isTypeOnly = true;
|
|
848
|
-
}
|
|
849
|
-
for (const spec of node.specifiers) {
|
|
850
|
-
if (spec.type === "ImportSpecifier") {
|
|
851
|
-
const imported = spec.imported;
|
|
852
|
-
const name = imported.type === "Identifier" ? imported.name : imported.value;
|
|
853
|
-
specifiers.push(name);
|
|
854
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
855
|
-
specifiers.push("default");
|
|
856
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
857
|
-
specifiers.push("*");
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
imports.push({
|
|
861
|
-
source: node.source.value,
|
|
862
|
-
specifiers,
|
|
863
|
-
isTypeOnly,
|
|
864
|
-
loc: node.loc ? {
|
|
865
|
-
start: {
|
|
866
|
-
line: node.loc.start.line,
|
|
867
|
-
column: node.loc.start.column
|
|
868
|
-
},
|
|
869
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
870
|
-
} : void 0
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return imports;
|
|
875
|
-
}
|
|
876
|
-
extractExports(ast, code) {
|
|
877
|
-
const exports = [];
|
|
878
|
-
for (const node of ast.body) {
|
|
879
|
-
if (node.type === "ExportNamedDeclaration") {
|
|
880
|
-
if (node.declaration) {
|
|
881
|
-
const declaration = node.declaration;
|
|
882
|
-
if ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") && declaration.id) {
|
|
883
|
-
exports.push(
|
|
884
|
-
this.createExport(
|
|
885
|
-
declaration.id.name,
|
|
886
|
-
"function",
|
|
887
|
-
node,
|
|
888
|
-
// Pass the outer ExportNamedDeclaration
|
|
889
|
-
code
|
|
890
|
-
)
|
|
891
|
-
);
|
|
892
|
-
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
893
|
-
exports.push(
|
|
894
|
-
this.createExport(
|
|
895
|
-
declaration.id.name,
|
|
896
|
-
"class",
|
|
897
|
-
node,
|
|
898
|
-
// Pass the outer ExportNamedDeclaration
|
|
899
|
-
code
|
|
900
|
-
)
|
|
901
|
-
);
|
|
902
|
-
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
903
|
-
exports.push(
|
|
904
|
-
this.createExport(
|
|
905
|
-
declaration.id.name,
|
|
906
|
-
"type",
|
|
907
|
-
node,
|
|
908
|
-
// Pass the outer ExportNamedDeclaration
|
|
909
|
-
code
|
|
910
|
-
)
|
|
911
|
-
);
|
|
912
|
-
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
913
|
-
exports.push(
|
|
914
|
-
this.createExport(
|
|
915
|
-
declaration.id.name,
|
|
916
|
-
"interface",
|
|
917
|
-
node,
|
|
918
|
-
// Pass the outer ExportNamedDeclaration
|
|
919
|
-
code
|
|
920
|
-
)
|
|
921
|
-
);
|
|
922
|
-
} else if (declaration.type === "VariableDeclaration") {
|
|
923
|
-
for (const decl of declaration.declarations) {
|
|
924
|
-
if (decl.id.type === "Identifier") {
|
|
925
|
-
exports.push(
|
|
926
|
-
this.createExport(
|
|
927
|
-
decl.id.name,
|
|
928
|
-
"const",
|
|
929
|
-
node,
|
|
930
|
-
// Pass the outer ExportNamedDeclaration
|
|
931
|
-
code,
|
|
932
|
-
decl.init
|
|
933
|
-
)
|
|
934
|
-
);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
} else if (node.type === "ExportDefaultDeclaration") {
|
|
940
|
-
exports.push(this.createExport("default", "default", node, code));
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
return exports;
|
|
944
|
-
}
|
|
945
|
-
createExport(name, type, node, code, initializer) {
|
|
946
|
-
const documentation = this.extractDocumentation(node, code);
|
|
947
|
-
let methodCount;
|
|
948
|
-
let propertyCount;
|
|
949
|
-
let parameters;
|
|
950
|
-
let isPrimitive = false;
|
|
951
|
-
if (initializer) {
|
|
952
|
-
if (initializer.type === "Literal" || initializer.type === "TemplateLiteral" && initializer.expressions.length === 0) {
|
|
953
|
-
isPrimitive = true;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
const structNode = node.type === "ExportNamedDeclaration" ? node.declaration : node.type === "ExportDefaultDeclaration" ? node.declaration : node;
|
|
957
|
-
if (structNode.type === "ClassDeclaration" || structNode.type === "TSInterfaceDeclaration") {
|
|
958
|
-
const body = structNode.type === "ClassDeclaration" ? structNode.body.body : structNode.body.body;
|
|
959
|
-
methodCount = body.filter(
|
|
960
|
-
(m) => m.type === "MethodDefinition" || m.type === "TSMethodSignature"
|
|
961
|
-
).length;
|
|
962
|
-
propertyCount = body.filter(
|
|
963
|
-
(m) => m.type === "PropertyDefinition" || m.type === "TSPropertySignature"
|
|
964
|
-
).length;
|
|
965
|
-
if (structNode.type === "ClassDeclaration") {
|
|
966
|
-
const constructor = body.find(
|
|
967
|
-
(m) => m.type === "MethodDefinition" && m.kind === "constructor"
|
|
968
|
-
);
|
|
969
|
-
if (constructor && constructor.value && constructor.value.params) {
|
|
970
|
-
parameters = constructor.value.params.map((p) => {
|
|
971
|
-
if (p.type === "Identifier") return p.name;
|
|
972
|
-
if (p.type === "TSParameterProperty" && p.parameter.type === "Identifier") {
|
|
973
|
-
return p.parameter.name;
|
|
974
|
-
}
|
|
975
|
-
return void 0;
|
|
976
|
-
}).filter(Boolean);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
if (!parameters && (structNode.type === "FunctionDeclaration" || structNode.type === "TSDeclareFunction" || structNode.type === "MethodDefinition")) {
|
|
981
|
-
const funcNode = structNode.type === "MethodDefinition" ? structNode.value : structNode;
|
|
982
|
-
if (funcNode && funcNode.params) {
|
|
983
|
-
parameters = funcNode.params.map((p) => {
|
|
984
|
-
if (p.type === "Identifier") return p.name;
|
|
985
|
-
return void 0;
|
|
986
|
-
}).filter(Boolean);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return {
|
|
990
|
-
name,
|
|
991
|
-
type,
|
|
992
|
-
isPrimitive,
|
|
993
|
-
loc: node.loc ? {
|
|
994
|
-
start: { line: node.loc.start.line, column: node.loc.start.column },
|
|
995
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
996
|
-
} : void 0,
|
|
997
|
-
documentation,
|
|
998
|
-
methodCount,
|
|
999
|
-
propertyCount,
|
|
1000
|
-
parameters,
|
|
1001
|
-
isPure: this.isLikelyPure(node),
|
|
1002
|
-
hasSideEffects: !this.isLikelyPure(node)
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
extractDocumentation(node, code) {
|
|
1006
|
-
if (node.range) {
|
|
1007
|
-
const start = node.range[0];
|
|
1008
|
-
const precedingCode = code.substring(0, start);
|
|
1009
|
-
const jsdocMatch = precedingCode.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
1010
|
-
if (jsdocMatch) {
|
|
1011
|
-
return {
|
|
1012
|
-
content: jsdocMatch[1].trim(),
|
|
1013
|
-
type: "jsdoc"
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return void 0;
|
|
862
|
+
this.parsers = /* @__PURE__ */ new Map();
|
|
863
|
+
this.registeredParsers = /* @__PURE__ */ new Map();
|
|
864
|
+
this.extensionMap = new Map(
|
|
865
|
+
Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
|
|
866
|
+
);
|
|
867
|
+
this.registerLazyParser("typescript" /* TypeScript */, async () => {
|
|
868
|
+
const { TypeScriptParser: TypeScriptParser2 } = await import("./typescript-parser-ZJKROMQG.mjs");
|
|
869
|
+
return new TypeScriptParser2();
|
|
870
|
+
});
|
|
871
|
+
this.registerLazyParser("javascript" /* JavaScript */, async () => {
|
|
872
|
+
const { TypeScriptParser: TypeScriptParser2 } = await import("./typescript-parser-ZJKROMQG.mjs");
|
|
873
|
+
return new TypeScriptParser2();
|
|
874
|
+
});
|
|
875
|
+
this.registerLazyParser("python" /* Python */, async () => {
|
|
876
|
+
const { PythonParser: PythonParser2 } = await import("./python-parser-FNFK2473.mjs");
|
|
877
|
+
return new PythonParser2();
|
|
878
|
+
});
|
|
879
|
+
this.registerLazyParser("java" /* Java */, async () => {
|
|
880
|
+
const { JavaParser: JavaParser2 } = await import("./java-parser-3KHXOXRQ.mjs");
|
|
881
|
+
return new JavaParser2();
|
|
882
|
+
});
|
|
883
|
+
this.registerLazyParser("csharp" /* CSharp */, async () => {
|
|
884
|
+
const { CSharpParser: CSharpParser2 } = await import("./csharp-parser-5HKICCRR.mjs");
|
|
885
|
+
return new CSharpParser2();
|
|
886
|
+
});
|
|
887
|
+
this.registerLazyParser("go" /* Go */, async () => {
|
|
888
|
+
const { GoParser: GoParser2 } = await import("./go-parser-XOM232XZ.mjs");
|
|
889
|
+
return new GoParser2();
|
|
890
|
+
});
|
|
1018
891
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const body = structNode.type === "MethodDefinition" ? structNode.value.body : structNode.body;
|
|
1025
|
-
if (body && body.type === "BlockStatement") {
|
|
1026
|
-
const bodyContent = JSON.stringify(body);
|
|
1027
|
-
if (bodyContent.includes('"name":"console"') || bodyContent.includes('"name":"process"') || bodyContent.includes('"type":"AssignmentExpression"')) {
|
|
1028
|
-
return false;
|
|
1029
|
-
}
|
|
1030
|
-
return true;
|
|
1031
|
-
}
|
|
1032
|
-
return true;
|
|
1033
|
-
}
|
|
1034
|
-
return false;
|
|
892
|
+
/**
|
|
893
|
+
* Register a lazy-loaded parser
|
|
894
|
+
*/
|
|
895
|
+
registerLazyParser(language, loader) {
|
|
896
|
+
this.registeredParsers.set(language, loader);
|
|
1035
897
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
try {
|
|
1045
|
-
let prev = node.previousSibling || null;
|
|
1046
|
-
while (prev && /comment/i.test(prev.type)) {
|
|
1047
|
-
const text = prev.text || "";
|
|
1048
|
-
const loc = {
|
|
1049
|
-
start: {
|
|
1050
|
-
line: prev.startPosition.row + 1,
|
|
1051
|
-
column: prev.startPosition.column
|
|
1052
|
-
},
|
|
1053
|
-
end: {
|
|
1054
|
-
line: prev.endPosition.row + 1,
|
|
1055
|
-
column: prev.endPosition.column
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
if (text.trim().startsWith("/**") || text.trim().startsWith("/*")) {
|
|
1059
|
-
metadata.documentation = {
|
|
1060
|
-
content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
|
|
1061
|
-
type: "comment",
|
|
1062
|
-
loc
|
|
1063
|
-
};
|
|
1064
|
-
break;
|
|
1065
|
-
}
|
|
1066
|
-
if (text.trim().startsWith("///")) {
|
|
1067
|
-
metadata.documentation = {
|
|
1068
|
-
content: text.replace(/^\/\/\//, "").trim(),
|
|
1069
|
-
type: "xml-doc",
|
|
1070
|
-
loc
|
|
1071
|
-
};
|
|
1072
|
-
break;
|
|
1073
|
-
}
|
|
1074
|
-
if (text.trim().startsWith("//")) {
|
|
1075
|
-
metadata.documentation = {
|
|
1076
|
-
content: text.replace(/^\/\//, "").trim(),
|
|
1077
|
-
type: "comment",
|
|
1078
|
-
loc
|
|
1079
|
-
};
|
|
1080
|
-
break;
|
|
1081
|
-
}
|
|
1082
|
-
prev = prev.previousSibling;
|
|
1083
|
-
}
|
|
1084
|
-
if (node.type === "function_definition" || node.type === "class_definition") {
|
|
1085
|
-
const body2 = node.childForFieldName ? node.childForFieldName("body") : node.children.find((c) => c.type === "block");
|
|
1086
|
-
if (body2 && body2.children.length > 0) {
|
|
1087
|
-
const firstStmt = body2.children[0];
|
|
1088
|
-
if (firstStmt.type === "expression_statement" && firstStmt.firstChild?.type === "string") {
|
|
1089
|
-
metadata.documentation = {
|
|
1090
|
-
content: firstStmt.firstChild.text.replace(/['"`]/g, "").trim(),
|
|
1091
|
-
type: "docstring",
|
|
1092
|
-
loc: {
|
|
1093
|
-
start: {
|
|
1094
|
-
line: firstStmt.startPosition.row + 1,
|
|
1095
|
-
column: firstStmt.startPosition.column
|
|
1096
|
-
},
|
|
1097
|
-
end: {
|
|
1098
|
-
line: firstStmt.endPosition.row + 1,
|
|
1099
|
-
column: firstStmt.endPosition.column
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
898
|
+
/**
|
|
899
|
+
* Get the global singleton instance
|
|
900
|
+
*
|
|
901
|
+
* @returns The singleton ParserFactory instance
|
|
902
|
+
*/
|
|
903
|
+
static getInstance() {
|
|
904
|
+
if (!_ParserFactory.instance) {
|
|
905
|
+
_ParserFactory.instance = new _ParserFactory();
|
|
1105
906
|
}
|
|
1106
|
-
|
|
907
|
+
return _ParserFactory.instance;
|
|
1107
908
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
"System.err",
|
|
1118
|
-
"Files.write",
|
|
1119
|
-
"process.exit",
|
|
1120
|
-
"exit("
|
|
1121
|
-
];
|
|
1122
|
-
const signatures = Array.from(
|
|
1123
|
-
/* @__PURE__ */ new Set([...options?.sideEffectSignatures || [], ...defaultSignatures])
|
|
1124
|
-
);
|
|
1125
|
-
const walk = (n) => {
|
|
1126
|
-
try {
|
|
1127
|
-
const t = n.type || "";
|
|
1128
|
-
if (/assign|assignment|assignment_statement|assignment_expression|throw|throw_statement|send_statement|global_statement|nonlocal_statement/i.test(
|
|
1129
|
-
t
|
|
1130
|
-
)) {
|
|
1131
|
-
metadata.isPure = false;
|
|
1132
|
-
metadata.hasSideEffects = true;
|
|
1133
|
-
}
|
|
1134
|
-
const text = n.text || "";
|
|
1135
|
-
for (const s of signatures) {
|
|
1136
|
-
if (text.includes(s)) {
|
|
1137
|
-
metadata.isPure = false;
|
|
1138
|
-
metadata.hasSideEffects = true;
|
|
1139
|
-
break;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1143
|
-
const c = n.child(i);
|
|
1144
|
-
if (c) walk(c);
|
|
1145
|
-
}
|
|
1146
|
-
} catch {
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
const body = node.childForFieldName?.("body") || node.children.find(
|
|
1150
|
-
(c) => /body|block|class_body|declaration_list|function_body/.test(c.type)
|
|
1151
|
-
);
|
|
1152
|
-
if (body) walk(body);
|
|
1153
|
-
return metadata;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// src/parsers/tree-sitter-utils.ts
|
|
1157
|
-
import * as Parser from "web-tree-sitter";
|
|
1158
|
-
import * as path from "path";
|
|
1159
|
-
import * as fs from "fs";
|
|
1160
|
-
var isTreeSitterInitialized = false;
|
|
1161
|
-
async function initTreeSitter() {
|
|
1162
|
-
if (isTreeSitterInitialized) return;
|
|
1163
|
-
try {
|
|
1164
|
-
const wasmPath = getWasmPath("web-tree-sitter");
|
|
1165
|
-
await Parser.Parser.init({
|
|
1166
|
-
locateFile() {
|
|
1167
|
-
return wasmPath || "web-tree-sitter.wasm";
|
|
1168
|
-
}
|
|
909
|
+
/**
|
|
910
|
+
* Register a language parser
|
|
911
|
+
*/
|
|
912
|
+
registerParser(parser) {
|
|
913
|
+
this.parsers.set(parser.language, parser);
|
|
914
|
+
parser.extensions.forEach((ext) => {
|
|
915
|
+
const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
|
|
916
|
+
this.extensionMap.set(ext, lang);
|
|
917
|
+
this.parsers.set(lang, parser);
|
|
1169
918
|
});
|
|
1170
|
-
isTreeSitterInitialized = true;
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
console.error("Failed to initialize web-tree-sitter:", error);
|
|
1173
|
-
isTreeSitterInitialized = true;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
function findInPnpmStore(startDir, fileName, depth = 0) {
|
|
1177
|
-
if (depth > 8) return null;
|
|
1178
|
-
const pnpmDir = path.join(startDir, "node_modules", ".pnpm");
|
|
1179
|
-
if (fs.existsSync(pnpmDir)) {
|
|
1180
|
-
return findFileRecursively(pnpmDir, fileName, 0);
|
|
1181
919
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
for (const entry of entries) {
|
|
1196
|
-
if (entry.isDirectory()) {
|
|
1197
|
-
const found = findFileRecursively(
|
|
1198
|
-
path.join(dir, entry.name),
|
|
1199
|
-
fileName,
|
|
1200
|
-
depth + 1
|
|
1201
|
-
);
|
|
1202
|
-
if (found) return found;
|
|
1203
|
-
}
|
|
920
|
+
/**
|
|
921
|
+
* Get parser for a specific language
|
|
922
|
+
*/
|
|
923
|
+
async getParserForLanguage(language) {
|
|
924
|
+
const parser = this.parsers.get(language);
|
|
925
|
+
if (parser) return parser;
|
|
926
|
+
const loader = this.registeredParsers.get(language);
|
|
927
|
+
if (loader) {
|
|
928
|
+
const loadedParser = await loader();
|
|
929
|
+
this.parsers.set(language, loadedParser);
|
|
930
|
+
return loadedParser;
|
|
1204
931
|
}
|
|
1205
|
-
} catch {
|
|
1206
|
-
}
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
function getWasmPath(language) {
|
|
1210
|
-
const wasmFileName = language === "web-tree-sitter" ? "web-tree-sitter.wasm" : `tree-sitter-${language}.wasm`;
|
|
1211
|
-
const immediatePaths = [
|
|
1212
|
-
path.join(process.cwd(), wasmFileName),
|
|
1213
|
-
path.join(__dirname, wasmFileName),
|
|
1214
|
-
path.join(__dirname, "assets", wasmFileName)
|
|
1215
|
-
];
|
|
1216
|
-
for (const p of immediatePaths) {
|
|
1217
|
-
if (fs.existsSync(p)) return p;
|
|
1218
|
-
}
|
|
1219
|
-
const pnpmPath = findInPnpmStore(__dirname, wasmFileName);
|
|
1220
|
-
if (pnpmPath) return pnpmPath;
|
|
1221
|
-
const pnpmPathCwd = findInPnpmStore(process.cwd(), wasmFileName);
|
|
1222
|
-
if (pnpmPathCwd) return pnpmPathCwd;
|
|
1223
|
-
console.warn(
|
|
1224
|
-
`[Parser] WASM file for ${language} not found. CWD: ${process.cwd()}, DIR: ${__dirname}`
|
|
1225
|
-
);
|
|
1226
|
-
return null;
|
|
1227
|
-
}
|
|
1228
|
-
async function setupParser(language) {
|
|
1229
|
-
await initTreeSitter();
|
|
1230
|
-
const wasmPath = getWasmPath(language);
|
|
1231
|
-
if (!wasmPath) {
|
|
1232
|
-
return null;
|
|
1233
|
-
}
|
|
1234
|
-
try {
|
|
1235
|
-
const parser = new Parser.Parser();
|
|
1236
|
-
const Lang = await Parser.Language.load(wasmPath);
|
|
1237
|
-
parser.setLanguage(Lang);
|
|
1238
|
-
return parser;
|
|
1239
|
-
} catch {
|
|
1240
932
|
return null;
|
|
1241
933
|
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
// src/parsers/base-parser.ts
|
|
1245
|
-
var BaseLanguageParser = class {
|
|
1246
|
-
constructor() {
|
|
1247
|
-
this.parser = null;
|
|
1248
|
-
this.initialized = false;
|
|
1249
|
-
}
|
|
1250
934
|
/**
|
|
1251
|
-
*
|
|
935
|
+
* Get parser for a file based on its extension
|
|
1252
936
|
*/
|
|
1253
|
-
async
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
} catch (error) {
|
|
1259
|
-
console.warn(`Failed to initialize ${this.language} parser:`, error);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
async getAST(code, _filePath) {
|
|
1263
|
-
void _filePath;
|
|
1264
|
-
if (!this.initialized) await this.initialize();
|
|
1265
|
-
if (!this.parser) return null;
|
|
1266
|
-
return this.parser.parse(code);
|
|
1267
|
-
}
|
|
1268
|
-
parse(code, filePath) {
|
|
1269
|
-
if (!this.initialized || !this.parser) {
|
|
1270
|
-
return this.parseRegex(code, filePath);
|
|
1271
|
-
}
|
|
1272
|
-
try {
|
|
1273
|
-
const tree = this.parser.parse(code);
|
|
1274
|
-
if (!tree || tree.rootNode.type === "ERROR" || tree.rootNode.hasError) {
|
|
1275
|
-
return this.parseRegex(code, filePath);
|
|
1276
|
-
}
|
|
1277
|
-
const imports = this.extractImportsAST(tree.rootNode);
|
|
1278
|
-
const exports = this.extractExportsAST(tree.rootNode, code);
|
|
1279
|
-
return {
|
|
1280
|
-
exports,
|
|
1281
|
-
imports,
|
|
1282
|
-
language: this.language,
|
|
1283
|
-
warnings: []
|
|
1284
|
-
};
|
|
1285
|
-
} catch (error) {
|
|
1286
|
-
console.warn(
|
|
1287
|
-
`AST parsing failed for ${filePath}, falling back to regex: ${error.message}`
|
|
1288
|
-
);
|
|
1289
|
-
return this.parseRegex(code, filePath);
|
|
937
|
+
async getParserForFile(filePath) {
|
|
938
|
+
const ext = this.getFileExtension(filePath);
|
|
939
|
+
const language = this.extensionMap.get(ext);
|
|
940
|
+
if (!language) {
|
|
941
|
+
return null;
|
|
1290
942
|
}
|
|
943
|
+
return this.getParserForLanguage(language);
|
|
1291
944
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
945
|
+
/**
|
|
946
|
+
* Check if a file is supported (synchronous check based on extension)
|
|
947
|
+
*/
|
|
948
|
+
isSupported(filePath) {
|
|
949
|
+
const ext = this.getFileExtension(filePath);
|
|
950
|
+
return this.extensionMap.has(ext);
|
|
1295
951
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
952
|
+
/**
|
|
953
|
+
* Get all registered languages
|
|
954
|
+
*/
|
|
955
|
+
getSupportedLanguages() {
|
|
956
|
+
const languages = /* @__PURE__ */ new Set([
|
|
957
|
+
...this.parsers.keys(),
|
|
958
|
+
...this.registeredParsers.keys()
|
|
959
|
+
]);
|
|
960
|
+
return Array.from(languages);
|
|
1304
961
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Get all supported file extensions
|
|
964
|
+
*/
|
|
965
|
+
getSupportedExtensions() {
|
|
966
|
+
return Array.from(this.extensionMap.keys());
|
|
1307
967
|
}
|
|
1308
968
|
/**
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
* @param node - Tree-sitter node to analyze.
|
|
1312
|
-
* @param code - Source code for context.
|
|
1313
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
969
|
+
* Get language for a file
|
|
1314
970
|
*/
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
});
|
|
971
|
+
getLanguageForFile(filePath) {
|
|
972
|
+
const ext = this.getFileExtension(filePath);
|
|
973
|
+
return this.extensionMap.get(ext) || null;
|
|
1319
974
|
}
|
|
1320
975
|
/**
|
|
1321
|
-
* Extract
|
|
1322
|
-
*
|
|
1323
|
-
* @param rootNode - Root node of the Python AST.
|
|
1324
|
-
* @returns Array of discovered FileImport objects.
|
|
1325
|
-
*/
|
|
1326
|
-
extractImportsAST(rootNode) {
|
|
1327
|
-
const imports = [];
|
|
1328
|
-
const processImportNode = (node) => {
|
|
1329
|
-
if (node.type === "import_statement") {
|
|
1330
|
-
for (const child of node.children) {
|
|
1331
|
-
if (child.type === "dotted_name") {
|
|
1332
|
-
const source = child.text;
|
|
1333
|
-
imports.push({
|
|
1334
|
-
source,
|
|
1335
|
-
specifiers: [source],
|
|
1336
|
-
loc: {
|
|
1337
|
-
start: {
|
|
1338
|
-
line: child.startPosition.row + 1,
|
|
1339
|
-
column: child.startPosition.column
|
|
1340
|
-
},
|
|
1341
|
-
end: {
|
|
1342
|
-
line: child.endPosition.row + 1,
|
|
1343
|
-
column: child.endPosition.column
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
});
|
|
1347
|
-
} else if (child.type === "aliased_import") {
|
|
1348
|
-
const nameNode = child.childForFieldName("name");
|
|
1349
|
-
if (nameNode) {
|
|
1350
|
-
const source = nameNode.text;
|
|
1351
|
-
imports.push({
|
|
1352
|
-
source,
|
|
1353
|
-
specifiers: [source],
|
|
1354
|
-
loc: {
|
|
1355
|
-
start: {
|
|
1356
|
-
line: child.startPosition.row + 1,
|
|
1357
|
-
column: child.startPosition.column
|
|
1358
|
-
},
|
|
1359
|
-
end: {
|
|
1360
|
-
line: child.endPosition.row + 1,
|
|
1361
|
-
column: child.endPosition.column
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
} else if (node.type === "import_from_statement") {
|
|
1369
|
-
const moduleNameNode = node.childForFieldName("module_name");
|
|
1370
|
-
if (moduleNameNode) {
|
|
1371
|
-
const source = moduleNameNode.text;
|
|
1372
|
-
const specifiers = [];
|
|
1373
|
-
for (const child of node.children) {
|
|
1374
|
-
if (child.type === "dotted_name" && child !== moduleNameNode) {
|
|
1375
|
-
specifiers.push(child.text);
|
|
1376
|
-
} else if (child.type === "aliased_import") {
|
|
1377
|
-
const nameNode = child.childForFieldName("name");
|
|
1378
|
-
if (nameNode) specifiers.push(nameNode.text);
|
|
1379
|
-
} else if (child.type === "wildcard_import") {
|
|
1380
|
-
specifiers.push("*");
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
if (specifiers.length > 0) {
|
|
1384
|
-
imports.push({
|
|
1385
|
-
source,
|
|
1386
|
-
specifiers,
|
|
1387
|
-
loc: {
|
|
1388
|
-
start: {
|
|
1389
|
-
line: node.startPosition.row + 1,
|
|
1390
|
-
column: node.startPosition.column
|
|
1391
|
-
},
|
|
1392
|
-
end: {
|
|
1393
|
-
line: node.endPosition.row + 1,
|
|
1394
|
-
column: node.endPosition.column
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
for (const node of rootNode.children) {
|
|
1403
|
-
processImportNode(node);
|
|
1404
|
-
}
|
|
1405
|
-
return imports;
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Extract export information using AST walk.
|
|
1409
|
-
*
|
|
1410
|
-
* @param rootNode - Root node of the Python AST.
|
|
1411
|
-
* @param code - Source code for documentation extraction.
|
|
1412
|
-
* @returns Array of discovered ExportInfo objects.
|
|
1413
|
-
*/
|
|
1414
|
-
extractExportsAST(rootNode, code) {
|
|
1415
|
-
const exports = [];
|
|
1416
|
-
for (const node of rootNode.children) {
|
|
1417
|
-
if (node.type === "function_definition") {
|
|
1418
|
-
const nameNode = node.childForFieldName("name");
|
|
1419
|
-
if (nameNode) {
|
|
1420
|
-
const name = nameNode.text;
|
|
1421
|
-
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
1422
|
-
if (!isPrivate) {
|
|
1423
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1424
|
-
exports.push({
|
|
1425
|
-
name,
|
|
1426
|
-
type: "function",
|
|
1427
|
-
loc: {
|
|
1428
|
-
start: {
|
|
1429
|
-
line: node.startPosition.row + 1,
|
|
1430
|
-
column: node.startPosition.column
|
|
1431
|
-
},
|
|
1432
|
-
end: {
|
|
1433
|
-
line: node.endPosition.row + 1,
|
|
1434
|
-
column: node.endPosition.column
|
|
1435
|
-
}
|
|
1436
|
-
},
|
|
1437
|
-
parameters: this.extractParameters(node),
|
|
1438
|
-
...metadata
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
} else if (node.type === "class_definition") {
|
|
1443
|
-
const nameNode = node.childForFieldName("name");
|
|
1444
|
-
if (nameNode) {
|
|
1445
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1446
|
-
exports.push({
|
|
1447
|
-
name: nameNode.text,
|
|
1448
|
-
type: "class",
|
|
1449
|
-
loc: {
|
|
1450
|
-
start: {
|
|
1451
|
-
line: node.startPosition.row + 1,
|
|
1452
|
-
column: node.startPosition.column
|
|
1453
|
-
},
|
|
1454
|
-
end: {
|
|
1455
|
-
line: node.endPosition.row + 1,
|
|
1456
|
-
column: node.endPosition.column
|
|
1457
|
-
}
|
|
1458
|
-
},
|
|
1459
|
-
...metadata
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
} else if (node.type === "expression_statement") {
|
|
1463
|
-
const assignment = node.firstChild;
|
|
1464
|
-
if (assignment && assignment.type === "assignment") {
|
|
1465
|
-
const left = assignment.childForFieldName("left");
|
|
1466
|
-
if (left && left.type === "identifier") {
|
|
1467
|
-
const name = left.text;
|
|
1468
|
-
const isInternal = name === "__all__" || name === "__version__" || name === "__author__";
|
|
1469
|
-
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
1470
|
-
if (!isInternal && !isPrivate) {
|
|
1471
|
-
exports.push({
|
|
1472
|
-
name,
|
|
1473
|
-
type: name === name.toUpperCase() ? "const" : "variable",
|
|
1474
|
-
loc: {
|
|
1475
|
-
start: {
|
|
1476
|
-
line: node.startPosition.row + 1,
|
|
1477
|
-
column: node.startPosition.column
|
|
1478
|
-
},
|
|
1479
|
-
end: {
|
|
1480
|
-
line: node.endPosition.row + 1,
|
|
1481
|
-
column: node.endPosition.column
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
return exports;
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Extract parameter names from a function definition node.
|
|
1494
|
-
*
|
|
1495
|
-
* @param node - Function definition node.
|
|
1496
|
-
* @returns Array of parameter name strings.
|
|
1497
|
-
*/
|
|
1498
|
-
extractParameters(node) {
|
|
1499
|
-
const paramsNode = node.childForFieldName("parameters");
|
|
1500
|
-
if (!paramsNode) return [];
|
|
1501
|
-
return paramsNode.children.filter(
|
|
1502
|
-
(c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter"
|
|
1503
|
-
).map((c) => {
|
|
1504
|
-
if (c.type === "identifier") return c.text;
|
|
1505
|
-
if (c.type === "typed_parameter" || c.type === "default_parameter") {
|
|
1506
|
-
return c.firstChild?.text || "unknown";
|
|
1507
|
-
}
|
|
1508
|
-
return "unknown";
|
|
1509
|
-
});
|
|
1510
|
-
}
|
|
1511
|
-
/**
|
|
1512
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
1513
|
-
*
|
|
1514
|
-
* @param code - Source code content.
|
|
1515
|
-
* @param filePath - Path to the file being parsed.
|
|
1516
|
-
* @returns Consolidated ParseResult.
|
|
1517
|
-
*/
|
|
1518
|
-
parseRegex(code, filePath) {
|
|
1519
|
-
try {
|
|
1520
|
-
const imports = this.extractImportsRegex(code, filePath);
|
|
1521
|
-
const exports = this.extractExportsRegex(code, filePath);
|
|
1522
|
-
return {
|
|
1523
|
-
exports,
|
|
1524
|
-
imports,
|
|
1525
|
-
language: "python" /* Python */,
|
|
1526
|
-
warnings: [
|
|
1527
|
-
"Python parsing is currently using regex-based extraction as tree-sitter wasm was not available."
|
|
1528
|
-
]
|
|
1529
|
-
};
|
|
1530
|
-
} catch (error) {
|
|
1531
|
-
const wrapper = new Error(
|
|
1532
|
-
`Failed to parse Python file ${filePath}: ${error.message}`
|
|
1533
|
-
);
|
|
1534
|
-
wrapper.cause = error;
|
|
1535
|
-
throw wrapper;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
getNamingConventions() {
|
|
1539
|
-
return {
|
|
1540
|
-
variablePattern: /^[a-z_][a-z0-9_]*$/,
|
|
1541
|
-
functionPattern: /^[a-z_][a-z0-9_]*$/,
|
|
1542
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
1543
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
1544
|
-
exceptions: [
|
|
1545
|
-
"__init__",
|
|
1546
|
-
"__str__",
|
|
1547
|
-
"__repr__",
|
|
1548
|
-
"__name__",
|
|
1549
|
-
"__main__",
|
|
1550
|
-
"__file__",
|
|
1551
|
-
"__doc__",
|
|
1552
|
-
"__all__",
|
|
1553
|
-
"__version__",
|
|
1554
|
-
"__author__",
|
|
1555
|
-
"__dict__",
|
|
1556
|
-
"__class__",
|
|
1557
|
-
"__module__",
|
|
1558
|
-
"__bases__"
|
|
1559
|
-
]
|
|
1560
|
-
};
|
|
1561
|
-
}
|
|
1562
|
-
canHandle(filePath) {
|
|
1563
|
-
return filePath.toLowerCase().endsWith(".py");
|
|
1564
|
-
}
|
|
1565
|
-
extractImportsRegex(code, _filePath) {
|
|
1566
|
-
void _filePath;
|
|
1567
|
-
const imports = [];
|
|
1568
|
-
const lines = code.split("\n");
|
|
1569
|
-
const importRegex = /^\s*import\s+([a-zA-Z0-9_., ]+)/;
|
|
1570
|
-
const fromImportRegex = /^\s*from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/;
|
|
1571
|
-
lines.forEach((line, idx) => {
|
|
1572
|
-
if (line.trim().startsWith("#")) return;
|
|
1573
|
-
const importMatch = line.match(importRegex);
|
|
1574
|
-
if (importMatch) {
|
|
1575
|
-
const modules = importMatch[1].split(",").map((m) => m.trim().split(" as ")[0]);
|
|
1576
|
-
modules.forEach((module) => {
|
|
1577
|
-
imports.push({
|
|
1578
|
-
source: module,
|
|
1579
|
-
specifiers: [module],
|
|
1580
|
-
loc: {
|
|
1581
|
-
start: { line: idx + 1, column: 0 },
|
|
1582
|
-
end: { line: idx + 1, column: line.length }
|
|
1583
|
-
}
|
|
1584
|
-
});
|
|
1585
|
-
});
|
|
1586
|
-
return;
|
|
1587
|
-
}
|
|
1588
|
-
const fromMatch = line.match(fromImportRegex);
|
|
1589
|
-
if (fromMatch) {
|
|
1590
|
-
const module = fromMatch[1];
|
|
1591
|
-
const imports_str = fromMatch[2];
|
|
1592
|
-
if (imports_str.trim() === "*") {
|
|
1593
|
-
imports.push({
|
|
1594
|
-
source: module,
|
|
1595
|
-
specifiers: ["*"],
|
|
1596
|
-
loc: {
|
|
1597
|
-
start: { line: idx + 1, column: 0 },
|
|
1598
|
-
end: { line: idx + 1, column: line.length }
|
|
1599
|
-
}
|
|
1600
|
-
});
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
|
|
1604
|
-
imports.push({
|
|
1605
|
-
source: module,
|
|
1606
|
-
specifiers,
|
|
1607
|
-
loc: {
|
|
1608
|
-
start: { line: idx + 1, column: 0 },
|
|
1609
|
-
end: { line: idx + 1, column: line.length }
|
|
1610
|
-
}
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
});
|
|
1614
|
-
return imports;
|
|
1615
|
-
}
|
|
1616
|
-
extractExportsRegex(code, _filePath) {
|
|
1617
|
-
void _filePath;
|
|
1618
|
-
const exports = [];
|
|
1619
|
-
const lines = code.split("\n");
|
|
1620
|
-
const funcRegex = /^def\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
1621
|
-
const classRegex = /^class\s+([a-zA-Z0-9_]+)/;
|
|
1622
|
-
lines.forEach((line, idx) => {
|
|
1623
|
-
const indent = line.search(/\S/);
|
|
1624
|
-
if (indent !== 0) return;
|
|
1625
|
-
const classMatch = line.match(classRegex);
|
|
1626
|
-
if (classMatch) {
|
|
1627
|
-
exports.push({
|
|
1628
|
-
name: classMatch[1],
|
|
1629
|
-
type: "class",
|
|
1630
|
-
visibility: "public",
|
|
1631
|
-
isPure: true,
|
|
1632
|
-
hasSideEffects: false,
|
|
1633
|
-
loc: {
|
|
1634
|
-
start: { line: idx + 1, column: 0 },
|
|
1635
|
-
end: { line: idx + 1, column: line.length }
|
|
1636
|
-
}
|
|
1637
|
-
});
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
const funcMatch = line.match(funcRegex);
|
|
1641
|
-
if (funcMatch) {
|
|
1642
|
-
const name = funcMatch[1];
|
|
1643
|
-
if (name.startsWith("_") && !name.startsWith("__")) return;
|
|
1644
|
-
let docContent;
|
|
1645
|
-
const nextLines = lines.slice(idx + 1, idx + 4);
|
|
1646
|
-
for (const nextLine of nextLines) {
|
|
1647
|
-
const docMatch = nextLine.match(/^\s*"""([\s\S]*?)"""/) || nextLine.match(/^\s*'''([\s\S]*?)'''/);
|
|
1648
|
-
if (docMatch) {
|
|
1649
|
-
docContent = docMatch[1].trim();
|
|
1650
|
-
break;
|
|
1651
|
-
}
|
|
1652
|
-
if (nextLine.trim() && !nextLine.trim().startsWith('"""') && !nextLine.trim().startsWith("'''"))
|
|
1653
|
-
break;
|
|
1654
|
-
}
|
|
1655
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("print(") || idx + 1 < lines.length && lines[idx + 1].includes("print(");
|
|
1656
|
-
exports.push({
|
|
1657
|
-
name,
|
|
1658
|
-
type: "function",
|
|
1659
|
-
visibility: "public",
|
|
1660
|
-
isPure: !isImpure,
|
|
1661
|
-
hasSideEffects: isImpure,
|
|
1662
|
-
documentation: docContent ? { content: docContent, type: "docstring" } : void 0,
|
|
1663
|
-
loc: {
|
|
1664
|
-
start: { line: idx + 1, column: 0 },
|
|
1665
|
-
end: { line: idx + 1, column: line.length }
|
|
1666
|
-
}
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
});
|
|
1670
|
-
return exports;
|
|
1671
|
-
}
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
// src/parsers/shared-parser-utils.ts
|
|
1675
|
-
var SIDE_EFFECT_KEYWORDS = [
|
|
1676
|
-
"print(",
|
|
1677
|
-
"console.",
|
|
1678
|
-
"System.out",
|
|
1679
|
-
"System.err",
|
|
1680
|
-
"fmt.",
|
|
1681
|
-
"File.Write",
|
|
1682
|
-
"Files.write",
|
|
1683
|
-
"os.Exit",
|
|
1684
|
-
"panic(",
|
|
1685
|
-
"throw ",
|
|
1686
|
-
"Logging.",
|
|
1687
|
-
"log."
|
|
1688
|
-
];
|
|
1689
|
-
function analyzeGeneralMetadata(node, code, options = {}) {
|
|
1690
|
-
const metadata = {
|
|
1691
|
-
isPure: true,
|
|
1692
|
-
hasSideEffects: false
|
|
1693
|
-
};
|
|
1694
|
-
try {
|
|
1695
|
-
let prev = node.previousSibling || null;
|
|
1696
|
-
while (prev && /comment/i.test(prev.type)) {
|
|
1697
|
-
const text = prev.text || "";
|
|
1698
|
-
if (text.trim().startsWith("/**")) {
|
|
1699
|
-
metadata.documentation = {
|
|
1700
|
-
content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
|
|
1701
|
-
type: "jsdoc"
|
|
1702
|
-
};
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
if (text.trim().startsWith("///")) {
|
|
1706
|
-
metadata.documentation = {
|
|
1707
|
-
content: text.replace(/^\/\/\//, "").trim(),
|
|
1708
|
-
type: "xml-doc"
|
|
1709
|
-
};
|
|
1710
|
-
break;
|
|
1711
|
-
}
|
|
1712
|
-
if (text.trim().startsWith("//")) {
|
|
1713
|
-
metadata.documentation = {
|
|
1714
|
-
content: text.replace(/^\/\//, "").trim(),
|
|
1715
|
-
type: "comment"
|
|
1716
|
-
};
|
|
1717
|
-
break;
|
|
1718
|
-
}
|
|
1719
|
-
prev = prev.previousSibling;
|
|
1720
|
-
}
|
|
1721
|
-
} catch {
|
|
1722
|
-
}
|
|
1723
|
-
const signatures = [
|
|
1724
|
-
...SIDE_EFFECT_KEYWORDS,
|
|
1725
|
-
...options.sideEffectSignatures || []
|
|
1726
|
-
];
|
|
1727
|
-
const walk = (n) => {
|
|
1728
|
-
if (/assign|assignment|assignment_statement|assignment_expression/i.test(
|
|
1729
|
-
n.type
|
|
1730
|
-
)) {
|
|
1731
|
-
metadata.isPure = false;
|
|
1732
|
-
metadata.hasSideEffects = true;
|
|
1733
|
-
}
|
|
1734
|
-
const text = n.text;
|
|
1735
|
-
for (const sig of signatures) {
|
|
1736
|
-
if (text.includes(sig)) {
|
|
1737
|
-
metadata.isPure = false;
|
|
1738
|
-
metadata.hasSideEffects = true;
|
|
1739
|
-
break;
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
if (!metadata.hasSideEffects) {
|
|
1743
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1744
|
-
const child = n.child(i);
|
|
1745
|
-
if (child) walk(child);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
};
|
|
1749
|
-
walk(node);
|
|
1750
|
-
return metadata;
|
|
1751
|
-
}
|
|
1752
|
-
function extractParameterNames(node) {
|
|
1753
|
-
const params = [];
|
|
1754
|
-
const candidates = [
|
|
1755
|
-
// common field name
|
|
1756
|
-
node.childForFieldName ? node.childForFieldName("parameters") : null,
|
|
1757
|
-
node.childForFieldName ? node.childForFieldName("parameter_list") : null,
|
|
1758
|
-
node.children.find((c) => c.type === "parameter_list") || null,
|
|
1759
|
-
node.children.find((c) => c.type === "parameters") || null,
|
|
1760
|
-
node.children.find((c) => c.type === "formal_parameters") || null,
|
|
1761
|
-
node.children.find((c) => c.type === "formal_parameter") || null
|
|
1762
|
-
];
|
|
1763
|
-
const list = candidates.find(Boolean);
|
|
1764
|
-
if (!list) return params;
|
|
1765
|
-
for (const child of list.children) {
|
|
1766
|
-
if (!child) continue;
|
|
1767
|
-
const id = child.childForFieldName?.("name") || child.children.find(
|
|
1768
|
-
(c) => [
|
|
1769
|
-
"identifier",
|
|
1770
|
-
"variable_name",
|
|
1771
|
-
"name",
|
|
1772
|
-
"parameter",
|
|
1773
|
-
"formal_parameter"
|
|
1774
|
-
].includes(c.type)
|
|
1775
|
-
) || (child.type === "identifier" ? child : void 0);
|
|
1776
|
-
if (id && typeof id.text === "string") params.push(id.text);
|
|
1777
|
-
}
|
|
1778
|
-
return params;
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
// src/parsers/java-parser.ts
|
|
1782
|
-
var JavaParser = class extends BaseLanguageParser {
|
|
1783
|
-
constructor() {
|
|
1784
|
-
super(...arguments);
|
|
1785
|
-
this.language = "java" /* Java */;
|
|
1786
|
-
this.extensions = [".java"];
|
|
1787
|
-
}
|
|
1788
|
-
getParserName() {
|
|
1789
|
-
return "java";
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* Analyze metadata for a Java node (purity, side effects).
|
|
1793
|
-
*
|
|
1794
|
-
* @param node - Tree-sitter node to analyze.
|
|
1795
|
-
* @param code - Source code for context.
|
|
1796
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
1797
|
-
*/
|
|
1798
|
-
analyzeMetadata(node, code) {
|
|
1799
|
-
return analyzeGeneralMetadata(node, code, {
|
|
1800
|
-
sideEffectSignatures: [
|
|
1801
|
-
"System.out",
|
|
1802
|
-
"System.err",
|
|
1803
|
-
"Files.write",
|
|
1804
|
-
"Logging."
|
|
1805
|
-
]
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
parseRegex(code) {
|
|
1809
|
-
const lines = code.split("\n");
|
|
1810
|
-
const exports = [];
|
|
1811
|
-
const imports = [];
|
|
1812
|
-
const importRegex = /^import\s+([a-zA-Z0-9_.]+)/;
|
|
1813
|
-
const classRegex = /^\s*(?:public\s+)?(?:class|interface|enum)\s+([a-zA-Z0-9_]+)/;
|
|
1814
|
-
const methodRegex = /^\s*public\s+(?:static\s+)?[a-zA-Z0-9_<>[\]]+\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
1815
|
-
let currentClassName = "";
|
|
1816
|
-
lines.forEach((line, idx) => {
|
|
1817
|
-
const importMatch = line.match(importRegex);
|
|
1818
|
-
if (importMatch) {
|
|
1819
|
-
const source = importMatch[1];
|
|
1820
|
-
imports.push({
|
|
1821
|
-
source,
|
|
1822
|
-
specifiers: [source.split(".").pop() || source],
|
|
1823
|
-
loc: {
|
|
1824
|
-
start: { line: idx + 1, column: 0 },
|
|
1825
|
-
end: { line: idx + 1, column: line.length }
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
}
|
|
1829
|
-
const classMatch = line.match(classRegex);
|
|
1830
|
-
if (classMatch) {
|
|
1831
|
-
currentClassName = classMatch[1];
|
|
1832
|
-
exports.push({
|
|
1833
|
-
name: currentClassName,
|
|
1834
|
-
type: line.includes("interface") ? "interface" : "class",
|
|
1835
|
-
visibility: "public",
|
|
1836
|
-
isPure: true,
|
|
1837
|
-
hasSideEffects: false,
|
|
1838
|
-
loc: {
|
|
1839
|
-
start: { line: idx + 1, column: 0 },
|
|
1840
|
-
end: { line: idx + 1, column: line.length }
|
|
1841
|
-
}
|
|
1842
|
-
});
|
|
1843
|
-
}
|
|
1844
|
-
const methodMatch = line.match(methodRegex);
|
|
1845
|
-
if (methodMatch && currentClassName) {
|
|
1846
|
-
const name = methodMatch[1];
|
|
1847
|
-
let docContent;
|
|
1848
|
-
const prevLines = lines.slice(Math.max(0, idx - 5), idx);
|
|
1849
|
-
const prevText = prevLines.join("\n");
|
|
1850
|
-
const javadocMatch = prevText.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
1851
|
-
if (javadocMatch) {
|
|
1852
|
-
docContent = javadocMatch[1].replace(/^\s*\*+/gm, "").trim();
|
|
1853
|
-
}
|
|
1854
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("System.out");
|
|
1855
|
-
exports.push({
|
|
1856
|
-
name,
|
|
1857
|
-
type: "function",
|
|
1858
|
-
parentClass: currentClassName,
|
|
1859
|
-
visibility: "public",
|
|
1860
|
-
isPure: !isImpure,
|
|
1861
|
-
hasSideEffects: isImpure,
|
|
1862
|
-
documentation: docContent ? { content: docContent, type: "jsdoc" } : void 0,
|
|
1863
|
-
loc: {
|
|
1864
|
-
start: { line: idx + 1, column: 0 },
|
|
1865
|
-
end: { line: idx + 1, column: line.length }
|
|
1866
|
-
}
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
});
|
|
1870
|
-
return {
|
|
1871
|
-
exports,
|
|
1872
|
-
imports,
|
|
1873
|
-
language: "java" /* Java */,
|
|
1874
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1877
|
-
/**
|
|
1878
|
-
* Extract import information using AST walk.
|
|
1879
|
-
*
|
|
1880
|
-
* @param rootNode - Root node of the Java AST.
|
|
1881
|
-
* @returns Array of discovered FileImport objects.
|
|
1882
|
-
*/
|
|
1883
|
-
extractImportsAST(rootNode) {
|
|
1884
|
-
const imports = [];
|
|
1885
|
-
for (const node of rootNode.children) {
|
|
1886
|
-
if (node.type === "import_declaration") {
|
|
1887
|
-
const sourceArr = [];
|
|
1888
|
-
let isWildcard = false;
|
|
1889
|
-
for (const child of node.children) {
|
|
1890
|
-
if (child.type === "scoped_identifier" || child.type === "identifier") {
|
|
1891
|
-
sourceArr.push(child.text);
|
|
1892
|
-
}
|
|
1893
|
-
if (child.type === "asterisk") isWildcard = true;
|
|
1894
|
-
}
|
|
1895
|
-
const source = sourceArr.join(".");
|
|
1896
|
-
if (source) {
|
|
1897
|
-
imports.push({
|
|
1898
|
-
source: isWildcard ? `${source}.*` : source,
|
|
1899
|
-
specifiers: isWildcard ? ["*"] : [source.split(".").pop() || source],
|
|
1900
|
-
loc: {
|
|
1901
|
-
start: {
|
|
1902
|
-
line: node.startPosition.row + 1,
|
|
1903
|
-
column: node.startPosition.column
|
|
1904
|
-
},
|
|
1905
|
-
end: {
|
|
1906
|
-
line: node.endPosition.row + 1,
|
|
1907
|
-
column: node.endPosition.column
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
return imports;
|
|
1915
|
-
}
|
|
1916
|
-
/**
|
|
1917
|
-
* Extract export information (classes, interfaces, methods) using AST walk.
|
|
1918
|
-
*
|
|
1919
|
-
* @param rootNode - Root node of the Java AST.
|
|
1920
|
-
* @param code - Source code for documentation extraction.
|
|
1921
|
-
* @returns Array of discovered ExportInfo objects.
|
|
1922
|
-
*/
|
|
1923
|
-
extractExportsAST(rootNode, code) {
|
|
1924
|
-
const exports = [];
|
|
1925
|
-
for (const node of rootNode.children) {
|
|
1926
|
-
if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration") {
|
|
1927
|
-
const nameNode = node.children.find((c) => c.type === "identifier");
|
|
1928
|
-
if (nameNode) {
|
|
1929
|
-
const modifiers = this.getModifiers(node);
|
|
1930
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1931
|
-
exports.push({
|
|
1932
|
-
name: nameNode.text,
|
|
1933
|
-
type: node.type === "class_declaration" ? "class" : "interface",
|
|
1934
|
-
loc: {
|
|
1935
|
-
start: {
|
|
1936
|
-
line: node.startPosition.row + 1,
|
|
1937
|
-
column: node.startPosition.column
|
|
1938
|
-
},
|
|
1939
|
-
end: {
|
|
1940
|
-
line: node.endPosition.row + 1,
|
|
1941
|
-
column: node.endPosition.column
|
|
1942
|
-
}
|
|
1943
|
-
},
|
|
1944
|
-
visibility: modifiers.includes("public") ? "public" : "private",
|
|
1945
|
-
...metadata
|
|
1946
|
-
});
|
|
1947
|
-
this.extractSubExports(node, nameNode.text, exports, code);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
return exports;
|
|
1952
|
-
}
|
|
1953
|
-
/**
|
|
1954
|
-
* Extract modifiers (visibility, static, etc.) from a node.
|
|
1955
|
-
*
|
|
1956
|
-
* @param node - AST node to extract modifiers from.
|
|
1957
|
-
* @returns Array of modifier strings.
|
|
1958
|
-
*/
|
|
1959
|
-
getModifiers(node) {
|
|
1960
|
-
const modifiersNode = node.children.find((c) => c.type === "modifiers");
|
|
1961
|
-
if (!modifiersNode) return [];
|
|
1962
|
-
return modifiersNode.children.map((c) => c.text);
|
|
1963
|
-
}
|
|
1964
|
-
/**
|
|
1965
|
-
* Extract methods and nested exports from a class or interface body.
|
|
1966
|
-
*
|
|
1967
|
-
* @param parentNode - Class or interface declaration node.
|
|
1968
|
-
* @param parentName - Name of the parent class/interface.
|
|
1969
|
-
* @param exports - Array to collect discovered exports into.
|
|
1970
|
-
* @param code - Source code for context.
|
|
1971
|
-
*/
|
|
1972
|
-
extractSubExports(parentNode, parentName, exports, code) {
|
|
1973
|
-
const bodyNode = parentNode.children.find((c) => c.type === "class_body");
|
|
1974
|
-
if (!bodyNode) return;
|
|
1975
|
-
for (const node of bodyNode.children) {
|
|
1976
|
-
if (node.type === "method_declaration") {
|
|
1977
|
-
const nameNode = node.children.find((c) => c.type === "identifier");
|
|
1978
|
-
const modifiers = this.getModifiers(node);
|
|
1979
|
-
if (nameNode && modifiers.includes("public")) {
|
|
1980
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1981
|
-
exports.push({
|
|
1982
|
-
name: nameNode.text,
|
|
1983
|
-
type: "function",
|
|
1984
|
-
parentClass: parentName,
|
|
1985
|
-
visibility: "public",
|
|
1986
|
-
loc: {
|
|
1987
|
-
start: {
|
|
1988
|
-
line: node.startPosition.row + 1,
|
|
1989
|
-
column: node.startPosition.column
|
|
1990
|
-
},
|
|
1991
|
-
end: {
|
|
1992
|
-
line: node.endPosition.row + 1,
|
|
1993
|
-
column: node.endPosition.column
|
|
1994
|
-
}
|
|
1995
|
-
},
|
|
1996
|
-
parameters: this.extractParameters(node),
|
|
1997
|
-
...metadata
|
|
1998
|
-
});
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
extractParameters(node) {
|
|
2004
|
-
return extractParameterNames(node);
|
|
2005
|
-
}
|
|
2006
|
-
getNamingConventions() {
|
|
2007
|
-
return {
|
|
2008
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2009
|
-
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2010
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2011
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
2012
|
-
exceptions: ["main", "serialVersionUID"]
|
|
2013
|
-
};
|
|
2014
|
-
}
|
|
2015
|
-
canHandle(filePath) {
|
|
2016
|
-
return filePath.toLowerCase().endsWith(".java");
|
|
2017
|
-
}
|
|
2018
|
-
};
|
|
2019
|
-
|
|
2020
|
-
// src/parsers/csharp-parser.ts
|
|
2021
|
-
var CSharpParser = class extends BaseLanguageParser {
|
|
2022
|
-
constructor() {
|
|
2023
|
-
super(...arguments);
|
|
2024
|
-
this.language = "csharp" /* CSharp */;
|
|
2025
|
-
this.extensions = [".cs"];
|
|
2026
|
-
}
|
|
2027
|
-
getParserName() {
|
|
2028
|
-
return "c_sharp";
|
|
2029
|
-
}
|
|
2030
|
-
/**
|
|
2031
|
-
* Analyze metadata for a C# node (purity, side effects).
|
|
2032
|
-
*
|
|
2033
|
-
* @param node - Tree-sitter node to analyze.
|
|
2034
|
-
* @param code - Source code for context.
|
|
2035
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
2036
|
-
*/
|
|
2037
|
-
analyzeMetadata(node, code) {
|
|
2038
|
-
return analyzeGeneralMetadata(node, code, {
|
|
2039
|
-
sideEffectSignatures: ["Console.Write", "File.Write", "Logging."]
|
|
2040
|
-
});
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
2044
|
-
*
|
|
2045
|
-
* @param code - Source code content.
|
|
2046
|
-
* @returns Consolidated ParseResult.
|
|
2047
|
-
*/
|
|
2048
|
-
parseRegex(code) {
|
|
2049
|
-
const lines = code.split("\n");
|
|
2050
|
-
const exports = [];
|
|
2051
|
-
const imports = [];
|
|
2052
|
-
const usingRegex = /^using\s+([a-zA-Z0-9_.]+);/;
|
|
2053
|
-
const classRegex = /^\s*(?:public\s+)?class\s+([a-zA-Z0-9_]+)/;
|
|
2054
|
-
const methodRegex = /^\s*(?:public|protected)\s+(?:static\s+)?[a-zA-Z0-9_.]+\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
2055
|
-
let currentClassName = "";
|
|
2056
|
-
lines.forEach((line, idx) => {
|
|
2057
|
-
const usingMatch = line.match(usingRegex);
|
|
2058
|
-
if (usingMatch) {
|
|
2059
|
-
const source = usingMatch[1];
|
|
2060
|
-
imports.push({
|
|
2061
|
-
source,
|
|
2062
|
-
specifiers: [source.split(".").pop() || source],
|
|
2063
|
-
loc: {
|
|
2064
|
-
start: { line: idx + 1, column: 0 },
|
|
2065
|
-
end: { line: idx + 1, column: line.length }
|
|
2066
|
-
}
|
|
2067
|
-
});
|
|
2068
|
-
}
|
|
2069
|
-
const classMatch = line.match(classRegex);
|
|
2070
|
-
if (classMatch) {
|
|
2071
|
-
currentClassName = classMatch[1];
|
|
2072
|
-
exports.push({
|
|
2073
|
-
name: currentClassName,
|
|
2074
|
-
type: "class",
|
|
2075
|
-
visibility: "public",
|
|
2076
|
-
isPure: true,
|
|
2077
|
-
hasSideEffects: false,
|
|
2078
|
-
loc: {
|
|
2079
|
-
start: { line: idx + 1, column: 0 },
|
|
2080
|
-
end: { line: idx + 1, column: line.length }
|
|
2081
|
-
}
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
const methodMatch = line.match(methodRegex);
|
|
2085
|
-
if (methodMatch && currentClassName) {
|
|
2086
|
-
const name = methodMatch[1];
|
|
2087
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("Console.WriteLine");
|
|
2088
|
-
exports.push({
|
|
2089
|
-
name,
|
|
2090
|
-
type: "function",
|
|
2091
|
-
parentClass: currentClassName,
|
|
2092
|
-
visibility: "public",
|
|
2093
|
-
isPure: !isImpure,
|
|
2094
|
-
hasSideEffects: isImpure,
|
|
2095
|
-
loc: {
|
|
2096
|
-
start: { line: idx + 1, column: 0 },
|
|
2097
|
-
end: { line: idx + 1, column: line.length }
|
|
2098
|
-
}
|
|
2099
|
-
});
|
|
2100
|
-
}
|
|
2101
|
-
});
|
|
2102
|
-
return {
|
|
2103
|
-
exports,
|
|
2104
|
-
imports,
|
|
2105
|
-
language: "csharp" /* CSharp */,
|
|
2106
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
|
-
/**
|
|
2110
|
-
* Extract import information (usings) using AST walk.
|
|
2111
|
-
*
|
|
2112
|
-
* @param rootNode - Root node of the C# AST.
|
|
2113
|
-
* @returns Array of discovered FileImport objects.
|
|
2114
|
-
*/
|
|
2115
|
-
extractImportsAST(rootNode) {
|
|
2116
|
-
const imports = [];
|
|
2117
|
-
const findUsings = (node) => {
|
|
2118
|
-
if (node.type === "using_directive") {
|
|
2119
|
-
const nameNode = node.childForFieldName("name") || node.children.find(
|
|
2120
|
-
(c) => c.type === "qualified_name" || c.type === "identifier"
|
|
2121
|
-
);
|
|
2122
|
-
if (nameNode) {
|
|
2123
|
-
const aliasNode = node.childForFieldName("alias");
|
|
2124
|
-
imports.push({
|
|
2125
|
-
source: nameNode.text,
|
|
2126
|
-
specifiers: aliasNode ? [aliasNode.text] : [nameNode.text.split(".").pop() || nameNode.text],
|
|
2127
|
-
loc: {
|
|
2128
|
-
start: {
|
|
2129
|
-
line: node.startPosition.row + 1,
|
|
2130
|
-
column: node.startPosition.column
|
|
2131
|
-
},
|
|
2132
|
-
end: {
|
|
2133
|
-
line: node.endPosition.row + 1,
|
|
2134
|
-
column: node.endPosition.column
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2141
|
-
const child = node.child(i);
|
|
2142
|
-
if (child) findUsings(child);
|
|
2143
|
-
}
|
|
2144
|
-
};
|
|
2145
|
-
findUsings(rootNode);
|
|
2146
|
-
return imports;
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Extract export information (classes, methods, properties) using AST walk.
|
|
2150
|
-
* Handles nested namespaces and classes.
|
|
2151
|
-
*
|
|
2152
|
-
* @param rootNode - Root node of the C# AST.
|
|
2153
|
-
* @param code - Source code for documentation extraction.
|
|
2154
|
-
* @returns Array of discovered ExportInfo objects.
|
|
2155
|
-
*/
|
|
2156
|
-
extractExportsAST(rootNode, code) {
|
|
2157
|
-
const exports = [];
|
|
2158
|
-
const traverse = (node, currentNamespace, currentClass) => {
|
|
2159
|
-
let nextNamespace = currentNamespace;
|
|
2160
|
-
let nextClass = currentClass;
|
|
2161
|
-
if (node.type === "namespace_declaration" || node.type === "file_scoped_namespace_declaration") {
|
|
2162
|
-
const nameNode = node.childForFieldName("name") || node.children.find(
|
|
2163
|
-
(c) => c.type === "identifier" || c.type === "qualified_name"
|
|
2164
|
-
);
|
|
2165
|
-
if (nameNode) {
|
|
2166
|
-
nextNamespace = currentNamespace ? `${currentNamespace}.${nameNode.text}` : nameNode.text;
|
|
2167
|
-
}
|
|
2168
|
-
} else if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration" || node.type === "struct_declaration" || node.type === "record_declaration") {
|
|
2169
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2170
|
-
if (nameNode) {
|
|
2171
|
-
const modifiers = this.getModifiers(node);
|
|
2172
|
-
const isPublic = modifiers.includes("public") || modifiers.includes("protected");
|
|
2173
|
-
if (isPublic) {
|
|
2174
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2175
|
-
const type = node.type.replace("_declaration", "");
|
|
2176
|
-
const fullName = nextClass ? `${nextClass}.${nameNode.text}` : nextNamespace ? `${nextNamespace}.${nameNode.text}` : nameNode.text;
|
|
2177
|
-
exports.push({
|
|
2178
|
-
name: fullName,
|
|
2179
|
-
type: type === "record" ? "class" : type,
|
|
2180
|
-
loc: {
|
|
2181
|
-
start: {
|
|
2182
|
-
line: node.startPosition.row + 1,
|
|
2183
|
-
column: node.startPosition.column
|
|
2184
|
-
},
|
|
2185
|
-
end: {
|
|
2186
|
-
line: node.endPosition.row + 1,
|
|
2187
|
-
column: node.endPosition.column
|
|
2188
|
-
}
|
|
2189
|
-
},
|
|
2190
|
-
visibility: modifiers.includes("public") ? "public" : "protected",
|
|
2191
|
-
...metadata
|
|
2192
|
-
});
|
|
2193
|
-
nextClass = fullName;
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
} else if (node.type === "method_declaration" || node.type === "property_declaration") {
|
|
2197
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2198
|
-
if (nameNode) {
|
|
2199
|
-
const modifiers = this.getModifiers(node);
|
|
2200
|
-
const isPublic = modifiers.includes("public") || modifiers.includes("protected");
|
|
2201
|
-
if (isPublic) {
|
|
2202
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2203
|
-
exports.push({
|
|
2204
|
-
name: nameNode.text,
|
|
2205
|
-
type: node.type === "method_declaration" ? "function" : "property",
|
|
2206
|
-
parentClass: currentClass,
|
|
2207
|
-
loc: {
|
|
2208
|
-
start: {
|
|
2209
|
-
line: node.startPosition.row + 1,
|
|
2210
|
-
column: node.startPosition.column
|
|
2211
|
-
},
|
|
2212
|
-
end: {
|
|
2213
|
-
line: node.endPosition.row + 1,
|
|
2214
|
-
column: node.endPosition.column
|
|
2215
|
-
}
|
|
2216
|
-
},
|
|
2217
|
-
visibility: modifiers.includes("public") ? "public" : "protected",
|
|
2218
|
-
parameters: node.type === "method_declaration" ? this.extractParameters(node) : void 0,
|
|
2219
|
-
...metadata
|
|
2220
|
-
});
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2225
|
-
const child = node.child(i);
|
|
2226
|
-
if (child) traverse(child, nextNamespace, nextClass);
|
|
2227
|
-
}
|
|
2228
|
-
};
|
|
2229
|
-
traverse(rootNode);
|
|
2230
|
-
return exports;
|
|
2231
|
-
}
|
|
2232
|
-
getModifiers(node) {
|
|
2233
|
-
const modifiers = [];
|
|
2234
|
-
for (const child of node.children) {
|
|
2235
|
-
if (child.type === "modifier") {
|
|
2236
|
-
modifiers.push(child.text);
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
return modifiers;
|
|
2240
|
-
}
|
|
2241
|
-
extractParameters(node) {
|
|
2242
|
-
return extractParameterNames(node);
|
|
2243
|
-
}
|
|
2244
|
-
getNamingConventions() {
|
|
2245
|
-
return {
|
|
2246
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2247
|
-
functionPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2248
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2249
|
-
constantPattern: /^[A-Z][a-zA-Z0-9_]*$/
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
canHandle(filePath) {
|
|
2253
|
-
return filePath.toLowerCase().endsWith(".cs");
|
|
2254
|
-
}
|
|
2255
|
-
};
|
|
2256
|
-
|
|
2257
|
-
// src/parsers/go-parser.ts
|
|
2258
|
-
var GoParser = class extends BaseLanguageParser {
|
|
2259
|
-
constructor() {
|
|
2260
|
-
super(...arguments);
|
|
2261
|
-
this.language = "go" /* Go */;
|
|
2262
|
-
this.extensions = [".go"];
|
|
2263
|
-
}
|
|
2264
|
-
getParserName() {
|
|
2265
|
-
return "go";
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* Analyze metadata for a Go node (purity, side effects).
|
|
2269
|
-
*
|
|
2270
|
-
* @param node - Tree-sitter node to analyze.
|
|
2271
|
-
* @param code - Source code for context.
|
|
2272
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
2273
|
-
*/
|
|
2274
|
-
analyzeMetadata(node, code) {
|
|
2275
|
-
return analyzeGeneralMetadata(node, code, {
|
|
2276
|
-
sideEffectSignatures: ["<-", "fmt.Print", "fmt.Fprintf", "os.Exit"]
|
|
2277
|
-
});
|
|
2278
|
-
}
|
|
2279
|
-
/**
|
|
2280
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
2281
|
-
*
|
|
2282
|
-
* @param code - Source code content.
|
|
2283
|
-
* @returns Consolidated ParseResult.
|
|
2284
|
-
*/
|
|
2285
|
-
parseRegex(code) {
|
|
2286
|
-
const lines = code.split("\n");
|
|
2287
|
-
const exports = [];
|
|
2288
|
-
const imports = [];
|
|
2289
|
-
const importRegex = /^import\s+"([^"]+)"/;
|
|
2290
|
-
const funcRegex = /^func\s+([A-Z][a-zA-Z0-9_]*)\s*\(/;
|
|
2291
|
-
const typeRegex = /^type\s+([A-Z][a-zA-Z0-9_]*)\s+(struct|interface)/;
|
|
2292
|
-
lines.forEach((line, idx) => {
|
|
2293
|
-
const importMatch = line.match(importRegex);
|
|
2294
|
-
if (importMatch) {
|
|
2295
|
-
const source = importMatch[1];
|
|
2296
|
-
imports.push({
|
|
2297
|
-
source,
|
|
2298
|
-
specifiers: [source.split("/").pop() || source],
|
|
2299
|
-
loc: {
|
|
2300
|
-
start: { line: idx + 1, column: 0 },
|
|
2301
|
-
end: { line: idx + 1, column: line.length }
|
|
2302
|
-
}
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
const funcMatch = line.match(funcRegex);
|
|
2306
|
-
if (funcMatch) {
|
|
2307
|
-
const name = funcMatch[1];
|
|
2308
|
-
const isPublic = /^[A-Z]/.test(name);
|
|
2309
|
-
let docContent;
|
|
2310
|
-
const prevLines = lines.slice(Math.max(0, idx - 3), idx);
|
|
2311
|
-
for (let i = prevLines.length - 1; i >= 0; i--) {
|
|
2312
|
-
const prevLine = prevLines[i].trim();
|
|
2313
|
-
if (prevLine.startsWith("//")) {
|
|
2314
|
-
const content = prevLine.slice(2).trim();
|
|
2315
|
-
docContent = docContent ? content + "\n" + docContent : content;
|
|
2316
|
-
} else if (prevLine.endsWith("*/")) {
|
|
2317
|
-
const blockMatch = prevLine.match(/\/\*([\s\S]*)\*\//);
|
|
2318
|
-
if (blockMatch) docContent = blockMatch[1].trim();
|
|
2319
|
-
break;
|
|
2320
|
-
} else if (!prevLine) {
|
|
2321
|
-
if (docContent) break;
|
|
2322
|
-
} else {
|
|
2323
|
-
break;
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("fmt.Print");
|
|
2327
|
-
exports.push({
|
|
2328
|
-
name,
|
|
2329
|
-
type: "function",
|
|
2330
|
-
visibility: isPublic ? "public" : "private",
|
|
2331
|
-
isPure: !isImpure,
|
|
2332
|
-
hasSideEffects: isImpure,
|
|
2333
|
-
documentation: docContent ? { content: docContent, type: "comment" } : void 0,
|
|
2334
|
-
loc: {
|
|
2335
|
-
start: { line: idx + 1, column: 0 },
|
|
2336
|
-
end: { line: idx + 1, column: line.length }
|
|
2337
|
-
}
|
|
2338
|
-
});
|
|
2339
|
-
}
|
|
2340
|
-
const typeMatch = line.match(typeRegex);
|
|
2341
|
-
if (typeMatch) {
|
|
2342
|
-
exports.push({
|
|
2343
|
-
name: typeMatch[1],
|
|
2344
|
-
type: typeMatch[2] === "struct" ? "class" : "interface",
|
|
2345
|
-
visibility: "public",
|
|
2346
|
-
isPure: true,
|
|
2347
|
-
hasSideEffects: false,
|
|
2348
|
-
loc: {
|
|
2349
|
-
start: { line: idx + 1, column: 0 },
|
|
2350
|
-
end: { line: idx + 1, column: line.length }
|
|
2351
|
-
}
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
2354
|
-
});
|
|
2355
|
-
return {
|
|
2356
|
-
exports,
|
|
2357
|
-
imports,
|
|
2358
|
-
language: "go" /* Go */,
|
|
2359
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
/**
|
|
2363
|
-
* Extract import information using AST walk.
|
|
2364
|
-
*
|
|
2365
|
-
* @param rootNode - Root node of the Go AST.
|
|
2366
|
-
* @returns Array of discovered FileImport objects.
|
|
2367
|
-
*/
|
|
2368
|
-
extractImportsAST(rootNode) {
|
|
2369
|
-
const imports = [];
|
|
2370
|
-
const findImports = (node) => {
|
|
2371
|
-
if (node.type === "import_spec") {
|
|
2372
|
-
const pathNode = node.children.find(
|
|
2373
|
-
(c) => c.type === "interpreted_string_literal"
|
|
2374
|
-
);
|
|
2375
|
-
if (pathNode) {
|
|
2376
|
-
const source = pathNode.text.replace(/"/g, "");
|
|
2377
|
-
imports.push({
|
|
2378
|
-
source,
|
|
2379
|
-
specifiers: [source.split("/").pop() || source],
|
|
2380
|
-
loc: {
|
|
2381
|
-
start: {
|
|
2382
|
-
line: node.startPosition.row + 1,
|
|
2383
|
-
column: node.startPosition.column
|
|
2384
|
-
},
|
|
2385
|
-
end: {
|
|
2386
|
-
line: node.endPosition.row + 1,
|
|
2387
|
-
column: node.endPosition.column
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2394
|
-
const child = node.child(i);
|
|
2395
|
-
if (child) findImports(child);
|
|
2396
|
-
}
|
|
2397
|
-
};
|
|
2398
|
-
findImports(rootNode);
|
|
2399
|
-
return imports;
|
|
2400
|
-
}
|
|
2401
|
-
/**
|
|
2402
|
-
* Extract export information (functions, types, vars) using AST walk.
|
|
2403
|
-
*
|
|
2404
|
-
* @param rootNode - Root node of the Go AST.
|
|
2405
|
-
* @param code - Source code for documentation extraction.
|
|
2406
|
-
* @returns Array of discovered ExportInfo objects.
|
|
2407
|
-
*/
|
|
2408
|
-
extractExportsAST(rootNode, code) {
|
|
2409
|
-
const exports = [];
|
|
2410
|
-
const isExported = (name) => {
|
|
2411
|
-
return /^[A-Z]/.test(name);
|
|
2412
|
-
};
|
|
2413
|
-
const traverse = (node) => {
|
|
2414
|
-
if (node.type === "function_declaration" || node.type === "method_declaration") {
|
|
2415
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2416
|
-
if (nameNode && isExported(nameNode.text)) {
|
|
2417
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2418
|
-
exports.push({
|
|
2419
|
-
name: nameNode.text,
|
|
2420
|
-
type: "function",
|
|
2421
|
-
loc: {
|
|
2422
|
-
start: {
|
|
2423
|
-
line: node.startPosition.row + 1,
|
|
2424
|
-
column: node.startPosition.column
|
|
2425
|
-
},
|
|
2426
|
-
end: {
|
|
2427
|
-
line: node.endPosition.row + 1,
|
|
2428
|
-
column: node.endPosition.column
|
|
2429
|
-
}
|
|
2430
|
-
},
|
|
2431
|
-
visibility: "public",
|
|
2432
|
-
parameters: this.extractParameters(node),
|
|
2433
|
-
...metadata
|
|
2434
|
-
});
|
|
2435
|
-
}
|
|
2436
|
-
} else if (node.type === "type_spec") {
|
|
2437
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "type_identifier");
|
|
2438
|
-
if (nameNode && isExported(nameNode.text)) {
|
|
2439
|
-
const metadata = this.analyzeMetadata(node.parent || node, code);
|
|
2440
|
-
const type = node.children.some((c) => c.type === "struct_type") ? "class" : "interface";
|
|
2441
|
-
exports.push({
|
|
2442
|
-
name: nameNode.text,
|
|
2443
|
-
type,
|
|
2444
|
-
loc: {
|
|
2445
|
-
start: {
|
|
2446
|
-
line: node.startPosition.row + 1,
|
|
2447
|
-
column: node.startPosition.column
|
|
2448
|
-
},
|
|
2449
|
-
end: {
|
|
2450
|
-
line: node.endPosition.row + 1,
|
|
2451
|
-
column: node.endPosition.column
|
|
2452
|
-
}
|
|
2453
|
-
},
|
|
2454
|
-
visibility: "public",
|
|
2455
|
-
...metadata
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
} else if (node.type === "var_spec" || node.type === "const_spec") {
|
|
2459
|
-
const identifiers = node.children.filter(
|
|
2460
|
-
(c) => c.type === "identifier"
|
|
2461
|
-
);
|
|
2462
|
-
for (const idNode of identifiers) {
|
|
2463
|
-
if (isExported(idNode.text)) {
|
|
2464
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2465
|
-
exports.push({
|
|
2466
|
-
name: idNode.text,
|
|
2467
|
-
type: "variable",
|
|
2468
|
-
loc: {
|
|
2469
|
-
start: {
|
|
2470
|
-
line: idNode.startPosition.row + 1,
|
|
2471
|
-
column: idNode.startPosition.column
|
|
2472
|
-
},
|
|
2473
|
-
end: {
|
|
2474
|
-
line: idNode.endPosition.row + 1,
|
|
2475
|
-
column: idNode.endPosition.column
|
|
2476
|
-
}
|
|
2477
|
-
},
|
|
2478
|
-
visibility: "public",
|
|
2479
|
-
...metadata
|
|
2480
|
-
});
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2485
|
-
const child = node.child(i);
|
|
2486
|
-
if (child) traverse(child);
|
|
2487
|
-
}
|
|
2488
|
-
};
|
|
2489
|
-
traverse(rootNode);
|
|
2490
|
-
return exports;
|
|
2491
|
-
}
|
|
2492
|
-
extractParameters(node) {
|
|
2493
|
-
return extractParameterNames(node);
|
|
2494
|
-
}
|
|
2495
|
-
getNamingConventions() {
|
|
2496
|
-
return {
|
|
2497
|
-
variablePattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2498
|
-
functionPattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2499
|
-
classPattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2500
|
-
constantPattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
2501
|
-
};
|
|
2502
|
-
}
|
|
2503
|
-
canHandle(filePath) {
|
|
2504
|
-
return filePath.toLowerCase().endsWith(".go");
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
|
|
2508
|
-
// src/parsers/parser-factory.ts
|
|
2509
|
-
var ParserFactory = class _ParserFactory {
|
|
2510
|
-
/**
|
|
2511
|
-
* Create a new ParserFactory instance
|
|
2512
|
-
*/
|
|
2513
|
-
constructor() {
|
|
2514
|
-
this.parsers = /* @__PURE__ */ new Map();
|
|
2515
|
-
this.extensionMap = new Map(
|
|
2516
|
-
Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
|
|
2517
|
-
);
|
|
2518
|
-
this.registerParser(new TypeScriptParser());
|
|
2519
|
-
this.registerParser(new PythonParser());
|
|
2520
|
-
this.registerParser(new JavaParser());
|
|
2521
|
-
this.registerParser(new CSharpParser());
|
|
2522
|
-
this.registerParser(new GoParser());
|
|
2523
|
-
}
|
|
2524
|
-
/**
|
|
2525
|
-
* Get the global singleton instance
|
|
2526
|
-
*
|
|
2527
|
-
* @returns The singleton ParserFactory instance
|
|
2528
|
-
*/
|
|
2529
|
-
static getInstance() {
|
|
2530
|
-
if (!_ParserFactory.instance) {
|
|
2531
|
-
_ParserFactory.instance = new _ParserFactory();
|
|
2532
|
-
}
|
|
2533
|
-
return _ParserFactory.instance;
|
|
2534
|
-
}
|
|
2535
|
-
/**
|
|
2536
|
-
* Register a language parser
|
|
2537
|
-
*/
|
|
2538
|
-
registerParser(parser) {
|
|
2539
|
-
this.parsers.set(parser.language, parser);
|
|
2540
|
-
parser.extensions.forEach((ext) => {
|
|
2541
|
-
const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
|
|
2542
|
-
this.extensionMap.set(ext, lang);
|
|
2543
|
-
this.parsers.set(lang, parser);
|
|
2544
|
-
});
|
|
2545
|
-
}
|
|
2546
|
-
/**
|
|
2547
|
-
* Get parser for a specific language
|
|
2548
|
-
*/
|
|
2549
|
-
getParserForLanguage(language) {
|
|
2550
|
-
return this.parsers.get(language) || null;
|
|
2551
|
-
}
|
|
2552
|
-
/**
|
|
2553
|
-
* Get parser for a file based on its extension
|
|
2554
|
-
*/
|
|
2555
|
-
getParserForFile(filePath) {
|
|
2556
|
-
const ext = this.getFileExtension(filePath);
|
|
2557
|
-
const language = this.extensionMap.get(ext);
|
|
2558
|
-
if (!language) {
|
|
2559
|
-
return null;
|
|
2560
|
-
}
|
|
2561
|
-
return this.parsers.get(language) || null;
|
|
2562
|
-
}
|
|
2563
|
-
/**
|
|
2564
|
-
* Check if a file is supported
|
|
2565
|
-
*/
|
|
2566
|
-
isSupported(filePath) {
|
|
2567
|
-
return this.getParserForFile(filePath) !== null;
|
|
2568
|
-
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Get all registered languages
|
|
2571
|
-
*/
|
|
2572
|
-
getSupportedLanguages() {
|
|
2573
|
-
return Array.from(this.parsers.keys());
|
|
2574
|
-
}
|
|
2575
|
-
/**
|
|
2576
|
-
* Get all supported file extensions
|
|
2577
|
-
*/
|
|
2578
|
-
getSupportedExtensions() {
|
|
2579
|
-
return Array.from(this.extensionMap.keys());
|
|
2580
|
-
}
|
|
2581
|
-
/**
|
|
2582
|
-
* Get language for a file
|
|
2583
|
-
*/
|
|
2584
|
-
getLanguageForFile(filePath) {
|
|
2585
|
-
const ext = this.getFileExtension(filePath);
|
|
2586
|
-
return this.extensionMap.get(ext) || null;
|
|
2587
|
-
}
|
|
2588
|
-
/**
|
|
2589
|
-
* Extract file extension (with dot)
|
|
976
|
+
* Extract file extension (with dot)
|
|
2590
977
|
*/
|
|
2591
978
|
getFileExtension(filePath) {
|
|
2592
979
|
const match = filePath.match(/\.[^.]+$/);
|
|
@@ -2608,7 +995,7 @@ var ParserFactory = class _ParserFactory {
|
|
|
2608
995
|
await Promise.all(promises);
|
|
2609
996
|
}
|
|
2610
997
|
};
|
|
2611
|
-
function getParser(filePath) {
|
|
998
|
+
async function getParser(filePath) {
|
|
2612
999
|
return ParserFactory.getInstance().getParserForFile(filePath);
|
|
2613
1000
|
}
|
|
2614
1001
|
async function initializeParsers() {
|
|
@@ -2795,10 +1182,11 @@ function extractTypeReferences(node) {
|
|
|
2795
1182
|
}
|
|
2796
1183
|
|
|
2797
1184
|
// src/utils/dependency-analyzer.ts
|
|
2798
|
-
function parseFileExports(code, filePath) {
|
|
2799
|
-
const parser = getParser(filePath);
|
|
1185
|
+
async function parseFileExports(code, filePath) {
|
|
1186
|
+
const parser = await getParser(filePath);
|
|
2800
1187
|
if (parser && parser.language !== "typescript" /* TypeScript */ && parser.language !== "javascript" /* JavaScript */) {
|
|
2801
1188
|
try {
|
|
1189
|
+
await parser.initialize();
|
|
2802
1190
|
const result = parser.parse(code, filePath);
|
|
2803
1191
|
return {
|
|
2804
1192
|
exports: result.exports.map((e) => ({
|
|
@@ -2823,7 +1211,7 @@ function parseFileExports(code, filePath) {
|
|
|
2823
1211
|
}
|
|
2824
1212
|
}
|
|
2825
1213
|
try {
|
|
2826
|
-
const ast =
|
|
1214
|
+
const ast = parse(code, {
|
|
2827
1215
|
loc: true,
|
|
2828
1216
|
range: true,
|
|
2829
1217
|
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
@@ -2843,8 +1231,8 @@ function estimateTokens(text) {
|
|
|
2843
1231
|
}
|
|
2844
1232
|
|
|
2845
1233
|
// src/utils/config.ts
|
|
2846
|
-
import { readFileSync, existsSync as
|
|
2847
|
-
import { join as
|
|
1234
|
+
import { readFileSync, existsSync as existsSync3 } from "fs";
|
|
1235
|
+
import { join as join3, resolve, dirname as dirname3 } from "path";
|
|
2848
1236
|
import { pathToFileURL } from "url";
|
|
2849
1237
|
var CONFIG_FILES = [
|
|
2850
1238
|
"aiready.json",
|
|
@@ -2859,7 +1247,7 @@ async function loadConfig(rootDir) {
|
|
|
2859
1247
|
while (true) {
|
|
2860
1248
|
const foundConfigs = [];
|
|
2861
1249
|
for (const configFile of CONFIG_FILES) {
|
|
2862
|
-
if (
|
|
1250
|
+
if (existsSync3(join3(currentDir, configFile))) {
|
|
2863
1251
|
foundConfigs.push(configFile);
|
|
2864
1252
|
}
|
|
2865
1253
|
}
|
|
@@ -2873,7 +1261,7 @@ async function loadConfig(rootDir) {
|
|
|
2873
1261
|
} else {
|
|
2874
1262
|
}
|
|
2875
1263
|
const configFile = foundConfigs[0];
|
|
2876
|
-
const configPath =
|
|
1264
|
+
const configPath = join3(currentDir, configFile);
|
|
2877
1265
|
try {
|
|
2878
1266
|
let config;
|
|
2879
1267
|
if (configFile.endsWith(".js")) {
|
|
@@ -2915,7 +1303,7 @@ async function loadConfig(rootDir) {
|
|
|
2915
1303
|
throw configError;
|
|
2916
1304
|
}
|
|
2917
1305
|
}
|
|
2918
|
-
const parent =
|
|
1306
|
+
const parent = dirname3(currentDir);
|
|
2919
1307
|
if (parent === currentDir) {
|
|
2920
1308
|
break;
|
|
2921
1309
|
}
|
|
@@ -2947,6 +1335,450 @@ function mergeConfigWithDefaults(userConfig, defaults) {
|
|
|
2947
1335
|
return mergedConfig;
|
|
2948
1336
|
}
|
|
2949
1337
|
|
|
1338
|
+
// src/utils/report-formatters.ts
|
|
1339
|
+
function generateReportHead(title) {
|
|
1340
|
+
return `<!DOCTYPE html>
|
|
1341
|
+
<html lang="en">
|
|
1342
|
+
<head>
|
|
1343
|
+
<meta charset="UTF-8">
|
|
1344
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1345
|
+
<title>${title}</title>
|
|
1346
|
+
<style>
|
|
1347
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 2rem; background-color: #f9f9f9; }
|
|
1348
|
+
h1, h2, h3 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
|
|
1349
|
+
.card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 2rem; border: 1px solid #eaeaea; }
|
|
1350
|
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
1351
|
+
.stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
|
|
1352
|
+
.stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
|
|
1353
|
+
.stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
|
|
1354
|
+
.hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px; }
|
|
1355
|
+
.hero h1 { border: none; color: white; margin: 0; }
|
|
1356
|
+
.hero p { margin: 10px 0 0 0; opacity: 0.9; }
|
|
1357
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
|
|
1358
|
+
th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
|
|
1359
|
+
th { background-color: #f8fafc; font-weight: 600; color: #475569; }
|
|
1360
|
+
tr:last-child td { border-bottom: none; }
|
|
1361
|
+
.critical { color: #dc2626; font-weight: bold; }
|
|
1362
|
+
.major { color: #ea580c; font-weight: bold; }
|
|
1363
|
+
.minor { color: #2563eb; }
|
|
1364
|
+
.issue-critical { color: #dc2626; font-weight: bold; text-transform: uppercase; }
|
|
1365
|
+
.issue-major { color: #ea580c; font-weight: bold; text-transform: uppercase; }
|
|
1366
|
+
.issue-minor { color: #2563eb; font-weight: bold; text-transform: uppercase; }
|
|
1367
|
+
code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
|
|
1368
|
+
.footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
|
|
1369
|
+
a { color: #2563eb; text-decoration: none; }
|
|
1370
|
+
a:hover { text-decoration: underline; }
|
|
1371
|
+
</style>
|
|
1372
|
+
</head>`;
|
|
1373
|
+
}
|
|
1374
|
+
function generateReportHero(title, subtitle) {
|
|
1375
|
+
const subtitleHtml = subtitle ? `<p>${subtitle}</p>` : "";
|
|
1376
|
+
return `<div class="hero">
|
|
1377
|
+
<h1>${title}</h1>
|
|
1378
|
+
${subtitleHtml}
|
|
1379
|
+
</div>`;
|
|
1380
|
+
}
|
|
1381
|
+
function generateStatCards(cards) {
|
|
1382
|
+
const cardsHtml = cards.map(
|
|
1383
|
+
(card) => `
|
|
1384
|
+
<div class="stat-card">
|
|
1385
|
+
<div class="stat-value"${card.color ? ` style="color: ${card.color}"` : ""}>${card.value}</div>
|
|
1386
|
+
<div class="stat-label">${card.label}</div>
|
|
1387
|
+
</div>`
|
|
1388
|
+
).join("");
|
|
1389
|
+
return `<div class="stats">${cardsHtml}</div>`;
|
|
1390
|
+
}
|
|
1391
|
+
function generateScoreCard(value, label) {
|
|
1392
|
+
return `<div class="stat-card" style="margin-bottom: 2rem;">
|
|
1393
|
+
<div class="stat-label">${label}</div>
|
|
1394
|
+
<div class="stat-value">${value}</div>
|
|
1395
|
+
</div>`;
|
|
1396
|
+
}
|
|
1397
|
+
function generateTable(config) {
|
|
1398
|
+
const headersHtml = config.headers.map((h) => `<th>${h}</th>`).join("");
|
|
1399
|
+
const rowsHtml = config.rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
|
|
1400
|
+
return `<table>
|
|
1401
|
+
<thead><tr>${headersHtml}</tr></thead>
|
|
1402
|
+
<tbody>${rowsHtml}</tbody>
|
|
1403
|
+
</table>`;
|
|
1404
|
+
}
|
|
1405
|
+
function generateIssueSummary(critical, major, minor, potentialSavings) {
|
|
1406
|
+
const savingsHtml = potentialSavings ? `<p><strong>Potential Savings:</strong> ${potentialSavings.toLocaleString()} tokens</p>` : "";
|
|
1407
|
+
return `<div class="card" style="margin-bottom: 30px;">
|
|
1408
|
+
<h2>\u26A0\uFE0F Issues Summary</h2>
|
|
1409
|
+
<p>
|
|
1410
|
+
<span class="critical">\u{1F534} Critical: ${critical}</span>
|
|
1411
|
+
<span class="major">\u{1F7E1} Major: ${major}</span>
|
|
1412
|
+
<span class="minor">\u{1F535} Minor: ${minor}</span>
|
|
1413
|
+
</p>
|
|
1414
|
+
${savingsHtml}
|
|
1415
|
+
</div>`;
|
|
1416
|
+
}
|
|
1417
|
+
function generateReportFooter(options) {
|
|
1418
|
+
const versionText = options.version ? ` v${options.version}` : "";
|
|
1419
|
+
const links = [];
|
|
1420
|
+
if (options.packageUrl) {
|
|
1421
|
+
links.push(`<a href="${options.packageUrl}">Star us on GitHub</a>`);
|
|
1422
|
+
}
|
|
1423
|
+
if (options.bugUrl) {
|
|
1424
|
+
links.push(`<a href="${options.bugUrl}">Report it here</a>`);
|
|
1425
|
+
}
|
|
1426
|
+
const linksHtml = links.length ? links.map((l) => `<p>Like AIReady? ${l}</p>`).join("\n ") : "";
|
|
1427
|
+
return `<div class="footer">
|
|
1428
|
+
<p>Generated by <strong>@aiready/${options.packageName}</strong>${versionText}</p>
|
|
1429
|
+
${linksHtml}
|
|
1430
|
+
</div>`;
|
|
1431
|
+
}
|
|
1432
|
+
function wrapInCard(content, title) {
|
|
1433
|
+
const titleHtml = title ? `<h2>${title}</h2>` : "";
|
|
1434
|
+
return `<div class="card">
|
|
1435
|
+
${titleHtml}
|
|
1436
|
+
${content}
|
|
1437
|
+
</div>`;
|
|
1438
|
+
}
|
|
1439
|
+
function generateCompleteReport(options, bodyContent) {
|
|
1440
|
+
return `${generateReportHead(options.title)}
|
|
1441
|
+
<body>
|
|
1442
|
+
${bodyContent}
|
|
1443
|
+
${generateReportFooter(options)}
|
|
1444
|
+
</body>
|
|
1445
|
+
</html>`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/utils/scoring-helpers.ts
|
|
1449
|
+
function buildFactorsFromDimensions(dimensions, dimensionNames, rawData) {
|
|
1450
|
+
return Object.entries(dimensionNames).map(([key, name]) => {
|
|
1451
|
+
const val = dimensions[key] ?? 50;
|
|
1452
|
+
return {
|
|
1453
|
+
name,
|
|
1454
|
+
impact: Math.round(val - 50),
|
|
1455
|
+
description: formatDimensionDescription(key, rawData) || `${val}/100`
|
|
1456
|
+
};
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
function formatDimensionDescription(key, rawData) {
|
|
1460
|
+
if (key === "testCoverageRatio" && rawData.testFiles !== void 0) {
|
|
1461
|
+
return `${rawData.testFiles} test files / ${rawData.sourceFiles} source files`;
|
|
1462
|
+
}
|
|
1463
|
+
if (key === "purityScore" && rawData.pureFunctions !== void 0) {
|
|
1464
|
+
return `${rawData.pureFunctions}/${rawData.totalFunctions} functions are pure`;
|
|
1465
|
+
}
|
|
1466
|
+
if (key === "dependencyInjectionScore" && rawData.injectionPatterns !== void 0) {
|
|
1467
|
+
return `${rawData.injectionPatterns}/${rawData.totalClasses} classes use DI`;
|
|
1468
|
+
}
|
|
1469
|
+
if (key === "structureClarityScore" && rawData.deepDirectories !== void 0) {
|
|
1470
|
+
return `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`;
|
|
1471
|
+
}
|
|
1472
|
+
if (key === "apiClarityScore" && rawData.untypedExports !== void 0) {
|
|
1473
|
+
return `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`;
|
|
1474
|
+
}
|
|
1475
|
+
if (key === "graphStabilityScore") {
|
|
1476
|
+
return `${rawData.score}/100`;
|
|
1477
|
+
}
|
|
1478
|
+
return void 0;
|
|
1479
|
+
}
|
|
1480
|
+
function buildStandardToolScore(params) {
|
|
1481
|
+
const {
|
|
1482
|
+
toolName,
|
|
1483
|
+
score,
|
|
1484
|
+
rawData,
|
|
1485
|
+
dimensions,
|
|
1486
|
+
dimensionNames,
|
|
1487
|
+
recommendations,
|
|
1488
|
+
recommendationImpact = 8,
|
|
1489
|
+
rating
|
|
1490
|
+
} = params;
|
|
1491
|
+
const factors = buildFactorsFromDimensions(
|
|
1492
|
+
dimensions,
|
|
1493
|
+
dimensionNames,
|
|
1494
|
+
rawData
|
|
1495
|
+
);
|
|
1496
|
+
const recs = recommendations.map(
|
|
1497
|
+
(action) => ({
|
|
1498
|
+
action,
|
|
1499
|
+
estimatedImpact: recommendationImpact,
|
|
1500
|
+
priority: score < 50 || [
|
|
1501
|
+
"high-risk",
|
|
1502
|
+
"blind-risk",
|
|
1503
|
+
"explosive",
|
|
1504
|
+
"fragile",
|
|
1505
|
+
"critical"
|
|
1506
|
+
].includes(rating || "") ? "high" /* High */ : "medium" /* Medium */
|
|
1507
|
+
})
|
|
1508
|
+
);
|
|
1509
|
+
return {
|
|
1510
|
+
toolName,
|
|
1511
|
+
score,
|
|
1512
|
+
rawMetrics: {
|
|
1513
|
+
...rawData,
|
|
1514
|
+
rating: rating || rawData.rating
|
|
1515
|
+
},
|
|
1516
|
+
factors,
|
|
1517
|
+
recommendations: recs
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// src/utils/similarity.ts
|
|
1522
|
+
function calculateStringSimilarity(a, b) {
|
|
1523
|
+
if (a === b) return 1;
|
|
1524
|
+
const tokensA = a.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
1525
|
+
const tokensB = b.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 0);
|
|
1526
|
+
if (tokensA.length === 0 || tokensB.length === 0) return 0;
|
|
1527
|
+
const setA = new Set(tokensA);
|
|
1528
|
+
const setB = new Set(tokensB);
|
|
1529
|
+
const intersection = new Set([...setA].filter((x) => setB.has(x)));
|
|
1530
|
+
const union = /* @__PURE__ */ new Set([...setA, ...setB]);
|
|
1531
|
+
return intersection.size / union.size;
|
|
1532
|
+
}
|
|
1533
|
+
function calculateHeuristicConfidence(similarity, tokens, lines) {
|
|
1534
|
+
let confidence = similarity;
|
|
1535
|
+
if (lines > 20) confidence += 0.05;
|
|
1536
|
+
if (tokens > 200) confidence += 0.05;
|
|
1537
|
+
if (lines < 5) confidence -= 0.1;
|
|
1538
|
+
return Math.max(0, Math.min(1, confidence));
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// src/utils/cli-factory.ts
|
|
1542
|
+
import chalk2 from "chalk";
|
|
1543
|
+
function createStandardProgressCallback(toolName) {
|
|
1544
|
+
return (processed, total, message) => {
|
|
1545
|
+
const percent = Math.round(processed / Math.max(1, total) * 100);
|
|
1546
|
+
process.stdout.write(
|
|
1547
|
+
`\r\x1B[K [${toolName}] ${chalk2.cyan(`${percent}%`)} ${message}`
|
|
1548
|
+
);
|
|
1549
|
+
if (processed === total) {
|
|
1550
|
+
process.stdout.write("\n");
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
function formatStandardCliResult(toolName, score, issuesCount) {
|
|
1555
|
+
const scoreColor = score >= 75 ? chalk2.green : score >= 50 ? chalk2.yellow : chalk2.red;
|
|
1556
|
+
console.log(`
|
|
1557
|
+
${chalk2.bold(toolName.toUpperCase())} Analysis Complete`);
|
|
1558
|
+
console.log(` Overall Score: ${scoreColor(score)}/100`);
|
|
1559
|
+
console.log(
|
|
1560
|
+
` Issues Found: ${issuesCount > 0 ? chalk2.red(issuesCount) : chalk2.green("None")}`
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
async function runStandardCliAction(toolName, action) {
|
|
1564
|
+
try {
|
|
1565
|
+
const { score, issuesCount } = await action();
|
|
1566
|
+
formatStandardCliResult(toolName, score, issuesCount);
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
console.error(
|
|
1569
|
+
chalk2.red(`
|
|
1570
|
+
\u274C [${toolName}] critical error: ${error.message}`)
|
|
1571
|
+
);
|
|
1572
|
+
process.exit(1);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// src/utils/reporting.ts
|
|
1577
|
+
import chalk3 from "chalk";
|
|
1578
|
+
function getScoreColor(score) {
|
|
1579
|
+
if (score >= 85) return chalk3.green;
|
|
1580
|
+
if (score >= 70) return chalk3.cyan;
|
|
1581
|
+
if (score >= 50) return chalk3.yellow;
|
|
1582
|
+
if (score >= 30) return chalk3.red;
|
|
1583
|
+
return chalk3.bgRed.white;
|
|
1584
|
+
}
|
|
1585
|
+
function displayStandardConsoleReport(data) {
|
|
1586
|
+
const {
|
|
1587
|
+
title,
|
|
1588
|
+
score,
|
|
1589
|
+
rating,
|
|
1590
|
+
dimensions,
|
|
1591
|
+
stats = [],
|
|
1592
|
+
issues,
|
|
1593
|
+
recommendations = [],
|
|
1594
|
+
elapsedTime,
|
|
1595
|
+
noIssuesMessage = "\u2728 No issues found!",
|
|
1596
|
+
safetyRating
|
|
1597
|
+
} = data;
|
|
1598
|
+
console.log(chalk3.bold(`
|
|
1599
|
+
${title}
|
|
1600
|
+
`));
|
|
1601
|
+
if (safetyRating) {
|
|
1602
|
+
if (safetyRating === "blind-risk" || safetyRating === "\u{1F480} blind-risk") {
|
|
1603
|
+
console.log(
|
|
1604
|
+
chalk3.bgRed.white.bold(
|
|
1605
|
+
" \u{1F480} BLIND RISK \u2014 NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. "
|
|
1606
|
+
)
|
|
1607
|
+
);
|
|
1608
|
+
console.log();
|
|
1609
|
+
} else if (safetyRating === "high-risk" || safetyRating === "\u{1F534} high-risk") {
|
|
1610
|
+
console.log(
|
|
1611
|
+
chalk3.red.bold(
|
|
1612
|
+
` \u{1F534} HIGH RISK \u2014 Insufficient test coverage. AI changes may introduce silent bugs.`
|
|
1613
|
+
)
|
|
1614
|
+
);
|
|
1615
|
+
console.log();
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
const safetyColor = safetyRating ? getSeverityColor(safetyRating, chalk3) : getScoreColor(score);
|
|
1619
|
+
if (safetyRating) {
|
|
1620
|
+
console.log(
|
|
1621
|
+
`AI Change Safety: ${safetyColor(`${getSafetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
console.log(
|
|
1625
|
+
`Score: ${getScoreColor(score)(score + "/100")} (${rating.toUpperCase()})`
|
|
1626
|
+
);
|
|
1627
|
+
if (stats.length > 0) {
|
|
1628
|
+
const statsStr = stats.map((s) => `${s.label}: ${chalk3.cyan(s.value)}`).join(" ");
|
|
1629
|
+
console.log(statsStr);
|
|
1630
|
+
}
|
|
1631
|
+
console.log(`Analysis Time: ${chalk3.gray(elapsedTime + "s")}
|
|
1632
|
+
`);
|
|
1633
|
+
console.log(chalk3.bold("\u{1F4D0} Dimension Scores\n"));
|
|
1634
|
+
for (const dim of dimensions) {
|
|
1635
|
+
const color = getScoreColor(dim.value);
|
|
1636
|
+
console.log(
|
|
1637
|
+
` ${dim.name.padEnd(22)} ${color(getScoreBar(dim.value))} ${dim.value}/100`
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
if (issues.length > 0) {
|
|
1641
|
+
console.log(chalk3.bold("\n\u26A0\uFE0F Issues\n"));
|
|
1642
|
+
for (const issue of issues) {
|
|
1643
|
+
const sev = getSeverityColor(issue.severity, chalk3);
|
|
1644
|
+
console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
|
|
1645
|
+
if (issue.suggestion) {
|
|
1646
|
+
console.log(
|
|
1647
|
+
` ${chalk3.dim("\u2192")} ${chalk3.italic(issue.suggestion)}`
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
console.log();
|
|
1651
|
+
}
|
|
1652
|
+
} else {
|
|
1653
|
+
console.log(chalk3.green(`
|
|
1654
|
+
${noIssuesMessage}
|
|
1655
|
+
`));
|
|
1656
|
+
}
|
|
1657
|
+
if (recommendations.length > 0) {
|
|
1658
|
+
console.log(chalk3.bold("\u{1F4A1} Recommendations\n"));
|
|
1659
|
+
recommendations.forEach((rec, i) => {
|
|
1660
|
+
console.log(`${i + 1}. ${rec}`);
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
console.log();
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/utils/code-extractor.ts
|
|
1667
|
+
function inferPatternType(keyword, name) {
|
|
1668
|
+
const n = name.toLowerCase();
|
|
1669
|
+
if (keyword === "handler" || n.includes("handler") || n.includes("controller") || n.startsWith("app.")) {
|
|
1670
|
+
return "api-handler";
|
|
1671
|
+
}
|
|
1672
|
+
if (n.includes("validate") || n.includes("schema")) return "validator";
|
|
1673
|
+
if (n.includes("util") || n.includes("helper")) return "utility";
|
|
1674
|
+
if (keyword === "class") return "class-method";
|
|
1675
|
+
if (n.match(/^[A-Z]/)) return "component";
|
|
1676
|
+
if (keyword === "function" || keyword === "def") return "function";
|
|
1677
|
+
return "unknown";
|
|
1678
|
+
}
|
|
1679
|
+
function extractCodeBlocks(file, content) {
|
|
1680
|
+
const isPython = file.toLowerCase().endsWith(".py");
|
|
1681
|
+
if (isPython) {
|
|
1682
|
+
return extractBlocksPython(file, content);
|
|
1683
|
+
}
|
|
1684
|
+
const blocks = [];
|
|
1685
|
+
const lines = content.split("\n");
|
|
1686
|
+
const blockRegex = /^\s*(?:export\s+)?(?:async\s+)?(?:public\s+|private\s+|protected\s+|internal\s+|static\s+|readonly\s+|virtual\s+|abstract\s+|override\s+)*(function|class|interface|type|enum|record|struct|void|func|[a-zA-Z0-9_<>[]]+)\s+([a-zA-Z0-9_]+)(?:\s*\(|(?:\s+extends|\s+implements|\s+where)?\s*\{)|^\s*(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*[a-zA-Z0-9_.]+\.object\(|^\s*(app\.(?:get|post|put|delete|patch|use))\(/gm;
|
|
1687
|
+
let match;
|
|
1688
|
+
while ((match = blockRegex.exec(content)) !== null) {
|
|
1689
|
+
const startLine = content.substring(0, match.index).split("\n").length;
|
|
1690
|
+
let type;
|
|
1691
|
+
let name;
|
|
1692
|
+
if (match[1]) {
|
|
1693
|
+
type = match[1];
|
|
1694
|
+
name = match[2];
|
|
1695
|
+
} else if (match[3]) {
|
|
1696
|
+
type = "const";
|
|
1697
|
+
name = match[3];
|
|
1698
|
+
} else {
|
|
1699
|
+
type = "handler";
|
|
1700
|
+
name = match[4];
|
|
1701
|
+
}
|
|
1702
|
+
let endLine = -1;
|
|
1703
|
+
let openBraces = 0;
|
|
1704
|
+
let foundStart = false;
|
|
1705
|
+
const lineEnd = content.indexOf("\n", match.index);
|
|
1706
|
+
const lineText = content.substring(
|
|
1707
|
+
match.index,
|
|
1708
|
+
lineEnd === -1 ? content.length : lineEnd
|
|
1709
|
+
);
|
|
1710
|
+
if (lineText.includes("{") && lineText.includes("}")) {
|
|
1711
|
+
endLine = startLine;
|
|
1712
|
+
} else {
|
|
1713
|
+
for (let i = match.index; i < content.length; i++) {
|
|
1714
|
+
if (content[i] === "{") {
|
|
1715
|
+
openBraces++;
|
|
1716
|
+
foundStart = true;
|
|
1717
|
+
} else if (content[i] === "}") {
|
|
1718
|
+
openBraces--;
|
|
1719
|
+
}
|
|
1720
|
+
if (foundStart && openBraces === 0) {
|
|
1721
|
+
endLine = content.substring(0, i + 1).split("\n").length;
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (endLine === -1) {
|
|
1727
|
+
endLine = startLine;
|
|
1728
|
+
}
|
|
1729
|
+
endLine = Math.max(startLine, endLine);
|
|
1730
|
+
const blockCode = lines.slice(startLine - 1, endLine).join("\n");
|
|
1731
|
+
const tokens = estimateTokens(blockCode);
|
|
1732
|
+
blocks.push({
|
|
1733
|
+
file,
|
|
1734
|
+
startLine,
|
|
1735
|
+
endLine,
|
|
1736
|
+
code: blockCode,
|
|
1737
|
+
tokens,
|
|
1738
|
+
patternType: inferPatternType(type, name)
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
return blocks;
|
|
1742
|
+
}
|
|
1743
|
+
function extractBlocksPython(file, content) {
|
|
1744
|
+
const blocks = [];
|
|
1745
|
+
const lines = content.split("\n");
|
|
1746
|
+
const blockRegex = /^\s*(?:async\s+)?(def|class)\s+([a-zA-Z0-9_]+)/gm;
|
|
1747
|
+
let match;
|
|
1748
|
+
while ((match = blockRegex.exec(content)) !== null) {
|
|
1749
|
+
const startLinePos = content.substring(0, match.index).split("\n").length;
|
|
1750
|
+
const startLineIdx = startLinePos - 1;
|
|
1751
|
+
const initialIndent = lines[startLineIdx].search(/\S/);
|
|
1752
|
+
let endLineIdx = startLineIdx;
|
|
1753
|
+
for (let i = startLineIdx + 1; i < lines.length; i++) {
|
|
1754
|
+
const line = lines[i];
|
|
1755
|
+
if (line.trim().length === 0) {
|
|
1756
|
+
endLineIdx = i;
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const currentIndent = line.search(/\S/);
|
|
1760
|
+
if (currentIndent <= initialIndent) {
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
endLineIdx = i;
|
|
1764
|
+
}
|
|
1765
|
+
while (endLineIdx > startLineIdx && lines[endLineIdx].trim().length === 0) {
|
|
1766
|
+
endLineIdx--;
|
|
1767
|
+
}
|
|
1768
|
+
const blockCode = lines.slice(startLineIdx, endLineIdx + 1).join("\n");
|
|
1769
|
+
const tokens = estimateTokens(blockCode);
|
|
1770
|
+
blocks.push({
|
|
1771
|
+
file,
|
|
1772
|
+
startLine: startLinePos,
|
|
1773
|
+
endLine: endLineIdx + 1,
|
|
1774
|
+
code: blockCode,
|
|
1775
|
+
tokens,
|
|
1776
|
+
patternType: inferPatternType(match[1], match[2])
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
return blocks;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
2950
1782
|
// src/business/pricing-models.ts
|
|
2951
1783
|
var MODEL_PRICING_PRESETS = {
|
|
2952
1784
|
"gpt-5.4-mini": {
|
|
@@ -3010,20 +1842,52 @@ var DEFAULT_COST_CONFIG = {
|
|
|
3010
1842
|
developerCount: 5,
|
|
3011
1843
|
daysPerMonth: 30
|
|
3012
1844
|
};
|
|
3013
|
-
function calculateMonthlyCost(tokenWaste, config = {}) {
|
|
3014
|
-
const
|
|
1845
|
+
function calculateMonthlyCost(tokenWaste, config = {}, options) {
|
|
1846
|
+
const baseMultiplier = tokenWaste > 5e4 ? 5 : tokenWaste > 1e4 ? 3.5 : 2.5;
|
|
1847
|
+
const contextMultiplier = options?.avgContextBudget ? options.avgContextBudget / Math.max(1, tokenWaste) : baseMultiplier;
|
|
1848
|
+
const fragRatio = options?.fragmentationScore ?? 0.3;
|
|
1849
|
+
const dupRatio = 1 - fragRatio;
|
|
3015
1850
|
const budget = calculateTokenBudget({
|
|
3016
|
-
totalContextTokens: tokenWaste *
|
|
1851
|
+
totalContextTokens: tokenWaste * contextMultiplier,
|
|
3017
1852
|
wastedTokens: {
|
|
3018
|
-
duplication: tokenWaste *
|
|
3019
|
-
fragmentation: tokenWaste *
|
|
1853
|
+
duplication: tokenWaste * dupRatio * (options?.potentialSavings ? 1.2 : 1),
|
|
1854
|
+
fragmentation: tokenWaste * fragRatio,
|
|
3020
1855
|
chattiness: 0.1 * tokenWaste
|
|
3021
|
-
// Added baseline chattiness
|
|
3022
1856
|
}
|
|
3023
1857
|
});
|
|
3024
|
-
const preset = getModelPreset("
|
|
1858
|
+
const preset = getModelPreset("claude-3.5-sonnet");
|
|
3025
1859
|
return estimateCostFromBudget(budget, preset, config);
|
|
3026
1860
|
}
|
|
1861
|
+
function calculateDetailedTokenROI(params) {
|
|
1862
|
+
const {
|
|
1863
|
+
totalTokens,
|
|
1864
|
+
avgContextBudget,
|
|
1865
|
+
potentialSavings,
|
|
1866
|
+
fragmentationScore,
|
|
1867
|
+
developerCount,
|
|
1868
|
+
queriesPerDevPerDay = 60
|
|
1869
|
+
} = params;
|
|
1870
|
+
const budget = calculateTokenBudget({
|
|
1871
|
+
totalContextTokens: avgContextBudget,
|
|
1872
|
+
wastedTokens: {
|
|
1873
|
+
duplication: potentialSavings * 0.8,
|
|
1874
|
+
// 80% of potential savings are duplication-based
|
|
1875
|
+
fragmentation: totalTokens * fragmentationScore * 0.5,
|
|
1876
|
+
// fragmentation impact
|
|
1877
|
+
chattiness: totalTokens * 0.1
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
const model = getModelPreset("claude-3.5-sonnet");
|
|
1881
|
+
const cost = estimateCostFromBudget(budget, model, {
|
|
1882
|
+
developerCount,
|
|
1883
|
+
queriesPerDevPerDay
|
|
1884
|
+
});
|
|
1885
|
+
return {
|
|
1886
|
+
monthlySavings: Math.round(cost.total),
|
|
1887
|
+
contextTaxPerDev: Math.round(cost.total / (developerCount || 1) * 100) / 100,
|
|
1888
|
+
efficiencyGain: Math.round(budget.efficiencyRatio * 100) / 100
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
3027
1891
|
function calculateTokenBudget(params) {
|
|
3028
1892
|
const { totalContextTokens, wastedTokens } = params;
|
|
3029
1893
|
const estimatedResponseTokens = params.estimatedResponseTokens ?? totalContextTokens * 0.2;
|
|
@@ -3579,45 +2443,175 @@ function calculatePatternEntropy(files) {
|
|
|
3579
2443
|
recommendations
|
|
3580
2444
|
};
|
|
3581
2445
|
}
|
|
3582
|
-
function calculateConceptCohesion(params) {
|
|
3583
|
-
const { exports } = params;
|
|
3584
|
-
if (exports.length === 0) {
|
|
3585
|
-
return {
|
|
3586
|
-
score: 1,
|
|
3587
|
-
rating: "excellent",
|
|
3588
|
-
analysis: {
|
|
3589
|
-
uniqueDomains: 0,
|
|
3590
|
-
domainConcentration: 0,
|
|
3591
|
-
exportPurposeClarity: 1
|
|
3592
|
-
}
|
|
3593
|
-
};
|
|
2446
|
+
function calculateConceptCohesion(params) {
|
|
2447
|
+
const { exports } = params;
|
|
2448
|
+
if (exports.length === 0) {
|
|
2449
|
+
return {
|
|
2450
|
+
score: 1,
|
|
2451
|
+
rating: "excellent",
|
|
2452
|
+
analysis: {
|
|
2453
|
+
uniqueDomains: 0,
|
|
2454
|
+
domainConcentration: 0,
|
|
2455
|
+
exportPurposeClarity: 1
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
const allDomains = [];
|
|
2460
|
+
for (const exp of exports) {
|
|
2461
|
+
if (exp.inferredDomain) allDomains.push(exp.inferredDomain);
|
|
2462
|
+
if (exp.domains) allDomains.push(...exp.domains);
|
|
2463
|
+
}
|
|
2464
|
+
const uniqueDomains = new Set(allDomains);
|
|
2465
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
2466
|
+
for (const d of allDomains)
|
|
2467
|
+
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
2468
|
+
const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
|
|
2469
|
+
const domainConcentration = maxCount / allDomains.length;
|
|
2470
|
+
const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports.length);
|
|
2471
|
+
const score = domainConcentration * 0.5 + exportPurposeClarity * 0.5;
|
|
2472
|
+
let rating;
|
|
2473
|
+
if (score > 0.8) rating = "excellent";
|
|
2474
|
+
else if (score > 0.6) rating = "good";
|
|
2475
|
+
else if (score > 0.4) rating = "moderate";
|
|
2476
|
+
else rating = "poor";
|
|
2477
|
+
return {
|
|
2478
|
+
score: Math.round(score * 100) / 100,
|
|
2479
|
+
rating,
|
|
2480
|
+
analysis: {
|
|
2481
|
+
uniqueDomains: uniqueDomains.size,
|
|
2482
|
+
domainConcentration: Math.round(domainConcentration * 100) / 100,
|
|
2483
|
+
exportPurposeClarity: Math.round(exportPurposeClarity * 100) / 100
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// src/future-proof-metrics.ts
|
|
2489
|
+
function calculateFutureProofScore(params) {
|
|
2490
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2491
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2492
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2493
|
+
const overall = Math.round(
|
|
2494
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2495
|
+
);
|
|
2496
|
+
const factors = [
|
|
2497
|
+
{
|
|
2498
|
+
name: "Cognitive Load",
|
|
2499
|
+
impact: Math.round(loadScore - 50),
|
|
2500
|
+
description: params.cognitiveLoad.rating
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
name: "Pattern Entropy",
|
|
2504
|
+
impact: Math.round(entropyScore - 50),
|
|
2505
|
+
description: params.patternEntropy.rating
|
|
2506
|
+
},
|
|
2507
|
+
{
|
|
2508
|
+
name: "Concept Cohesion",
|
|
2509
|
+
impact: Math.round(cohesionScore - 50),
|
|
2510
|
+
description: params.conceptCohesion.rating
|
|
2511
|
+
}
|
|
2512
|
+
];
|
|
2513
|
+
const recommendations = collectBaseFutureProofRecommendations({
|
|
2514
|
+
patternEntropy: params.patternEntropy,
|
|
2515
|
+
conceptCohesion: params.conceptCohesion
|
|
2516
|
+
});
|
|
2517
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2518
|
+
return {
|
|
2519
|
+
toolName: "future-proof",
|
|
2520
|
+
score: overall,
|
|
2521
|
+
rawMetrics: {
|
|
2522
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2523
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2524
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2525
|
+
semanticDistanceAvg
|
|
2526
|
+
},
|
|
2527
|
+
factors,
|
|
2528
|
+
recommendations
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
function calculateExtendedFutureProofScore(params) {
|
|
2532
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2533
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2534
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2535
|
+
const aiSignalClarityScore = 100 - params.aiSignalClarity.score;
|
|
2536
|
+
const groundingScore = params.agentGrounding.score;
|
|
2537
|
+
const testabilityScore = params.testability.score;
|
|
2538
|
+
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
2539
|
+
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
2540
|
+
let totalWeight = 0.8;
|
|
2541
|
+
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
2542
|
+
if (params.docDrift) {
|
|
2543
|
+
overall += docDriftScore * 0.1;
|
|
2544
|
+
totalWeight += 0.1;
|
|
3594
2545
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
if (exp.domains) allDomains.push(...exp.domains);
|
|
2546
|
+
if (params.dependencyHealth) {
|
|
2547
|
+
overall += depsHealthScore * 0.1;
|
|
2548
|
+
totalWeight += 0.1;
|
|
3599
2549
|
}
|
|
3600
|
-
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
2550
|
+
overall = Math.round(overall / totalWeight);
|
|
2551
|
+
const factors = [
|
|
2552
|
+
{
|
|
2553
|
+
name: "Cognitive Load",
|
|
2554
|
+
impact: Math.round(loadScore - 50),
|
|
2555
|
+
description: params.cognitiveLoad.rating
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
name: "Pattern Entropy",
|
|
2559
|
+
impact: Math.round(entropyScore - 50),
|
|
2560
|
+
description: params.patternEntropy.rating
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
name: "Concept Cohesion",
|
|
2564
|
+
impact: Math.round(cohesionScore - 50),
|
|
2565
|
+
description: params.conceptCohesion.rating
|
|
2566
|
+
},
|
|
2567
|
+
{
|
|
2568
|
+
name: "AI Signal Clarity",
|
|
2569
|
+
impact: Math.round(aiSignalClarityScore - 50),
|
|
2570
|
+
description: `${params.aiSignalClarity.rating} risk`
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
name: "Agent Grounding",
|
|
2574
|
+
impact: Math.round(groundingScore - 50),
|
|
2575
|
+
description: params.agentGrounding.rating
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
name: "Testability",
|
|
2579
|
+
impact: Math.round(testabilityScore - 50),
|
|
2580
|
+
description: params.testability.rating
|
|
3620
2581
|
}
|
|
2582
|
+
];
|
|
2583
|
+
if (params.docDrift) {
|
|
2584
|
+
factors.push({
|
|
2585
|
+
name: "Documentation Drift",
|
|
2586
|
+
impact: Math.round(docDriftScore - 50),
|
|
2587
|
+
description: params.docDrift.rating
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
if (params.dependencyHealth) {
|
|
2591
|
+
factors.push({
|
|
2592
|
+
name: "Dependency Health",
|
|
2593
|
+
impact: Math.round(depsHealthScore - 50),
|
|
2594
|
+
description: params.dependencyHealth.rating
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
const recommendations = collectFutureProofRecommendations(params);
|
|
2598
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2599
|
+
return {
|
|
2600
|
+
toolName: "future-proof",
|
|
2601
|
+
score: overall,
|
|
2602
|
+
rawMetrics: {
|
|
2603
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2604
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2605
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2606
|
+
aiSignalClarityScore: params.aiSignalClarity.score,
|
|
2607
|
+
agentGroundingScore: params.agentGrounding.score,
|
|
2608
|
+
testabilityScore: params.testability.score,
|
|
2609
|
+
docDriftScore: params.docDrift?.score,
|
|
2610
|
+
dependencyHealthScore: params.dependencyHealth?.score,
|
|
2611
|
+
semanticDistanceAvg
|
|
2612
|
+
},
|
|
2613
|
+
factors,
|
|
2614
|
+
recommendations
|
|
3621
2615
|
};
|
|
3622
2616
|
}
|
|
3623
2617
|
|
|
@@ -4173,145 +3167,15 @@ function calculateChangeAmplification(params) {
|
|
|
4173
3167
|
};
|
|
4174
3168
|
}
|
|
4175
3169
|
|
|
4176
|
-
// src/future-proof-metrics.ts
|
|
4177
|
-
function calculateFutureProofScore(params) {
|
|
4178
|
-
const loadScore = 100 - params.cognitiveLoad.score;
|
|
4179
|
-
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
4180
|
-
const cohesionScore = params.conceptCohesion.score * 100;
|
|
4181
|
-
const overall = Math.round(
|
|
4182
|
-
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
4183
|
-
);
|
|
4184
|
-
const factors = [
|
|
4185
|
-
{
|
|
4186
|
-
name: "Cognitive Load",
|
|
4187
|
-
impact: Math.round(loadScore - 50),
|
|
4188
|
-
description: params.cognitiveLoad.rating
|
|
4189
|
-
},
|
|
4190
|
-
{
|
|
4191
|
-
name: "Pattern Entropy",
|
|
4192
|
-
impact: Math.round(entropyScore - 50),
|
|
4193
|
-
description: params.patternEntropy.rating
|
|
4194
|
-
},
|
|
4195
|
-
{
|
|
4196
|
-
name: "Concept Cohesion",
|
|
4197
|
-
impact: Math.round(cohesionScore - 50),
|
|
4198
|
-
description: params.conceptCohesion.rating
|
|
4199
|
-
}
|
|
4200
|
-
];
|
|
4201
|
-
const recommendations = collectBaseFutureProofRecommendations({
|
|
4202
|
-
patternEntropy: params.patternEntropy,
|
|
4203
|
-
conceptCohesion: params.conceptCohesion
|
|
4204
|
-
});
|
|
4205
|
-
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
4206
|
-
return {
|
|
4207
|
-
toolName: "future-proof",
|
|
4208
|
-
score: overall,
|
|
4209
|
-
rawMetrics: {
|
|
4210
|
-
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
4211
|
-
entropyScore: params.patternEntropy.entropy,
|
|
4212
|
-
cohesionScore: params.conceptCohesion.score,
|
|
4213
|
-
semanticDistanceAvg
|
|
4214
|
-
},
|
|
4215
|
-
factors,
|
|
4216
|
-
recommendations
|
|
4217
|
-
};
|
|
4218
|
-
}
|
|
4219
|
-
function calculateExtendedFutureProofScore(params) {
|
|
4220
|
-
const loadScore = 100 - params.cognitiveLoad.score;
|
|
4221
|
-
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
4222
|
-
const cohesionScore = params.conceptCohesion.score * 100;
|
|
4223
|
-
const aiSignalClarityScore = 100 - params.aiSignalClarity.score;
|
|
4224
|
-
const groundingScore = params.agentGrounding.score;
|
|
4225
|
-
const testabilityScore = params.testability.score;
|
|
4226
|
-
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
4227
|
-
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
4228
|
-
let totalWeight = 0.8;
|
|
4229
|
-
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
4230
|
-
if (params.docDrift) {
|
|
4231
|
-
overall += docDriftScore * 0.1;
|
|
4232
|
-
totalWeight += 0.1;
|
|
4233
|
-
}
|
|
4234
|
-
if (params.dependencyHealth) {
|
|
4235
|
-
overall += depsHealthScore * 0.1;
|
|
4236
|
-
totalWeight += 0.1;
|
|
4237
|
-
}
|
|
4238
|
-
overall = Math.round(overall / totalWeight);
|
|
4239
|
-
const factors = [
|
|
4240
|
-
{
|
|
4241
|
-
name: "Cognitive Load",
|
|
4242
|
-
impact: Math.round(loadScore - 50),
|
|
4243
|
-
description: params.cognitiveLoad.rating
|
|
4244
|
-
},
|
|
4245
|
-
{
|
|
4246
|
-
name: "Pattern Entropy",
|
|
4247
|
-
impact: Math.round(entropyScore - 50),
|
|
4248
|
-
description: params.patternEntropy.rating
|
|
4249
|
-
},
|
|
4250
|
-
{
|
|
4251
|
-
name: "Concept Cohesion",
|
|
4252
|
-
impact: Math.round(cohesionScore - 50),
|
|
4253
|
-
description: params.conceptCohesion.rating
|
|
4254
|
-
},
|
|
4255
|
-
{
|
|
4256
|
-
name: "AI Signal Clarity",
|
|
4257
|
-
impact: Math.round(aiSignalClarityScore - 50),
|
|
4258
|
-
description: `${params.aiSignalClarity.rating} risk`
|
|
4259
|
-
},
|
|
4260
|
-
{
|
|
4261
|
-
name: "Agent Grounding",
|
|
4262
|
-
impact: Math.round(groundingScore - 50),
|
|
4263
|
-
description: params.agentGrounding.rating
|
|
4264
|
-
},
|
|
4265
|
-
{
|
|
4266
|
-
name: "Testability",
|
|
4267
|
-
impact: Math.round(testabilityScore - 50),
|
|
4268
|
-
description: params.testability.rating
|
|
4269
|
-
}
|
|
4270
|
-
];
|
|
4271
|
-
if (params.docDrift) {
|
|
4272
|
-
factors.push({
|
|
4273
|
-
name: "Documentation Drift",
|
|
4274
|
-
impact: Math.round(docDriftScore - 50),
|
|
4275
|
-
description: params.docDrift.rating
|
|
4276
|
-
});
|
|
4277
|
-
}
|
|
4278
|
-
if (params.dependencyHealth) {
|
|
4279
|
-
factors.push({
|
|
4280
|
-
name: "Dependency Health",
|
|
4281
|
-
impact: Math.round(depsHealthScore - 50),
|
|
4282
|
-
description: params.dependencyHealth.rating
|
|
4283
|
-
});
|
|
4284
|
-
}
|
|
4285
|
-
const recommendations = collectFutureProofRecommendations(params);
|
|
4286
|
-
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
4287
|
-
return {
|
|
4288
|
-
toolName: "future-proof",
|
|
4289
|
-
score: overall,
|
|
4290
|
-
rawMetrics: {
|
|
4291
|
-
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
4292
|
-
entropyScore: params.patternEntropy.entropy,
|
|
4293
|
-
cohesionScore: params.conceptCohesion.score,
|
|
4294
|
-
aiSignalClarityScore: params.aiSignalClarity.score,
|
|
4295
|
-
agentGroundingScore: params.agentGrounding.score,
|
|
4296
|
-
testabilityScore: params.testability.score,
|
|
4297
|
-
docDriftScore: params.docDrift?.score,
|
|
4298
|
-
dependencyHealthScore: params.dependencyHealth?.score,
|
|
4299
|
-
semanticDistanceAvg
|
|
4300
|
-
},
|
|
4301
|
-
factors,
|
|
4302
|
-
recommendations
|
|
4303
|
-
};
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
3170
|
// src/utils/history.ts
|
|
4307
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
4308
|
-
import { join as
|
|
3171
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
3172
|
+
import { join as join4, dirname as dirname4 } from "path";
|
|
4309
3173
|
function getHistoryPath(rootDir) {
|
|
4310
|
-
return
|
|
3174
|
+
return join4(rootDir, ".aiready", "history.json");
|
|
4311
3175
|
}
|
|
4312
3176
|
function loadScoreHistory(rootDir) {
|
|
4313
3177
|
const historyPath = getHistoryPath(rootDir);
|
|
4314
|
-
if (!
|
|
3178
|
+
if (!existsSync4(historyPath)) {
|
|
4315
3179
|
return [];
|
|
4316
3180
|
}
|
|
4317
3181
|
try {
|
|
@@ -4324,8 +3188,8 @@ function loadScoreHistory(rootDir) {
|
|
|
4324
3188
|
}
|
|
4325
3189
|
function saveScoreEntry(rootDir, entry) {
|
|
4326
3190
|
const historyPath = getHistoryPath(rootDir);
|
|
4327
|
-
const historyDir =
|
|
4328
|
-
if (!
|
|
3191
|
+
const historyDir = dirname4(historyPath);
|
|
3192
|
+
if (!existsSync4(historyDir)) {
|
|
4329
3193
|
mkdirSync2(historyDir, { recursive: true });
|
|
4330
3194
|
}
|
|
4331
3195
|
const history = loadScoreHistory(rootDir);
|
|
@@ -4372,7 +3236,7 @@ function exportHistory(rootDir, format = "json") {
|
|
|
4372
3236
|
}
|
|
4373
3237
|
function clearHistory(rootDir) {
|
|
4374
3238
|
const historyPath = getHistoryPath(rootDir);
|
|
4375
|
-
if (
|
|
3239
|
+
if (existsSync4(historyPath)) {
|
|
4376
3240
|
writeFileSync2(historyPath, JSON.stringify([]));
|
|
4377
3241
|
}
|
|
4378
3242
|
}
|
|
@@ -4539,8 +3403,10 @@ export {
|
|
|
4539
3403
|
TypeScriptParser,
|
|
4540
3404
|
UnifiedReportSchema,
|
|
4541
3405
|
VAGUE_FILE_NAMES,
|
|
3406
|
+
buildFactorsFromDimensions,
|
|
4542
3407
|
buildSimpleProviderScore,
|
|
4543
3408
|
buildSpokeOutput,
|
|
3409
|
+
buildStandardToolScore,
|
|
4544
3410
|
calculateAgentGrounding,
|
|
4545
3411
|
calculateAiSignalClarity,
|
|
4546
3412
|
calculateBusinessROI,
|
|
@@ -4550,9 +3416,11 @@ export {
|
|
|
4550
3416
|
calculateConceptCohesion,
|
|
4551
3417
|
calculateDebtInterest,
|
|
4552
3418
|
calculateDependencyHealth,
|
|
3419
|
+
calculateDetailedTokenROI,
|
|
4553
3420
|
calculateDocDrift,
|
|
4554
3421
|
calculateExtendedFutureProofScore,
|
|
4555
3422
|
calculateFutureProofScore,
|
|
3423
|
+
calculateHeuristicConfidence,
|
|
4556
3424
|
calculateImportSimilarity,
|
|
4557
3425
|
calculateKnowledgeConcentration,
|
|
4558
3426
|
calculateMonthlyCost,
|
|
@@ -4560,25 +3428,39 @@ export {
|
|
|
4560
3428
|
calculatePatternEntropy,
|
|
4561
3429
|
calculateProductivityImpact,
|
|
4562
3430
|
calculateSemanticDistance,
|
|
3431
|
+
calculateStringSimilarity,
|
|
4563
3432
|
calculateTechnicalValueChain,
|
|
4564
3433
|
calculateTestabilityIndex,
|
|
4565
3434
|
calculateTokenBudget,
|
|
4566
3435
|
clearHistory,
|
|
4567
3436
|
createProvider,
|
|
3437
|
+
createStandardProgressCallback,
|
|
3438
|
+
displayStandardConsoleReport,
|
|
4568
3439
|
emitAnnotation,
|
|
4569
3440
|
emitIssuesAsAnnotations,
|
|
4570
3441
|
emitProgress,
|
|
4571
3442
|
estimateCostFromBudget,
|
|
4572
3443
|
estimateTokens,
|
|
4573
3444
|
exportHistory,
|
|
3445
|
+
extractCodeBlocks,
|
|
4574
3446
|
findLatestReport,
|
|
4575
3447
|
findLatestScanReport,
|
|
4576
3448
|
formatAcceptanceRate,
|
|
4577
3449
|
formatCost,
|
|
4578
3450
|
formatHours,
|
|
4579
3451
|
formatScore,
|
|
3452
|
+
formatStandardCliResult,
|
|
3453
|
+
formatStandardReport,
|
|
4580
3454
|
formatToolScore,
|
|
3455
|
+
generateCompleteReport,
|
|
4581
3456
|
generateHTML,
|
|
3457
|
+
generateIssueSummary,
|
|
3458
|
+
generateReportFooter,
|
|
3459
|
+
generateReportHead,
|
|
3460
|
+
generateReportHero,
|
|
3461
|
+
generateScoreCard,
|
|
3462
|
+
generateStatCards,
|
|
3463
|
+
generateTable,
|
|
4582
3464
|
generateValueChain,
|
|
4583
3465
|
getElapsedTime,
|
|
4584
3466
|
getFileCommitTimestamps,
|
|
@@ -4587,26 +3469,36 @@ export {
|
|
|
4587
3469
|
getLineRangeLastModifiedCached,
|
|
4588
3470
|
getModelPreset,
|
|
4589
3471
|
getParser,
|
|
3472
|
+
getPriorityIcon,
|
|
4590
3473
|
getProjectSizeTier,
|
|
4591
3474
|
getRating,
|
|
4592
3475
|
getRatingDisplay,
|
|
3476
|
+
getRatingEmoji,
|
|
3477
|
+
getRatingLabel,
|
|
3478
|
+
getRatingMetadata,
|
|
4593
3479
|
getRatingSlug,
|
|
4594
3480
|
getRatingWithContext,
|
|
4595
3481
|
getRecommendedThreshold,
|
|
4596
3482
|
getRepoMetadata,
|
|
3483
|
+
getReportTimestamp,
|
|
4597
3484
|
getSafetyIcon,
|
|
4598
3485
|
getScoreBar,
|
|
3486
|
+
getScoreColor,
|
|
4599
3487
|
getSeverityBadge,
|
|
4600
3488
|
getSeverityColor,
|
|
4601
3489
|
getSeverityEnum,
|
|
4602
3490
|
getSeverityLevel,
|
|
4603
3491
|
getSeverityValue,
|
|
4604
3492
|
getSupportedLanguages,
|
|
3493
|
+
getTerminalDivider,
|
|
3494
|
+
getToolEmoji,
|
|
4605
3495
|
getToolWeight,
|
|
4606
3496
|
getWasmPath,
|
|
4607
3497
|
groupIssuesByFile,
|
|
4608
3498
|
handleCLIError,
|
|
4609
3499
|
handleJSONOutput,
|
|
3500
|
+
handleStandardJSONOutput,
|
|
3501
|
+
inferPatternType,
|
|
4610
3502
|
initTreeSitter,
|
|
4611
3503
|
initializeParsers,
|
|
4612
3504
|
isFileSupported,
|
|
@@ -4623,13 +3515,18 @@ export {
|
|
|
4623
3515
|
parseFileExports,
|
|
4624
3516
|
parseWeightString,
|
|
4625
3517
|
predictAcceptanceRate,
|
|
3518
|
+
prepareActionConfig,
|
|
3519
|
+
printTerminalHeader,
|
|
4626
3520
|
readFileContent,
|
|
3521
|
+
resolveOutputFormat,
|
|
4627
3522
|
resolveOutputPath,
|
|
3523
|
+
runStandardCliAction,
|
|
4628
3524
|
saveScoreEntry,
|
|
4629
3525
|
scanEntries,
|
|
4630
3526
|
scanFiles,
|
|
4631
3527
|
setupParser,
|
|
4632
3528
|
severityToAnnotationLevel,
|
|
4633
3529
|
validateSpokeOutput,
|
|
4634
|
-
validateWithSchema
|
|
3530
|
+
validateWithSchema,
|
|
3531
|
+
wrapInCard
|
|
4635
3532
|
};
|