@aiready/core 0.23.20 → 0.23.22
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-REU6OUBT.mjs → chunk-DBOPSRBC.mjs} +47 -22
- package/dist/chunk-E55RNGGK.mjs +852 -0
- 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-SQHS6PFL.mjs → chunk-UYLH35LA.mjs} +47 -22
- package/dist/{chunk-ZB3EHHAG.mjs → chunk-WVNVC2PP.mjs} +90 -60
- package/dist/chunk-WYOW6O3P.mjs +114 -0
- package/dist/{chunk-RMH2TPAT.mjs → chunk-YRSSR4KN.mjs} +87 -59
- package/dist/client/index.d.mts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +922 -0
- package/dist/client/index.mjs +104 -0
- 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-Ctl_0z6F.d.mts +1294 -0
- package/dist/client-Ctl_0z6F.d.ts +1294 -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-CYz0qxGB.d.mts → client-DcqGfDTt.d.mts} +90 -23
- package/dist/{client-CYz0qxGB.d.ts → client-DcqGfDTt.d.ts} +90 -23
- package/dist/{client-jGuH6TAG.d.mts → client-O8RvSRm0.d.mts} +18 -1
- package/dist/{client-jGuH6TAG.d.ts → client-O8RvSRm0.d.ts} +18 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +47 -13
- package/dist/client.mjs +6 -4
- 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-Ctl_0z6F.d.mts +1294 -0
- package/dist/index-Ctl_0z6F.d.ts +1294 -0
- package/dist/index.d.mts +372 -165
- package/dist/index.d.ts +372 -165
- package/dist/index.js +3825 -3123
- package/dist/index.mjs +901 -2128
- 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 +6 -6
- package/dist/chunk-2Y6WZCES.mjs +0 -859
- package/dist/chunk-5SHLHMH7.mjs +0 -760
- package/dist/chunk-CGOS2J6T.mjs +0 -807
- package/dist/chunk-FMNCV4CC.mjs +0 -859
- package/dist/chunk-Q55AMEFV.mjs +0 -760
- package/dist/chunk-ST75O5C5.mjs +0 -859
- package/dist/chunk-TJXR2CHZ.mjs +0 -799
- 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-DLvFR2qA.d.mts +0 -1197
- package/dist/client-DLvFR2qA.d.ts +0 -1197
- package/dist/client-PFPdeo-z.d.mts +0 -1186
- package/dist/client-PFPdeo-z.d.ts +0 -1186
- package/dist/client-WVCAIWdJ.d.mts +0 -1192
- package/dist/client-WVCAIWdJ.d.ts +0 -1192
- package/dist/client-pYldIAg2.d.mts +0 -1209
- package/dist/client-pYldIAg2.d.ts +0 -1209
- 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,
|
|
@@ -47,6 +44,7 @@ import {
|
|
|
47
44
|
getRatingDisplay,
|
|
48
45
|
getRatingEmoji,
|
|
49
46
|
getRatingLabel,
|
|
47
|
+
getRatingMetadata,
|
|
50
48
|
getRatingSlug,
|
|
51
49
|
getRatingWithContext,
|
|
52
50
|
getRecommendedThreshold,
|
|
@@ -54,7 +52,33 @@ import {
|
|
|
54
52
|
getToolWeight,
|
|
55
53
|
normalizeToolName,
|
|
56
54
|
parseWeightString
|
|
57
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-E55RNGGK.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";
|
|
58
82
|
|
|
59
83
|
// src/utils/normalization.ts
|
|
60
84
|
function normalizeIssue(raw) {
|
|
@@ -508,6 +532,30 @@ import {
|
|
|
508
532
|
} from "fs";
|
|
509
533
|
import { join as join2, dirname as dirname2, resolve as resolvePath } from "path";
|
|
510
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
|
+
}
|
|
511
559
|
function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
|
|
512
560
|
let outputPath;
|
|
513
561
|
if (userPath) {
|
|
@@ -523,10 +571,7 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
|
|
|
523
571
|
const aireadyDir = join2(baseDir, ".aiready");
|
|
524
572
|
outputPath = join2(aireadyDir, defaultFilename);
|
|
525
573
|
}
|
|
526
|
-
|
|
527
|
-
if (!existsSync2(parentDir)) {
|
|
528
|
-
mkdirSync(parentDir, { recursive: true });
|
|
529
|
-
}
|
|
574
|
+
ensureDir(outputPath);
|
|
530
575
|
return outputPath;
|
|
531
576
|
}
|
|
532
577
|
async function loadMergedConfig(directory, defaults, cliOptions) {
|
|
@@ -541,16 +586,24 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
|
|
|
541
586
|
}
|
|
542
587
|
function handleJSONOutput(data, outputFile, successMessage) {
|
|
543
588
|
if (outputFile) {
|
|
544
|
-
|
|
545
|
-
if (!existsSync2(dir)) {
|
|
546
|
-
mkdirSync(dir, { recursive: true });
|
|
547
|
-
}
|
|
589
|
+
ensureDir(outputFile);
|
|
548
590
|
writeFileSync(outputFile, JSON.stringify(data, null, 2));
|
|
549
591
|
console.log(successMessage || `\u2705 Results saved to ${outputFile}`);
|
|
550
592
|
} else {
|
|
551
593
|
console.log(JSON.stringify(data, null, 2));
|
|
552
594
|
}
|
|
553
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
|
+
}
|
|
554
607
|
function handleCLIError(error, commandName) {
|
|
555
608
|
console.error(`\u274C ${commandName} failed:`, error);
|
|
556
609
|
process.exit(1);
|
|
@@ -583,33 +636,30 @@ function emitProgress(processed, total, toolId, message, onProgress, throttleCou
|
|
|
583
636
|
}
|
|
584
637
|
}
|
|
585
638
|
function getSeverityColor(severity, chalkInstance = chalk) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
case "
|
|
589
|
-
case "blind-risk":
|
|
639
|
+
const normalized = normalizeSeverity(severity);
|
|
640
|
+
switch (normalized) {
|
|
641
|
+
case "critical" /* Critical */:
|
|
590
642
|
return chalkInstance.red;
|
|
591
|
-
case "major"
|
|
592
|
-
case "moderate-risk":
|
|
643
|
+
case "major" /* Major */:
|
|
593
644
|
return chalkInstance.yellow;
|
|
594
|
-
case "minor"
|
|
595
|
-
case "safe":
|
|
645
|
+
case "minor" /* Minor */:
|
|
596
646
|
return chalkInstance.green;
|
|
597
|
-
case "info"
|
|
647
|
+
case "info" /* Info */:
|
|
598
648
|
return chalkInstance.blue;
|
|
599
649
|
default:
|
|
600
650
|
return chalkInstance.white;
|
|
601
651
|
}
|
|
602
652
|
}
|
|
603
653
|
function getSeverityValue(s) {
|
|
604
|
-
|
|
605
|
-
switch (
|
|
606
|
-
case "critical"
|
|
654
|
+
const normalized = normalizeSeverity(s);
|
|
655
|
+
switch (normalized) {
|
|
656
|
+
case "critical" /* Critical */:
|
|
607
657
|
return 4;
|
|
608
|
-
case "major"
|
|
658
|
+
case "major" /* Major */:
|
|
609
659
|
return 3;
|
|
610
|
-
case "minor"
|
|
660
|
+
case "minor" /* Minor */:
|
|
611
661
|
return 2;
|
|
612
|
-
case "info"
|
|
662
|
+
case "info" /* Info */:
|
|
613
663
|
return 1;
|
|
614
664
|
default:
|
|
615
665
|
return 0;
|
|
@@ -644,24 +694,15 @@ function getSeverityEnum(s) {
|
|
|
644
694
|
return "major";
|
|
645
695
|
case 2:
|
|
646
696
|
return "minor";
|
|
647
|
-
case 1:
|
|
648
|
-
return "info";
|
|
649
697
|
default:
|
|
650
698
|
return "info";
|
|
651
699
|
}
|
|
652
700
|
}
|
|
653
701
|
function findLatestReport(dirPath) {
|
|
654
702
|
const aireadyDir = resolvePath(dirPath, ".aiready");
|
|
655
|
-
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
let files = readdirSync(aireadyDir).filter(
|
|
659
|
-
(f) => f.startsWith("aiready-report-") && f.endsWith(".json")
|
|
660
|
-
);
|
|
703
|
+
let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
|
|
661
704
|
if (files.length === 0) {
|
|
662
|
-
files =
|
|
663
|
-
(f) => f.startsWith("aiready-scan-") && f.endsWith(".json")
|
|
664
|
-
);
|
|
705
|
+
files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
|
|
665
706
|
}
|
|
666
707
|
if (files.length === 0) {
|
|
667
708
|
return null;
|
|
@@ -675,14 +716,8 @@ function findLatestReport(dirPath) {
|
|
|
675
716
|
}
|
|
676
717
|
function findLatestScanReport(scanReportsDir, reportFilePrefix) {
|
|
677
718
|
try {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const files = readdirSync(scanReportsDir);
|
|
681
|
-
if (files.length > 0) {
|
|
682
|
-
const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
|
|
683
|
-
reportFiles = files.filter((file) => prefixRegex.test(file));
|
|
684
|
-
}
|
|
685
|
-
}
|
|
719
|
+
const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
|
|
720
|
+
const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
|
|
686
721
|
if (reportFiles.length === 0) return null;
|
|
687
722
|
reportFiles.sort((a, b) => {
|
|
688
723
|
const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
|
|
@@ -696,6 +731,53 @@ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
|
|
|
696
731
|
}
|
|
697
732
|
}
|
|
698
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
|
+
|
|
699
781
|
// src/utils/provider-utils.ts
|
|
700
782
|
function groupIssuesByFile(issues) {
|
|
701
783
|
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
@@ -769,1828 +851,129 @@ function calculateImportSimilarity(export1, export2) {
|
|
|
769
851
|
}
|
|
770
852
|
|
|
771
853
|
// src/utils/dependency-analyzer.ts
|
|
772
|
-
import { parse as parse2 } from "@typescript-eslint/typescript-estree";
|
|
773
|
-
|
|
774
|
-
// src/parsers/typescript-parser.ts
|
|
775
854
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
776
|
-
|
|
855
|
+
|
|
856
|
+
// src/parsers/parser-factory.ts
|
|
857
|
+
var ParserFactory = class _ParserFactory {
|
|
858
|
+
/**
|
|
859
|
+
* Create a new ParserFactory instance
|
|
860
|
+
*/
|
|
777
861
|
constructor() {
|
|
778
|
-
this.
|
|
779
|
-
this.
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
loc: true,
|
|
808
|
-
range: true,
|
|
809
|
-
tokens: true,
|
|
810
|
-
comment: true,
|
|
811
|
-
jsx: filePath.endsWith("x")
|
|
812
|
-
});
|
|
813
|
-
const imports = this.extractImports(ast);
|
|
814
|
-
const exports = this.extractExports(ast, code);
|
|
815
|
-
return {
|
|
816
|
-
exports,
|
|
817
|
-
imports,
|
|
818
|
-
language: this.language
|
|
819
|
-
};
|
|
820
|
-
} catch (error) {
|
|
821
|
-
throw new ParseError(error.message, filePath, {
|
|
822
|
-
line: error.lineNumber || 1,
|
|
823
|
-
column: error.column || 0
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
getNamingConventions() {
|
|
828
|
-
return {
|
|
829
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
830
|
-
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
831
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
832
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
833
|
-
typePattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
834
|
-
interfacePattern: /^I?[A-Z][a-zA-Z0-9]*$/
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
analyzeMetadata(node, code) {
|
|
838
|
-
if (!code) return {};
|
|
839
|
-
return {
|
|
840
|
-
isPure: this.isLikelyPure(node),
|
|
841
|
-
hasSideEffects: !this.isLikelyPure(node)
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
extractImports(ast) {
|
|
845
|
-
const imports = [];
|
|
846
|
-
for (const node of ast.body) {
|
|
847
|
-
if (node.type === "ImportDeclaration") {
|
|
848
|
-
const specifiers = [];
|
|
849
|
-
let isTypeOnly = false;
|
|
850
|
-
if (node.importKind === "type") {
|
|
851
|
-
isTypeOnly = true;
|
|
852
|
-
}
|
|
853
|
-
for (const spec of node.specifiers) {
|
|
854
|
-
if (spec.type === "ImportSpecifier") {
|
|
855
|
-
const imported = spec.imported;
|
|
856
|
-
const name = imported.type === "Identifier" ? imported.name : imported.value;
|
|
857
|
-
specifiers.push(name);
|
|
858
|
-
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
859
|
-
specifiers.push("default");
|
|
860
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
861
|
-
specifiers.push("*");
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
imports.push({
|
|
865
|
-
source: node.source.value,
|
|
866
|
-
specifiers,
|
|
867
|
-
isTypeOnly,
|
|
868
|
-
loc: node.loc ? {
|
|
869
|
-
start: {
|
|
870
|
-
line: node.loc.start.line,
|
|
871
|
-
column: node.loc.start.column
|
|
872
|
-
},
|
|
873
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
874
|
-
} : void 0
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return imports;
|
|
879
|
-
}
|
|
880
|
-
extractExports(ast, code) {
|
|
881
|
-
const exports = [];
|
|
882
|
-
for (const node of ast.body) {
|
|
883
|
-
if (node.type === "ExportNamedDeclaration") {
|
|
884
|
-
if (node.declaration) {
|
|
885
|
-
const declaration = node.declaration;
|
|
886
|
-
if ((declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") && declaration.id) {
|
|
887
|
-
exports.push(
|
|
888
|
-
this.createExport(
|
|
889
|
-
declaration.id.name,
|
|
890
|
-
"function",
|
|
891
|
-
node,
|
|
892
|
-
// Pass the outer ExportNamedDeclaration
|
|
893
|
-
code
|
|
894
|
-
)
|
|
895
|
-
);
|
|
896
|
-
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
897
|
-
exports.push(
|
|
898
|
-
this.createExport(
|
|
899
|
-
declaration.id.name,
|
|
900
|
-
"class",
|
|
901
|
-
node,
|
|
902
|
-
// Pass the outer ExportNamedDeclaration
|
|
903
|
-
code
|
|
904
|
-
)
|
|
905
|
-
);
|
|
906
|
-
} else if (declaration.type === "TSTypeAliasDeclaration") {
|
|
907
|
-
exports.push(
|
|
908
|
-
this.createExport(
|
|
909
|
-
declaration.id.name,
|
|
910
|
-
"type",
|
|
911
|
-
node,
|
|
912
|
-
// Pass the outer ExportNamedDeclaration
|
|
913
|
-
code
|
|
914
|
-
)
|
|
915
|
-
);
|
|
916
|
-
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
917
|
-
exports.push(
|
|
918
|
-
this.createExport(
|
|
919
|
-
declaration.id.name,
|
|
920
|
-
"interface",
|
|
921
|
-
node,
|
|
922
|
-
// Pass the outer ExportNamedDeclaration
|
|
923
|
-
code
|
|
924
|
-
)
|
|
925
|
-
);
|
|
926
|
-
} else if (declaration.type === "VariableDeclaration") {
|
|
927
|
-
for (const decl of declaration.declarations) {
|
|
928
|
-
if (decl.id.type === "Identifier") {
|
|
929
|
-
exports.push(
|
|
930
|
-
this.createExport(
|
|
931
|
-
decl.id.name,
|
|
932
|
-
"const",
|
|
933
|
-
node,
|
|
934
|
-
// Pass the outer ExportNamedDeclaration
|
|
935
|
-
code,
|
|
936
|
-
decl.init
|
|
937
|
-
)
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
} else if (node.type === "ExportDefaultDeclaration") {
|
|
944
|
-
exports.push(this.createExport("default", "default", node, code));
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
return exports;
|
|
948
|
-
}
|
|
949
|
-
createExport(name, type, node, code, initializer) {
|
|
950
|
-
const documentation = this.extractDocumentation(node, code);
|
|
951
|
-
let methodCount;
|
|
952
|
-
let propertyCount;
|
|
953
|
-
let parameters;
|
|
954
|
-
let isPrimitive = false;
|
|
955
|
-
if (initializer) {
|
|
956
|
-
if (initializer.type === "Literal" || initializer.type === "TemplateLiteral" && initializer.expressions.length === 0) {
|
|
957
|
-
isPrimitive = true;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
const structNode = node.type === "ExportNamedDeclaration" ? node.declaration : node.type === "ExportDefaultDeclaration" ? node.declaration : node;
|
|
961
|
-
if (structNode.type === "ClassDeclaration" || structNode.type === "TSInterfaceDeclaration") {
|
|
962
|
-
const body = structNode.type === "ClassDeclaration" ? structNode.body.body : structNode.body.body;
|
|
963
|
-
methodCount = body.filter(
|
|
964
|
-
(m) => m.type === "MethodDefinition" || m.type === "TSMethodSignature"
|
|
965
|
-
).length;
|
|
966
|
-
propertyCount = body.filter(
|
|
967
|
-
(m) => m.type === "PropertyDefinition" || m.type === "TSPropertySignature"
|
|
968
|
-
).length;
|
|
969
|
-
if (structNode.type === "ClassDeclaration") {
|
|
970
|
-
const constructor = body.find(
|
|
971
|
-
(m) => m.type === "MethodDefinition" && m.kind === "constructor"
|
|
972
|
-
);
|
|
973
|
-
if (constructor && constructor.value && constructor.value.params) {
|
|
974
|
-
parameters = constructor.value.params.map((p) => {
|
|
975
|
-
if (p.type === "Identifier") return p.name;
|
|
976
|
-
if (p.type === "TSParameterProperty" && p.parameter.type === "Identifier") {
|
|
977
|
-
return p.parameter.name;
|
|
978
|
-
}
|
|
979
|
-
return void 0;
|
|
980
|
-
}).filter(Boolean);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
if (!parameters && (structNode.type === "FunctionDeclaration" || structNode.type === "TSDeclareFunction" || structNode.type === "MethodDefinition")) {
|
|
985
|
-
const funcNode = structNode.type === "MethodDefinition" ? structNode.value : structNode;
|
|
986
|
-
if (funcNode && funcNode.params) {
|
|
987
|
-
parameters = funcNode.params.map((p) => {
|
|
988
|
-
if (p.type === "Identifier") return p.name;
|
|
989
|
-
return void 0;
|
|
990
|
-
}).filter(Boolean);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
return {
|
|
994
|
-
name,
|
|
995
|
-
type,
|
|
996
|
-
isPrimitive,
|
|
997
|
-
loc: node.loc ? {
|
|
998
|
-
start: { line: node.loc.start.line, column: node.loc.start.column },
|
|
999
|
-
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
1000
|
-
} : void 0,
|
|
1001
|
-
documentation,
|
|
1002
|
-
methodCount,
|
|
1003
|
-
propertyCount,
|
|
1004
|
-
parameters,
|
|
1005
|
-
isPure: this.isLikelyPure(node),
|
|
1006
|
-
hasSideEffects: !this.isLikelyPure(node)
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
extractDocumentation(node, code) {
|
|
1010
|
-
if (node.range) {
|
|
1011
|
-
const start = node.range[0];
|
|
1012
|
-
const precedingCode = code.substring(0, start);
|
|
1013
|
-
const jsdocMatch = precedingCode.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
1014
|
-
if (jsdocMatch) {
|
|
1015
|
-
return {
|
|
1016
|
-
content: jsdocMatch[1].trim(),
|
|
1017
|
-
type: "jsdoc"
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
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
|
+
});
|
|
1022
891
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const body = structNode.type === "MethodDefinition" ? structNode.value.body : structNode.body;
|
|
1029
|
-
if (body && body.type === "BlockStatement") {
|
|
1030
|
-
const bodyContent = JSON.stringify(body);
|
|
1031
|
-
if (bodyContent.includes('"name":"console"') || bodyContent.includes('"name":"process"') || bodyContent.includes('"type":"AssignmentExpression"')) {
|
|
1032
|
-
return false;
|
|
1033
|
-
}
|
|
1034
|
-
return true;
|
|
1035
|
-
}
|
|
1036
|
-
return true;
|
|
1037
|
-
}
|
|
1038
|
-
return false;
|
|
892
|
+
/**
|
|
893
|
+
* Register a lazy-loaded parser
|
|
894
|
+
*/
|
|
895
|
+
registerLazyParser(language, loader) {
|
|
896
|
+
this.registeredParsers.set(language, loader);
|
|
1039
897
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
try {
|
|
1049
|
-
let prev = node.previousSibling || null;
|
|
1050
|
-
while (prev && /comment/i.test(prev.type)) {
|
|
1051
|
-
const text = prev.text || "";
|
|
1052
|
-
const loc = {
|
|
1053
|
-
start: {
|
|
1054
|
-
line: prev.startPosition.row + 1,
|
|
1055
|
-
column: prev.startPosition.column
|
|
1056
|
-
},
|
|
1057
|
-
end: {
|
|
1058
|
-
line: prev.endPosition.row + 1,
|
|
1059
|
-
column: prev.endPosition.column
|
|
1060
|
-
}
|
|
1061
|
-
};
|
|
1062
|
-
if (text.trim().startsWith("/**") || text.trim().startsWith("/*")) {
|
|
1063
|
-
metadata.documentation = {
|
|
1064
|
-
content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
|
|
1065
|
-
type: "comment",
|
|
1066
|
-
loc
|
|
1067
|
-
};
|
|
1068
|
-
break;
|
|
1069
|
-
}
|
|
1070
|
-
if (text.trim().startsWith("///")) {
|
|
1071
|
-
metadata.documentation = {
|
|
1072
|
-
content: text.replace(/^\/\/\//, "").trim(),
|
|
1073
|
-
type: "xml-doc",
|
|
1074
|
-
loc
|
|
1075
|
-
};
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
if (text.trim().startsWith("//")) {
|
|
1079
|
-
metadata.documentation = {
|
|
1080
|
-
content: text.replace(/^\/\//, "").trim(),
|
|
1081
|
-
type: "comment",
|
|
1082
|
-
loc
|
|
1083
|
-
};
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
prev = prev.previousSibling;
|
|
1087
|
-
}
|
|
1088
|
-
if (node.type === "function_definition" || node.type === "class_definition") {
|
|
1089
|
-
const body2 = node.childForFieldName ? node.childForFieldName("body") : node.children.find((c) => c.type === "block");
|
|
1090
|
-
if (body2 && body2.children.length > 0) {
|
|
1091
|
-
const firstStmt = body2.children[0];
|
|
1092
|
-
if (firstStmt.type === "expression_statement" && firstStmt.firstChild?.type === "string") {
|
|
1093
|
-
metadata.documentation = {
|
|
1094
|
-
content: firstStmt.firstChild.text.replace(/['"`]/g, "").trim(),
|
|
1095
|
-
type: "docstring",
|
|
1096
|
-
loc: {
|
|
1097
|
-
start: {
|
|
1098
|
-
line: firstStmt.startPosition.row + 1,
|
|
1099
|
-
column: firstStmt.startPosition.column
|
|
1100
|
-
},
|
|
1101
|
-
end: {
|
|
1102
|
-
line: firstStmt.endPosition.row + 1,
|
|
1103
|
-
column: firstStmt.endPosition.column
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
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();
|
|
1109
906
|
}
|
|
1110
|
-
|
|
907
|
+
return _ParserFactory.instance;
|
|
1111
908
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
"System.err",
|
|
1122
|
-
"Files.write",
|
|
1123
|
-
"process.exit",
|
|
1124
|
-
"exit("
|
|
1125
|
-
];
|
|
1126
|
-
const signatures = Array.from(
|
|
1127
|
-
/* @__PURE__ */ new Set([...options?.sideEffectSignatures || [], ...defaultSignatures])
|
|
1128
|
-
);
|
|
1129
|
-
const walk = (n) => {
|
|
1130
|
-
try {
|
|
1131
|
-
const t = n.type || "";
|
|
1132
|
-
if (/assign|assignment|assignment_statement|assignment_expression|throw|throw_statement|send_statement|global_statement|nonlocal_statement/i.test(
|
|
1133
|
-
t
|
|
1134
|
-
)) {
|
|
1135
|
-
metadata.isPure = false;
|
|
1136
|
-
metadata.hasSideEffects = true;
|
|
1137
|
-
}
|
|
1138
|
-
const text = n.text || "";
|
|
1139
|
-
for (const s of signatures) {
|
|
1140
|
-
if (text.includes(s)) {
|
|
1141
|
-
metadata.isPure = false;
|
|
1142
|
-
metadata.hasSideEffects = true;
|
|
1143
|
-
break;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1147
|
-
const c = n.child(i);
|
|
1148
|
-
if (c) walk(c);
|
|
1149
|
-
}
|
|
1150
|
-
} catch {
|
|
1151
|
-
}
|
|
1152
|
-
};
|
|
1153
|
-
const body = node.childForFieldName?.("body") || node.children.find(
|
|
1154
|
-
(c) => /body|block|class_body|declaration_list|function_body/.test(c.type)
|
|
1155
|
-
);
|
|
1156
|
-
if (body) walk(body);
|
|
1157
|
-
return metadata;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// src/parsers/tree-sitter-utils.ts
|
|
1161
|
-
import * as Parser from "web-tree-sitter";
|
|
1162
|
-
import * as path from "path";
|
|
1163
|
-
import * as fs from "fs";
|
|
1164
|
-
var isTreeSitterInitialized = false;
|
|
1165
|
-
async function initTreeSitter() {
|
|
1166
|
-
if (isTreeSitterInitialized) return;
|
|
1167
|
-
try {
|
|
1168
|
-
const wasmPath = getWasmPath("web-tree-sitter");
|
|
1169
|
-
await Parser.Parser.init({
|
|
1170
|
-
locateFile() {
|
|
1171
|
-
return wasmPath || "web-tree-sitter.wasm";
|
|
1172
|
-
}
|
|
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);
|
|
1173
918
|
});
|
|
1174
|
-
isTreeSitterInitialized = true;
|
|
1175
|
-
} catch (error) {
|
|
1176
|
-
console.error("Failed to initialize web-tree-sitter:", error);
|
|
1177
|
-
isTreeSitterInitialized = true;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
function findInPnpmStore(startDir, fileName, depth = 0) {
|
|
1181
|
-
if (depth > 8) return null;
|
|
1182
|
-
const pnpmDir = path.join(startDir, "node_modules", ".pnpm");
|
|
1183
|
-
if (fs.existsSync(pnpmDir)) {
|
|
1184
|
-
return findFileRecursively(pnpmDir, fileName, 0);
|
|
1185
919
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
for (const entry of entries) {
|
|
1200
|
-
if (entry.isDirectory()) {
|
|
1201
|
-
const found = findFileRecursively(
|
|
1202
|
-
path.join(dir, entry.name),
|
|
1203
|
-
fileName,
|
|
1204
|
-
depth + 1
|
|
1205
|
-
);
|
|
1206
|
-
if (found) return found;
|
|
1207
|
-
}
|
|
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;
|
|
1208
931
|
}
|
|
1209
|
-
} catch {
|
|
1210
|
-
}
|
|
1211
|
-
return null;
|
|
1212
|
-
}
|
|
1213
|
-
function getWasmPath(language) {
|
|
1214
|
-
const wasmFileName = language === "web-tree-sitter" ? "web-tree-sitter.wasm" : `tree-sitter-${language}.wasm`;
|
|
1215
|
-
const immediatePaths = [
|
|
1216
|
-
path.join(process.cwd(), wasmFileName),
|
|
1217
|
-
path.join(__dirname, wasmFileName),
|
|
1218
|
-
path.join(__dirname, "assets", wasmFileName)
|
|
1219
|
-
];
|
|
1220
|
-
for (const p of immediatePaths) {
|
|
1221
|
-
if (fs.existsSync(p)) return p;
|
|
1222
|
-
}
|
|
1223
|
-
const pnpmPath = findInPnpmStore(__dirname, wasmFileName);
|
|
1224
|
-
if (pnpmPath) return pnpmPath;
|
|
1225
|
-
const pnpmPathCwd = findInPnpmStore(process.cwd(), wasmFileName);
|
|
1226
|
-
if (pnpmPathCwd) return pnpmPathCwd;
|
|
1227
|
-
console.warn(
|
|
1228
|
-
`[Parser] WASM file for ${language} not found. CWD: ${process.cwd()}, DIR: ${__dirname}`
|
|
1229
|
-
);
|
|
1230
|
-
return null;
|
|
1231
|
-
}
|
|
1232
|
-
async function setupParser(language) {
|
|
1233
|
-
await initTreeSitter();
|
|
1234
|
-
const wasmPath = getWasmPath(language);
|
|
1235
|
-
if (!wasmPath) {
|
|
1236
|
-
return null;
|
|
1237
|
-
}
|
|
1238
|
-
try {
|
|
1239
|
-
const parser = new Parser.Parser();
|
|
1240
|
-
const Lang = await Parser.Language.load(wasmPath);
|
|
1241
|
-
parser.setLanguage(Lang);
|
|
1242
|
-
return parser;
|
|
1243
|
-
} catch {
|
|
1244
932
|
return null;
|
|
1245
933
|
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
// src/parsers/base-parser.ts
|
|
1249
|
-
var BaseLanguageParser = class {
|
|
1250
|
-
constructor() {
|
|
1251
|
-
this.parser = null;
|
|
1252
|
-
this.initialized = false;
|
|
1253
|
-
}
|
|
1254
934
|
/**
|
|
1255
|
-
*
|
|
935
|
+
* Get parser for a file based on its extension
|
|
1256
936
|
*/
|
|
1257
|
-
async
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
} catch (error) {
|
|
1263
|
-
console.warn(`Failed to initialize ${this.language} parser:`, error);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
async getAST(code, _filePath) {
|
|
1267
|
-
void _filePath;
|
|
1268
|
-
if (!this.initialized) await this.initialize();
|
|
1269
|
-
if (!this.parser) return null;
|
|
1270
|
-
return this.parser.parse(code);
|
|
1271
|
-
}
|
|
1272
|
-
parse(code, filePath) {
|
|
1273
|
-
if (!this.initialized || !this.parser) {
|
|
1274
|
-
return this.parseRegex(code, filePath);
|
|
1275
|
-
}
|
|
1276
|
-
try {
|
|
1277
|
-
const tree = this.parser.parse(code);
|
|
1278
|
-
if (!tree || tree.rootNode.type === "ERROR" || tree.rootNode.hasError) {
|
|
1279
|
-
return this.parseRegex(code, filePath);
|
|
1280
|
-
}
|
|
1281
|
-
const imports = this.extractImportsAST(tree.rootNode);
|
|
1282
|
-
const exports = this.extractExportsAST(tree.rootNode, code);
|
|
1283
|
-
return {
|
|
1284
|
-
exports,
|
|
1285
|
-
imports,
|
|
1286
|
-
language: this.language,
|
|
1287
|
-
warnings: []
|
|
1288
|
-
};
|
|
1289
|
-
} catch (error) {
|
|
1290
|
-
console.warn(
|
|
1291
|
-
`AST parsing failed for ${filePath}, falling back to regex: ${error.message}`
|
|
1292
|
-
);
|
|
1293
|
-
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;
|
|
1294
942
|
}
|
|
943
|
+
return this.getParserForLanguage(language);
|
|
1295
944
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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);
|
|
1299
951
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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);
|
|
1308
961
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Get all supported file extensions
|
|
964
|
+
*/
|
|
965
|
+
getSupportedExtensions() {
|
|
966
|
+
return Array.from(this.extensionMap.keys());
|
|
1311
967
|
}
|
|
1312
968
|
/**
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
* @param node - Tree-sitter node to analyze.
|
|
1316
|
-
* @param code - Source code for context.
|
|
1317
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
969
|
+
* Get language for a file
|
|
1318
970
|
*/
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
});
|
|
971
|
+
getLanguageForFile(filePath) {
|
|
972
|
+
const ext = this.getFileExtension(filePath);
|
|
973
|
+
return this.extensionMap.get(ext) || null;
|
|
1323
974
|
}
|
|
1324
975
|
/**
|
|
1325
|
-
* Extract
|
|
1326
|
-
*
|
|
1327
|
-
* @param rootNode - Root node of the Python AST.
|
|
1328
|
-
* @returns Array of discovered FileImport objects.
|
|
1329
|
-
*/
|
|
1330
|
-
extractImportsAST(rootNode) {
|
|
1331
|
-
const imports = [];
|
|
1332
|
-
const processImportNode = (node) => {
|
|
1333
|
-
if (node.type === "import_statement") {
|
|
1334
|
-
for (const child of node.children) {
|
|
1335
|
-
if (child.type === "dotted_name") {
|
|
1336
|
-
const source = child.text;
|
|
1337
|
-
imports.push({
|
|
1338
|
-
source,
|
|
1339
|
-
specifiers: [source],
|
|
1340
|
-
loc: {
|
|
1341
|
-
start: {
|
|
1342
|
-
line: child.startPosition.row + 1,
|
|
1343
|
-
column: child.startPosition.column
|
|
1344
|
-
},
|
|
1345
|
-
end: {
|
|
1346
|
-
line: child.endPosition.row + 1,
|
|
1347
|
-
column: child.endPosition.column
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
});
|
|
1351
|
-
} else if (child.type === "aliased_import") {
|
|
1352
|
-
const nameNode = child.childForFieldName("name");
|
|
1353
|
-
if (nameNode) {
|
|
1354
|
-
const source = nameNode.text;
|
|
1355
|
-
imports.push({
|
|
1356
|
-
source,
|
|
1357
|
-
specifiers: [source],
|
|
1358
|
-
loc: {
|
|
1359
|
-
start: {
|
|
1360
|
-
line: child.startPosition.row + 1,
|
|
1361
|
-
column: child.startPosition.column
|
|
1362
|
-
},
|
|
1363
|
-
end: {
|
|
1364
|
-
line: child.endPosition.row + 1,
|
|
1365
|
-
column: child.endPosition.column
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
} else if (node.type === "import_from_statement") {
|
|
1373
|
-
const moduleNameNode = node.childForFieldName("module_name");
|
|
1374
|
-
if (moduleNameNode) {
|
|
1375
|
-
const source = moduleNameNode.text;
|
|
1376
|
-
const specifiers = [];
|
|
1377
|
-
for (const child of node.children) {
|
|
1378
|
-
if (child.type === "dotted_name" && child !== moduleNameNode) {
|
|
1379
|
-
specifiers.push(child.text);
|
|
1380
|
-
} else if (child.type === "aliased_import") {
|
|
1381
|
-
const nameNode = child.childForFieldName("name");
|
|
1382
|
-
if (nameNode) specifiers.push(nameNode.text);
|
|
1383
|
-
} else if (child.type === "wildcard_import") {
|
|
1384
|
-
specifiers.push("*");
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
if (specifiers.length > 0) {
|
|
1388
|
-
imports.push({
|
|
1389
|
-
source,
|
|
1390
|
-
specifiers,
|
|
1391
|
-
loc: {
|
|
1392
|
-
start: {
|
|
1393
|
-
line: node.startPosition.row + 1,
|
|
1394
|
-
column: node.startPosition.column
|
|
1395
|
-
},
|
|
1396
|
-
end: {
|
|
1397
|
-
line: node.endPosition.row + 1,
|
|
1398
|
-
column: node.endPosition.column
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
};
|
|
1406
|
-
for (const node of rootNode.children) {
|
|
1407
|
-
processImportNode(node);
|
|
1408
|
-
}
|
|
1409
|
-
return imports;
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Extract export information using AST walk.
|
|
1413
|
-
*
|
|
1414
|
-
* @param rootNode - Root node of the Python AST.
|
|
1415
|
-
* @param code - Source code for documentation extraction.
|
|
1416
|
-
* @returns Array of discovered ExportInfo objects.
|
|
1417
|
-
*/
|
|
1418
|
-
extractExportsAST(rootNode, code) {
|
|
1419
|
-
const exports = [];
|
|
1420
|
-
for (const node of rootNode.children) {
|
|
1421
|
-
if (node.type === "function_definition") {
|
|
1422
|
-
const nameNode = node.childForFieldName("name");
|
|
1423
|
-
if (nameNode) {
|
|
1424
|
-
const name = nameNode.text;
|
|
1425
|
-
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
1426
|
-
if (!isPrivate) {
|
|
1427
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1428
|
-
exports.push({
|
|
1429
|
-
name,
|
|
1430
|
-
type: "function",
|
|
1431
|
-
loc: {
|
|
1432
|
-
start: {
|
|
1433
|
-
line: node.startPosition.row + 1,
|
|
1434
|
-
column: node.startPosition.column
|
|
1435
|
-
},
|
|
1436
|
-
end: {
|
|
1437
|
-
line: node.endPosition.row + 1,
|
|
1438
|
-
column: node.endPosition.column
|
|
1439
|
-
}
|
|
1440
|
-
},
|
|
1441
|
-
parameters: this.extractParameters(node),
|
|
1442
|
-
...metadata
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
} else if (node.type === "class_definition") {
|
|
1447
|
-
const nameNode = node.childForFieldName("name");
|
|
1448
|
-
if (nameNode) {
|
|
1449
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1450
|
-
exports.push({
|
|
1451
|
-
name: nameNode.text,
|
|
1452
|
-
type: "class",
|
|
1453
|
-
loc: {
|
|
1454
|
-
start: {
|
|
1455
|
-
line: node.startPosition.row + 1,
|
|
1456
|
-
column: node.startPosition.column
|
|
1457
|
-
},
|
|
1458
|
-
end: {
|
|
1459
|
-
line: node.endPosition.row + 1,
|
|
1460
|
-
column: node.endPosition.column
|
|
1461
|
-
}
|
|
1462
|
-
},
|
|
1463
|
-
...metadata
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
} else if (node.type === "expression_statement") {
|
|
1467
|
-
const assignment = node.firstChild;
|
|
1468
|
-
if (assignment && assignment.type === "assignment") {
|
|
1469
|
-
const left = assignment.childForFieldName("left");
|
|
1470
|
-
if (left && left.type === "identifier") {
|
|
1471
|
-
const name = left.text;
|
|
1472
|
-
const isInternal = name === "__all__" || name === "__version__" || name === "__author__";
|
|
1473
|
-
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
1474
|
-
if (!isInternal && !isPrivate) {
|
|
1475
|
-
exports.push({
|
|
1476
|
-
name,
|
|
1477
|
-
type: name === name.toUpperCase() ? "const" : "variable",
|
|
1478
|
-
loc: {
|
|
1479
|
-
start: {
|
|
1480
|
-
line: node.startPosition.row + 1,
|
|
1481
|
-
column: node.startPosition.column
|
|
1482
|
-
},
|
|
1483
|
-
end: {
|
|
1484
|
-
line: node.endPosition.row + 1,
|
|
1485
|
-
column: node.endPosition.column
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
return exports;
|
|
1495
|
-
}
|
|
1496
|
-
/**
|
|
1497
|
-
* Extract parameter names from a function definition node.
|
|
1498
|
-
*
|
|
1499
|
-
* @param node - Function definition node.
|
|
1500
|
-
* @returns Array of parameter name strings.
|
|
1501
|
-
*/
|
|
1502
|
-
extractParameters(node) {
|
|
1503
|
-
const paramsNode = node.childForFieldName("parameters");
|
|
1504
|
-
if (!paramsNode) return [];
|
|
1505
|
-
return paramsNode.children.filter(
|
|
1506
|
-
(c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter"
|
|
1507
|
-
).map((c) => {
|
|
1508
|
-
if (c.type === "identifier") return c.text;
|
|
1509
|
-
if (c.type === "typed_parameter" || c.type === "default_parameter") {
|
|
1510
|
-
return c.firstChild?.text || "unknown";
|
|
1511
|
-
}
|
|
1512
|
-
return "unknown";
|
|
1513
|
-
});
|
|
1514
|
-
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
1517
|
-
*
|
|
1518
|
-
* @param code - Source code content.
|
|
1519
|
-
* @param filePath - Path to the file being parsed.
|
|
1520
|
-
* @returns Consolidated ParseResult.
|
|
1521
|
-
*/
|
|
1522
|
-
parseRegex(code, filePath) {
|
|
1523
|
-
try {
|
|
1524
|
-
const imports = this.extractImportsRegex(code, filePath);
|
|
1525
|
-
const exports = this.extractExportsRegex(code, filePath);
|
|
1526
|
-
return {
|
|
1527
|
-
exports,
|
|
1528
|
-
imports,
|
|
1529
|
-
language: "python" /* Python */,
|
|
1530
|
-
warnings: [
|
|
1531
|
-
"Python parsing is currently using regex-based extraction as tree-sitter wasm was not available."
|
|
1532
|
-
]
|
|
1533
|
-
};
|
|
1534
|
-
} catch (error) {
|
|
1535
|
-
const wrapper = new Error(
|
|
1536
|
-
`Failed to parse Python file ${filePath}: ${error.message}`
|
|
1537
|
-
);
|
|
1538
|
-
wrapper.cause = error;
|
|
1539
|
-
throw wrapper;
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
getNamingConventions() {
|
|
1543
|
-
return {
|
|
1544
|
-
variablePattern: /^[a-z_][a-z0-9_]*$/,
|
|
1545
|
-
functionPattern: /^[a-z_][a-z0-9_]*$/,
|
|
1546
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
1547
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
1548
|
-
exceptions: [
|
|
1549
|
-
"__init__",
|
|
1550
|
-
"__str__",
|
|
1551
|
-
"__repr__",
|
|
1552
|
-
"__name__",
|
|
1553
|
-
"__main__",
|
|
1554
|
-
"__file__",
|
|
1555
|
-
"__doc__",
|
|
1556
|
-
"__all__",
|
|
1557
|
-
"__version__",
|
|
1558
|
-
"__author__",
|
|
1559
|
-
"__dict__",
|
|
1560
|
-
"__class__",
|
|
1561
|
-
"__module__",
|
|
1562
|
-
"__bases__"
|
|
1563
|
-
]
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
canHandle(filePath) {
|
|
1567
|
-
return filePath.toLowerCase().endsWith(".py");
|
|
1568
|
-
}
|
|
1569
|
-
extractImportsRegex(code, _filePath) {
|
|
1570
|
-
void _filePath;
|
|
1571
|
-
const imports = [];
|
|
1572
|
-
const lines = code.split("\n");
|
|
1573
|
-
const importRegex = /^\s*import\s+([a-zA-Z0-9_., ]+)/;
|
|
1574
|
-
const fromImportRegex = /^\s*from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/;
|
|
1575
|
-
lines.forEach((line, idx) => {
|
|
1576
|
-
if (line.trim().startsWith("#")) return;
|
|
1577
|
-
const importMatch = line.match(importRegex);
|
|
1578
|
-
if (importMatch) {
|
|
1579
|
-
const modules = importMatch[1].split(",").map((m) => m.trim().split(" as ")[0]);
|
|
1580
|
-
modules.forEach((module) => {
|
|
1581
|
-
imports.push({
|
|
1582
|
-
source: module,
|
|
1583
|
-
specifiers: [module],
|
|
1584
|
-
loc: {
|
|
1585
|
-
start: { line: idx + 1, column: 0 },
|
|
1586
|
-
end: { line: idx + 1, column: line.length }
|
|
1587
|
-
}
|
|
1588
|
-
});
|
|
1589
|
-
});
|
|
1590
|
-
return;
|
|
1591
|
-
}
|
|
1592
|
-
const fromMatch = line.match(fromImportRegex);
|
|
1593
|
-
if (fromMatch) {
|
|
1594
|
-
const module = fromMatch[1];
|
|
1595
|
-
const imports_str = fromMatch[2];
|
|
1596
|
-
if (imports_str.trim() === "*") {
|
|
1597
|
-
imports.push({
|
|
1598
|
-
source: module,
|
|
1599
|
-
specifiers: ["*"],
|
|
1600
|
-
loc: {
|
|
1601
|
-
start: { line: idx + 1, column: 0 },
|
|
1602
|
-
end: { line: idx + 1, column: line.length }
|
|
1603
|
-
}
|
|
1604
|
-
});
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
|
|
1608
|
-
imports.push({
|
|
1609
|
-
source: module,
|
|
1610
|
-
specifiers,
|
|
1611
|
-
loc: {
|
|
1612
|
-
start: { line: idx + 1, column: 0 },
|
|
1613
|
-
end: { line: idx + 1, column: line.length }
|
|
1614
|
-
}
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
return imports;
|
|
1619
|
-
}
|
|
1620
|
-
extractExportsRegex(code, _filePath) {
|
|
1621
|
-
void _filePath;
|
|
1622
|
-
const exports = [];
|
|
1623
|
-
const lines = code.split("\n");
|
|
1624
|
-
const funcRegex = /^def\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
1625
|
-
const classRegex = /^class\s+([a-zA-Z0-9_]+)/;
|
|
1626
|
-
lines.forEach((line, idx) => {
|
|
1627
|
-
const indent = line.search(/\S/);
|
|
1628
|
-
if (indent !== 0) return;
|
|
1629
|
-
const classMatch = line.match(classRegex);
|
|
1630
|
-
if (classMatch) {
|
|
1631
|
-
exports.push({
|
|
1632
|
-
name: classMatch[1],
|
|
1633
|
-
type: "class",
|
|
1634
|
-
visibility: "public",
|
|
1635
|
-
isPure: true,
|
|
1636
|
-
hasSideEffects: false,
|
|
1637
|
-
loc: {
|
|
1638
|
-
start: { line: idx + 1, column: 0 },
|
|
1639
|
-
end: { line: idx + 1, column: line.length }
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
const funcMatch = line.match(funcRegex);
|
|
1645
|
-
if (funcMatch) {
|
|
1646
|
-
const name = funcMatch[1];
|
|
1647
|
-
if (name.startsWith("_") && !name.startsWith("__")) return;
|
|
1648
|
-
let docContent;
|
|
1649
|
-
const nextLines = lines.slice(idx + 1, idx + 4);
|
|
1650
|
-
for (const nextLine of nextLines) {
|
|
1651
|
-
const docMatch = nextLine.match(/^\s*"""([\s\S]*?)"""/) || nextLine.match(/^\s*'''([\s\S]*?)'''/);
|
|
1652
|
-
if (docMatch) {
|
|
1653
|
-
docContent = docMatch[1].trim();
|
|
1654
|
-
break;
|
|
1655
|
-
}
|
|
1656
|
-
if (nextLine.trim() && !nextLine.trim().startsWith('"""') && !nextLine.trim().startsWith("'''"))
|
|
1657
|
-
break;
|
|
1658
|
-
}
|
|
1659
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("print(") || idx + 1 < lines.length && lines[idx + 1].includes("print(");
|
|
1660
|
-
exports.push({
|
|
1661
|
-
name,
|
|
1662
|
-
type: "function",
|
|
1663
|
-
visibility: "public",
|
|
1664
|
-
isPure: !isImpure,
|
|
1665
|
-
hasSideEffects: isImpure,
|
|
1666
|
-
documentation: docContent ? { content: docContent, type: "docstring" } : void 0,
|
|
1667
|
-
loc: {
|
|
1668
|
-
start: { line: idx + 1, column: 0 },
|
|
1669
|
-
end: { line: idx + 1, column: line.length }
|
|
1670
|
-
}
|
|
1671
|
-
});
|
|
1672
|
-
}
|
|
1673
|
-
});
|
|
1674
|
-
return exports;
|
|
1675
|
-
}
|
|
1676
|
-
};
|
|
1677
|
-
|
|
1678
|
-
// src/parsers/shared-parser-utils.ts
|
|
1679
|
-
var SIDE_EFFECT_KEYWORDS = [
|
|
1680
|
-
"print(",
|
|
1681
|
-
"console.",
|
|
1682
|
-
"System.out",
|
|
1683
|
-
"System.err",
|
|
1684
|
-
"fmt.",
|
|
1685
|
-
"File.Write",
|
|
1686
|
-
"Files.write",
|
|
1687
|
-
"os.Exit",
|
|
1688
|
-
"panic(",
|
|
1689
|
-
"throw ",
|
|
1690
|
-
"Logging.",
|
|
1691
|
-
"log."
|
|
1692
|
-
];
|
|
1693
|
-
function analyzeGeneralMetadata(node, code, options = {}) {
|
|
1694
|
-
const metadata = {
|
|
1695
|
-
isPure: true,
|
|
1696
|
-
hasSideEffects: false
|
|
1697
|
-
};
|
|
1698
|
-
try {
|
|
1699
|
-
let prev = node.previousSibling || null;
|
|
1700
|
-
while (prev && /comment/i.test(prev.type)) {
|
|
1701
|
-
const text = prev.text || "";
|
|
1702
|
-
if (text.trim().startsWith("/**")) {
|
|
1703
|
-
metadata.documentation = {
|
|
1704
|
-
content: text.replace(/^[/*]+|[/*]+$/g, "").trim(),
|
|
1705
|
-
type: "jsdoc"
|
|
1706
|
-
};
|
|
1707
|
-
break;
|
|
1708
|
-
}
|
|
1709
|
-
if (text.trim().startsWith("///")) {
|
|
1710
|
-
metadata.documentation = {
|
|
1711
|
-
content: text.replace(/^\/\/\//, "").trim(),
|
|
1712
|
-
type: "xml-doc"
|
|
1713
|
-
};
|
|
1714
|
-
break;
|
|
1715
|
-
}
|
|
1716
|
-
if (text.trim().startsWith("//")) {
|
|
1717
|
-
metadata.documentation = {
|
|
1718
|
-
content: text.replace(/^\/\//, "").trim(),
|
|
1719
|
-
type: "comment"
|
|
1720
|
-
};
|
|
1721
|
-
break;
|
|
1722
|
-
}
|
|
1723
|
-
prev = prev.previousSibling;
|
|
1724
|
-
}
|
|
1725
|
-
} catch {
|
|
1726
|
-
}
|
|
1727
|
-
const signatures = [
|
|
1728
|
-
...SIDE_EFFECT_KEYWORDS,
|
|
1729
|
-
...options.sideEffectSignatures || []
|
|
1730
|
-
];
|
|
1731
|
-
const walk = (n) => {
|
|
1732
|
-
if (/assign|assignment|assignment_statement|assignment_expression/i.test(
|
|
1733
|
-
n.type
|
|
1734
|
-
)) {
|
|
1735
|
-
metadata.isPure = false;
|
|
1736
|
-
metadata.hasSideEffects = true;
|
|
1737
|
-
}
|
|
1738
|
-
const text = n.text;
|
|
1739
|
-
for (const sig of signatures) {
|
|
1740
|
-
if (text.includes(sig)) {
|
|
1741
|
-
metadata.isPure = false;
|
|
1742
|
-
metadata.hasSideEffects = true;
|
|
1743
|
-
break;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
if (!metadata.hasSideEffects) {
|
|
1747
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1748
|
-
const child = n.child(i);
|
|
1749
|
-
if (child) walk(child);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
};
|
|
1753
|
-
walk(node);
|
|
1754
|
-
return metadata;
|
|
1755
|
-
}
|
|
1756
|
-
function extractParameterNames(node) {
|
|
1757
|
-
const params = [];
|
|
1758
|
-
const candidates = [
|
|
1759
|
-
// common field name
|
|
1760
|
-
node.childForFieldName ? node.childForFieldName("parameters") : null,
|
|
1761
|
-
node.childForFieldName ? node.childForFieldName("parameter_list") : null,
|
|
1762
|
-
node.children.find((c) => c.type === "parameter_list") || null,
|
|
1763
|
-
node.children.find((c) => c.type === "parameters") || null,
|
|
1764
|
-
node.children.find((c) => c.type === "formal_parameters") || null,
|
|
1765
|
-
node.children.find((c) => c.type === "formal_parameter") || null
|
|
1766
|
-
];
|
|
1767
|
-
const list = candidates.find(Boolean);
|
|
1768
|
-
if (!list) return params;
|
|
1769
|
-
for (const child of list.children) {
|
|
1770
|
-
if (!child) continue;
|
|
1771
|
-
const id = child.childForFieldName?.("name") || child.children.find(
|
|
1772
|
-
(c) => [
|
|
1773
|
-
"identifier",
|
|
1774
|
-
"variable_name",
|
|
1775
|
-
"name",
|
|
1776
|
-
"parameter",
|
|
1777
|
-
"formal_parameter"
|
|
1778
|
-
].includes(c.type)
|
|
1779
|
-
) || (child.type === "identifier" ? child : void 0);
|
|
1780
|
-
if (id && typeof id.text === "string") params.push(id.text);
|
|
1781
|
-
}
|
|
1782
|
-
return params;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
// src/parsers/java-parser.ts
|
|
1786
|
-
var JavaParser = class extends BaseLanguageParser {
|
|
1787
|
-
constructor() {
|
|
1788
|
-
super(...arguments);
|
|
1789
|
-
this.language = "java" /* Java */;
|
|
1790
|
-
this.extensions = [".java"];
|
|
1791
|
-
}
|
|
1792
|
-
getParserName() {
|
|
1793
|
-
return "java";
|
|
1794
|
-
}
|
|
1795
|
-
/**
|
|
1796
|
-
* Analyze metadata for a Java node (purity, side effects).
|
|
1797
|
-
*
|
|
1798
|
-
* @param node - Tree-sitter node to analyze.
|
|
1799
|
-
* @param code - Source code for context.
|
|
1800
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
1801
|
-
*/
|
|
1802
|
-
analyzeMetadata(node, code) {
|
|
1803
|
-
return analyzeGeneralMetadata(node, code, {
|
|
1804
|
-
sideEffectSignatures: [
|
|
1805
|
-
"System.out",
|
|
1806
|
-
"System.err",
|
|
1807
|
-
"Files.write",
|
|
1808
|
-
"Logging."
|
|
1809
|
-
]
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
parseRegex(code) {
|
|
1813
|
-
const lines = code.split("\n");
|
|
1814
|
-
const exports = [];
|
|
1815
|
-
const imports = [];
|
|
1816
|
-
const importRegex = /^import\s+([a-zA-Z0-9_.]+)/;
|
|
1817
|
-
const classRegex = /^\s*(?:public\s+)?(?:class|interface|enum)\s+([a-zA-Z0-9_]+)/;
|
|
1818
|
-
const methodRegex = /^\s*public\s+(?:static\s+)?[a-zA-Z0-9_<>[\]]+\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
1819
|
-
let currentClassName = "";
|
|
1820
|
-
lines.forEach((line, idx) => {
|
|
1821
|
-
const importMatch = line.match(importRegex);
|
|
1822
|
-
if (importMatch) {
|
|
1823
|
-
const source = importMatch[1];
|
|
1824
|
-
imports.push({
|
|
1825
|
-
source,
|
|
1826
|
-
specifiers: [source.split(".").pop() || source],
|
|
1827
|
-
loc: {
|
|
1828
|
-
start: { line: idx + 1, column: 0 },
|
|
1829
|
-
end: { line: idx + 1, column: line.length }
|
|
1830
|
-
}
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
const classMatch = line.match(classRegex);
|
|
1834
|
-
if (classMatch) {
|
|
1835
|
-
currentClassName = classMatch[1];
|
|
1836
|
-
exports.push({
|
|
1837
|
-
name: currentClassName,
|
|
1838
|
-
type: line.includes("interface") ? "interface" : "class",
|
|
1839
|
-
visibility: "public",
|
|
1840
|
-
isPure: true,
|
|
1841
|
-
hasSideEffects: false,
|
|
1842
|
-
loc: {
|
|
1843
|
-
start: { line: idx + 1, column: 0 },
|
|
1844
|
-
end: { line: idx + 1, column: line.length }
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
}
|
|
1848
|
-
const methodMatch = line.match(methodRegex);
|
|
1849
|
-
if (methodMatch && currentClassName) {
|
|
1850
|
-
const name = methodMatch[1];
|
|
1851
|
-
let docContent;
|
|
1852
|
-
const prevLines = lines.slice(Math.max(0, idx - 5), idx);
|
|
1853
|
-
const prevText = prevLines.join("\n");
|
|
1854
|
-
const javadocMatch = prevText.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
1855
|
-
if (javadocMatch) {
|
|
1856
|
-
docContent = javadocMatch[1].replace(/^\s*\*+/gm, "").trim();
|
|
1857
|
-
}
|
|
1858
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("System.out");
|
|
1859
|
-
exports.push({
|
|
1860
|
-
name,
|
|
1861
|
-
type: "function",
|
|
1862
|
-
parentClass: currentClassName,
|
|
1863
|
-
visibility: "public",
|
|
1864
|
-
isPure: !isImpure,
|
|
1865
|
-
hasSideEffects: isImpure,
|
|
1866
|
-
documentation: docContent ? { content: docContent, type: "jsdoc" } : void 0,
|
|
1867
|
-
loc: {
|
|
1868
|
-
start: { line: idx + 1, column: 0 },
|
|
1869
|
-
end: { line: idx + 1, column: line.length }
|
|
1870
|
-
}
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
|
-
});
|
|
1874
|
-
return {
|
|
1875
|
-
exports,
|
|
1876
|
-
imports,
|
|
1877
|
-
language: "java" /* Java */,
|
|
1878
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
/**
|
|
1882
|
-
* Extract import information using AST walk.
|
|
1883
|
-
*
|
|
1884
|
-
* @param rootNode - Root node of the Java AST.
|
|
1885
|
-
* @returns Array of discovered FileImport objects.
|
|
1886
|
-
*/
|
|
1887
|
-
extractImportsAST(rootNode) {
|
|
1888
|
-
const imports = [];
|
|
1889
|
-
for (const node of rootNode.children) {
|
|
1890
|
-
if (node.type === "import_declaration") {
|
|
1891
|
-
const sourceArr = [];
|
|
1892
|
-
let isWildcard = false;
|
|
1893
|
-
for (const child of node.children) {
|
|
1894
|
-
if (child.type === "scoped_identifier" || child.type === "identifier") {
|
|
1895
|
-
sourceArr.push(child.text);
|
|
1896
|
-
}
|
|
1897
|
-
if (child.type === "asterisk") isWildcard = true;
|
|
1898
|
-
}
|
|
1899
|
-
const source = sourceArr.join(".");
|
|
1900
|
-
if (source) {
|
|
1901
|
-
imports.push({
|
|
1902
|
-
source: isWildcard ? `${source}.*` : source,
|
|
1903
|
-
specifiers: isWildcard ? ["*"] : [source.split(".").pop() || source],
|
|
1904
|
-
loc: {
|
|
1905
|
-
start: {
|
|
1906
|
-
line: node.startPosition.row + 1,
|
|
1907
|
-
column: node.startPosition.column
|
|
1908
|
-
},
|
|
1909
|
-
end: {
|
|
1910
|
-
line: node.endPosition.row + 1,
|
|
1911
|
-
column: node.endPosition.column
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return imports;
|
|
1919
|
-
}
|
|
1920
|
-
/**
|
|
1921
|
-
* Extract export information (classes, interfaces, methods) using AST walk.
|
|
1922
|
-
*
|
|
1923
|
-
* @param rootNode - Root node of the Java AST.
|
|
1924
|
-
* @param code - Source code for documentation extraction.
|
|
1925
|
-
* @returns Array of discovered ExportInfo objects.
|
|
1926
|
-
*/
|
|
1927
|
-
extractExportsAST(rootNode, code) {
|
|
1928
|
-
const exports = [];
|
|
1929
|
-
for (const node of rootNode.children) {
|
|
1930
|
-
if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration") {
|
|
1931
|
-
const nameNode = node.children.find((c) => c.type === "identifier");
|
|
1932
|
-
if (nameNode) {
|
|
1933
|
-
const modifiers = this.getModifiers(node);
|
|
1934
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1935
|
-
exports.push({
|
|
1936
|
-
name: nameNode.text,
|
|
1937
|
-
type: node.type === "class_declaration" ? "class" : "interface",
|
|
1938
|
-
loc: {
|
|
1939
|
-
start: {
|
|
1940
|
-
line: node.startPosition.row + 1,
|
|
1941
|
-
column: node.startPosition.column
|
|
1942
|
-
},
|
|
1943
|
-
end: {
|
|
1944
|
-
line: node.endPosition.row + 1,
|
|
1945
|
-
column: node.endPosition.column
|
|
1946
|
-
}
|
|
1947
|
-
},
|
|
1948
|
-
visibility: modifiers.includes("public") ? "public" : "private",
|
|
1949
|
-
...metadata
|
|
1950
|
-
});
|
|
1951
|
-
this.extractSubExports(node, nameNode.text, exports, code);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
return exports;
|
|
1956
|
-
}
|
|
1957
|
-
/**
|
|
1958
|
-
* Extract modifiers (visibility, static, etc.) from a node.
|
|
1959
|
-
*
|
|
1960
|
-
* @param node - AST node to extract modifiers from.
|
|
1961
|
-
* @returns Array of modifier strings.
|
|
1962
|
-
*/
|
|
1963
|
-
getModifiers(node) {
|
|
1964
|
-
const modifiersNode = node.children.find((c) => c.type === "modifiers");
|
|
1965
|
-
if (!modifiersNode) return [];
|
|
1966
|
-
return modifiersNode.children.map((c) => c.text);
|
|
1967
|
-
}
|
|
1968
|
-
/**
|
|
1969
|
-
* Extract methods and nested exports from a class or interface body.
|
|
1970
|
-
*
|
|
1971
|
-
* @param parentNode - Class or interface declaration node.
|
|
1972
|
-
* @param parentName - Name of the parent class/interface.
|
|
1973
|
-
* @param exports - Array to collect discovered exports into.
|
|
1974
|
-
* @param code - Source code for context.
|
|
1975
|
-
*/
|
|
1976
|
-
extractSubExports(parentNode, parentName, exports, code) {
|
|
1977
|
-
const bodyNode = parentNode.children.find((c) => c.type === "class_body");
|
|
1978
|
-
if (!bodyNode) return;
|
|
1979
|
-
for (const node of bodyNode.children) {
|
|
1980
|
-
if (node.type === "method_declaration") {
|
|
1981
|
-
const nameNode = node.children.find((c) => c.type === "identifier");
|
|
1982
|
-
const modifiers = this.getModifiers(node);
|
|
1983
|
-
if (nameNode && modifiers.includes("public")) {
|
|
1984
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
1985
|
-
exports.push({
|
|
1986
|
-
name: nameNode.text,
|
|
1987
|
-
type: "function",
|
|
1988
|
-
parentClass: parentName,
|
|
1989
|
-
visibility: "public",
|
|
1990
|
-
loc: {
|
|
1991
|
-
start: {
|
|
1992
|
-
line: node.startPosition.row + 1,
|
|
1993
|
-
column: node.startPosition.column
|
|
1994
|
-
},
|
|
1995
|
-
end: {
|
|
1996
|
-
line: node.endPosition.row + 1,
|
|
1997
|
-
column: node.endPosition.column
|
|
1998
|
-
}
|
|
1999
|
-
},
|
|
2000
|
-
parameters: this.extractParameters(node),
|
|
2001
|
-
...metadata
|
|
2002
|
-
});
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
extractParameters(node) {
|
|
2008
|
-
return extractParameterNames(node);
|
|
2009
|
-
}
|
|
2010
|
-
getNamingConventions() {
|
|
2011
|
-
return {
|
|
2012
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2013
|
-
functionPattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2014
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2015
|
-
constantPattern: /^[A-Z][A-Z0-9_]*$/,
|
|
2016
|
-
exceptions: ["main", "serialVersionUID"]
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
canHandle(filePath) {
|
|
2020
|
-
return filePath.toLowerCase().endsWith(".java");
|
|
2021
|
-
}
|
|
2022
|
-
};
|
|
2023
|
-
|
|
2024
|
-
// src/parsers/csharp-parser.ts
|
|
2025
|
-
var CSharpParser = class extends BaseLanguageParser {
|
|
2026
|
-
constructor() {
|
|
2027
|
-
super(...arguments);
|
|
2028
|
-
this.language = "csharp" /* CSharp */;
|
|
2029
|
-
this.extensions = [".cs"];
|
|
2030
|
-
}
|
|
2031
|
-
getParserName() {
|
|
2032
|
-
return "c_sharp";
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* Analyze metadata for a C# node (purity, side effects).
|
|
2036
|
-
*
|
|
2037
|
-
* @param node - Tree-sitter node to analyze.
|
|
2038
|
-
* @param code - Source code for context.
|
|
2039
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
2040
|
-
*/
|
|
2041
|
-
analyzeMetadata(node, code) {
|
|
2042
|
-
return analyzeGeneralMetadata(node, code, {
|
|
2043
|
-
sideEffectSignatures: ["Console.Write", "File.Write", "Logging."]
|
|
2044
|
-
});
|
|
2045
|
-
}
|
|
2046
|
-
/**
|
|
2047
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
2048
|
-
*
|
|
2049
|
-
* @param code - Source code content.
|
|
2050
|
-
* @returns Consolidated ParseResult.
|
|
2051
|
-
*/
|
|
2052
|
-
parseRegex(code) {
|
|
2053
|
-
const lines = code.split("\n");
|
|
2054
|
-
const exports = [];
|
|
2055
|
-
const imports = [];
|
|
2056
|
-
const usingRegex = /^using\s+([a-zA-Z0-9_.]+);/;
|
|
2057
|
-
const classRegex = /^\s*(?:public\s+)?class\s+([a-zA-Z0-9_]+)/;
|
|
2058
|
-
const methodRegex = /^\s*(?:public|protected)\s+(?:static\s+)?[a-zA-Z0-9_.]+\s+([a-zA-Z0-9_]+)\s*\(/;
|
|
2059
|
-
let currentClassName = "";
|
|
2060
|
-
lines.forEach((line, idx) => {
|
|
2061
|
-
const usingMatch = line.match(usingRegex);
|
|
2062
|
-
if (usingMatch) {
|
|
2063
|
-
const source = usingMatch[1];
|
|
2064
|
-
imports.push({
|
|
2065
|
-
source,
|
|
2066
|
-
specifiers: [source.split(".").pop() || source],
|
|
2067
|
-
loc: {
|
|
2068
|
-
start: { line: idx + 1, column: 0 },
|
|
2069
|
-
end: { line: idx + 1, column: line.length }
|
|
2070
|
-
}
|
|
2071
|
-
});
|
|
2072
|
-
}
|
|
2073
|
-
const classMatch = line.match(classRegex);
|
|
2074
|
-
if (classMatch) {
|
|
2075
|
-
currentClassName = classMatch[1];
|
|
2076
|
-
exports.push({
|
|
2077
|
-
name: currentClassName,
|
|
2078
|
-
type: "class",
|
|
2079
|
-
visibility: "public",
|
|
2080
|
-
isPure: true,
|
|
2081
|
-
hasSideEffects: false,
|
|
2082
|
-
loc: {
|
|
2083
|
-
start: { line: idx + 1, column: 0 },
|
|
2084
|
-
end: { line: idx + 1, column: line.length }
|
|
2085
|
-
}
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
const methodMatch = line.match(methodRegex);
|
|
2089
|
-
if (methodMatch && currentClassName) {
|
|
2090
|
-
const name = methodMatch[1];
|
|
2091
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("Console.WriteLine");
|
|
2092
|
-
exports.push({
|
|
2093
|
-
name,
|
|
2094
|
-
type: "function",
|
|
2095
|
-
parentClass: currentClassName,
|
|
2096
|
-
visibility: "public",
|
|
2097
|
-
isPure: !isImpure,
|
|
2098
|
-
hasSideEffects: isImpure,
|
|
2099
|
-
loc: {
|
|
2100
|
-
start: { line: idx + 1, column: 0 },
|
|
2101
|
-
end: { line: idx + 1, column: line.length }
|
|
2102
|
-
}
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
});
|
|
2106
|
-
return {
|
|
2107
|
-
exports,
|
|
2108
|
-
imports,
|
|
2109
|
-
language: "csharp" /* CSharp */,
|
|
2110
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
2111
|
-
};
|
|
2112
|
-
}
|
|
2113
|
-
/**
|
|
2114
|
-
* Extract import information (usings) using AST walk.
|
|
2115
|
-
*
|
|
2116
|
-
* @param rootNode - Root node of the C# AST.
|
|
2117
|
-
* @returns Array of discovered FileImport objects.
|
|
2118
|
-
*/
|
|
2119
|
-
extractImportsAST(rootNode) {
|
|
2120
|
-
const imports = [];
|
|
2121
|
-
const findUsings = (node) => {
|
|
2122
|
-
if (node.type === "using_directive") {
|
|
2123
|
-
const nameNode = node.childForFieldName("name") || node.children.find(
|
|
2124
|
-
(c) => c.type === "qualified_name" || c.type === "identifier"
|
|
2125
|
-
);
|
|
2126
|
-
if (nameNode) {
|
|
2127
|
-
const aliasNode = node.childForFieldName("alias");
|
|
2128
|
-
imports.push({
|
|
2129
|
-
source: nameNode.text,
|
|
2130
|
-
specifiers: aliasNode ? [aliasNode.text] : [nameNode.text.split(".").pop() || nameNode.text],
|
|
2131
|
-
loc: {
|
|
2132
|
-
start: {
|
|
2133
|
-
line: node.startPosition.row + 1,
|
|
2134
|
-
column: node.startPosition.column
|
|
2135
|
-
},
|
|
2136
|
-
end: {
|
|
2137
|
-
line: node.endPosition.row + 1,
|
|
2138
|
-
column: node.endPosition.column
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2145
|
-
const child = node.child(i);
|
|
2146
|
-
if (child) findUsings(child);
|
|
2147
|
-
}
|
|
2148
|
-
};
|
|
2149
|
-
findUsings(rootNode);
|
|
2150
|
-
return imports;
|
|
2151
|
-
}
|
|
2152
|
-
/**
|
|
2153
|
-
* Extract export information (classes, methods, properties) using AST walk.
|
|
2154
|
-
* Handles nested namespaces and classes.
|
|
2155
|
-
*
|
|
2156
|
-
* @param rootNode - Root node of the C# AST.
|
|
2157
|
-
* @param code - Source code for documentation extraction.
|
|
2158
|
-
* @returns Array of discovered ExportInfo objects.
|
|
2159
|
-
*/
|
|
2160
|
-
extractExportsAST(rootNode, code) {
|
|
2161
|
-
const exports = [];
|
|
2162
|
-
const traverse = (node, currentNamespace, currentClass) => {
|
|
2163
|
-
let nextNamespace = currentNamespace;
|
|
2164
|
-
let nextClass = currentClass;
|
|
2165
|
-
if (node.type === "namespace_declaration" || node.type === "file_scoped_namespace_declaration") {
|
|
2166
|
-
const nameNode = node.childForFieldName("name") || node.children.find(
|
|
2167
|
-
(c) => c.type === "identifier" || c.type === "qualified_name"
|
|
2168
|
-
);
|
|
2169
|
-
if (nameNode) {
|
|
2170
|
-
nextNamespace = currentNamespace ? `${currentNamespace}.${nameNode.text}` : nameNode.text;
|
|
2171
|
-
}
|
|
2172
|
-
} else if (node.type === "class_declaration" || node.type === "interface_declaration" || node.type === "enum_declaration" || node.type === "struct_declaration" || node.type === "record_declaration") {
|
|
2173
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2174
|
-
if (nameNode) {
|
|
2175
|
-
const modifiers = this.getModifiers(node);
|
|
2176
|
-
const isPublic = modifiers.includes("public") || modifiers.includes("protected");
|
|
2177
|
-
if (isPublic) {
|
|
2178
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2179
|
-
const type = node.type.replace("_declaration", "");
|
|
2180
|
-
const fullName = nextClass ? `${nextClass}.${nameNode.text}` : nextNamespace ? `${nextNamespace}.${nameNode.text}` : nameNode.text;
|
|
2181
|
-
exports.push({
|
|
2182
|
-
name: fullName,
|
|
2183
|
-
type: type === "record" ? "class" : type,
|
|
2184
|
-
loc: {
|
|
2185
|
-
start: {
|
|
2186
|
-
line: node.startPosition.row + 1,
|
|
2187
|
-
column: node.startPosition.column
|
|
2188
|
-
},
|
|
2189
|
-
end: {
|
|
2190
|
-
line: node.endPosition.row + 1,
|
|
2191
|
-
column: node.endPosition.column
|
|
2192
|
-
}
|
|
2193
|
-
},
|
|
2194
|
-
visibility: modifiers.includes("public") ? "public" : "protected",
|
|
2195
|
-
...metadata
|
|
2196
|
-
});
|
|
2197
|
-
nextClass = fullName;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
} else if (node.type === "method_declaration" || node.type === "property_declaration") {
|
|
2201
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2202
|
-
if (nameNode) {
|
|
2203
|
-
const modifiers = this.getModifiers(node);
|
|
2204
|
-
const isPublic = modifiers.includes("public") || modifiers.includes("protected");
|
|
2205
|
-
if (isPublic) {
|
|
2206
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2207
|
-
exports.push({
|
|
2208
|
-
name: nameNode.text,
|
|
2209
|
-
type: node.type === "method_declaration" ? "function" : "property",
|
|
2210
|
-
parentClass: currentClass,
|
|
2211
|
-
loc: {
|
|
2212
|
-
start: {
|
|
2213
|
-
line: node.startPosition.row + 1,
|
|
2214
|
-
column: node.startPosition.column
|
|
2215
|
-
},
|
|
2216
|
-
end: {
|
|
2217
|
-
line: node.endPosition.row + 1,
|
|
2218
|
-
column: node.endPosition.column
|
|
2219
|
-
}
|
|
2220
|
-
},
|
|
2221
|
-
visibility: modifiers.includes("public") ? "public" : "protected",
|
|
2222
|
-
parameters: node.type === "method_declaration" ? this.extractParameters(node) : void 0,
|
|
2223
|
-
...metadata
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2229
|
-
const child = node.child(i);
|
|
2230
|
-
if (child) traverse(child, nextNamespace, nextClass);
|
|
2231
|
-
}
|
|
2232
|
-
};
|
|
2233
|
-
traverse(rootNode);
|
|
2234
|
-
return exports;
|
|
2235
|
-
}
|
|
2236
|
-
getModifiers(node) {
|
|
2237
|
-
const modifiers = [];
|
|
2238
|
-
for (const child of node.children) {
|
|
2239
|
-
if (child.type === "modifier") {
|
|
2240
|
-
modifiers.push(child.text);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
return modifiers;
|
|
2244
|
-
}
|
|
2245
|
-
extractParameters(node) {
|
|
2246
|
-
return extractParameterNames(node);
|
|
2247
|
-
}
|
|
2248
|
-
getNamingConventions() {
|
|
2249
|
-
return {
|
|
2250
|
-
variablePattern: /^[a-z][a-zA-Z0-9]*$/,
|
|
2251
|
-
functionPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2252
|
-
classPattern: /^[A-Z][a-zA-Z0-9]*$/,
|
|
2253
|
-
constantPattern: /^[A-Z][a-zA-Z0-9_]*$/
|
|
2254
|
-
};
|
|
2255
|
-
}
|
|
2256
|
-
canHandle(filePath) {
|
|
2257
|
-
return filePath.toLowerCase().endsWith(".cs");
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
|
|
2261
|
-
// src/parsers/go-parser.ts
|
|
2262
|
-
var GoParser = class extends BaseLanguageParser {
|
|
2263
|
-
constructor() {
|
|
2264
|
-
super(...arguments);
|
|
2265
|
-
this.language = "go" /* Go */;
|
|
2266
|
-
this.extensions = [".go"];
|
|
2267
|
-
}
|
|
2268
|
-
getParserName() {
|
|
2269
|
-
return "go";
|
|
2270
|
-
}
|
|
2271
|
-
/**
|
|
2272
|
-
* Analyze metadata for a Go node (purity, side effects).
|
|
2273
|
-
*
|
|
2274
|
-
* @param node - Tree-sitter node to analyze.
|
|
2275
|
-
* @param code - Source code for context.
|
|
2276
|
-
* @returns Partial ExportInfo containing discovered metadata.
|
|
2277
|
-
*/
|
|
2278
|
-
analyzeMetadata(node, code) {
|
|
2279
|
-
return analyzeGeneralMetadata(node, code, {
|
|
2280
|
-
sideEffectSignatures: ["<-", "fmt.Print", "fmt.Fprintf", "os.Exit"]
|
|
2281
|
-
});
|
|
2282
|
-
}
|
|
2283
|
-
/**
|
|
2284
|
-
* Fallback regex-based parsing when tree-sitter is unavailable.
|
|
2285
|
-
*
|
|
2286
|
-
* @param code - Source code content.
|
|
2287
|
-
* @returns Consolidated ParseResult.
|
|
2288
|
-
*/
|
|
2289
|
-
parseRegex(code) {
|
|
2290
|
-
const lines = code.split("\n");
|
|
2291
|
-
const exports = [];
|
|
2292
|
-
const imports = [];
|
|
2293
|
-
const importRegex = /^import\s+"([^"]+)"/;
|
|
2294
|
-
const funcRegex = /^func\s+([A-Z][a-zA-Z0-9_]*)\s*\(/;
|
|
2295
|
-
const typeRegex = /^type\s+([A-Z][a-zA-Z0-9_]*)\s+(struct|interface)/;
|
|
2296
|
-
lines.forEach((line, idx) => {
|
|
2297
|
-
const importMatch = line.match(importRegex);
|
|
2298
|
-
if (importMatch) {
|
|
2299
|
-
const source = importMatch[1];
|
|
2300
|
-
imports.push({
|
|
2301
|
-
source,
|
|
2302
|
-
specifiers: [source.split("/").pop() || source],
|
|
2303
|
-
loc: {
|
|
2304
|
-
start: { line: idx + 1, column: 0 },
|
|
2305
|
-
end: { line: idx + 1, column: line.length }
|
|
2306
|
-
}
|
|
2307
|
-
});
|
|
2308
|
-
}
|
|
2309
|
-
const funcMatch = line.match(funcRegex);
|
|
2310
|
-
if (funcMatch) {
|
|
2311
|
-
const name = funcMatch[1];
|
|
2312
|
-
const isPublic = /^[A-Z]/.test(name);
|
|
2313
|
-
let docContent;
|
|
2314
|
-
const prevLines = lines.slice(Math.max(0, idx - 3), idx);
|
|
2315
|
-
for (let i = prevLines.length - 1; i >= 0; i--) {
|
|
2316
|
-
const prevLine = prevLines[i].trim();
|
|
2317
|
-
if (prevLine.startsWith("//")) {
|
|
2318
|
-
const content = prevLine.slice(2).trim();
|
|
2319
|
-
docContent = docContent ? content + "\n" + docContent : content;
|
|
2320
|
-
} else if (prevLine.endsWith("*/")) {
|
|
2321
|
-
const blockMatch = prevLine.match(/\/\*([\s\S]*)\*\//);
|
|
2322
|
-
if (blockMatch) docContent = blockMatch[1].trim();
|
|
2323
|
-
break;
|
|
2324
|
-
} else if (!prevLine) {
|
|
2325
|
-
if (docContent) break;
|
|
2326
|
-
} else {
|
|
2327
|
-
break;
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
const isImpure = name.toLowerCase().includes("impure") || line.includes("fmt.Print");
|
|
2331
|
-
exports.push({
|
|
2332
|
-
name,
|
|
2333
|
-
type: "function",
|
|
2334
|
-
visibility: isPublic ? "public" : "private",
|
|
2335
|
-
isPure: !isImpure,
|
|
2336
|
-
hasSideEffects: isImpure,
|
|
2337
|
-
documentation: docContent ? { content: docContent, type: "comment" } : void 0,
|
|
2338
|
-
loc: {
|
|
2339
|
-
start: { line: idx + 1, column: 0 },
|
|
2340
|
-
end: { line: idx + 1, column: line.length }
|
|
2341
|
-
}
|
|
2342
|
-
});
|
|
2343
|
-
}
|
|
2344
|
-
const typeMatch = line.match(typeRegex);
|
|
2345
|
-
if (typeMatch) {
|
|
2346
|
-
exports.push({
|
|
2347
|
-
name: typeMatch[1],
|
|
2348
|
-
type: typeMatch[2] === "struct" ? "class" : "interface",
|
|
2349
|
-
visibility: "public",
|
|
2350
|
-
isPure: true,
|
|
2351
|
-
hasSideEffects: false,
|
|
2352
|
-
loc: {
|
|
2353
|
-
start: { line: idx + 1, column: 0 },
|
|
2354
|
-
end: { line: idx + 1, column: line.length }
|
|
2355
|
-
}
|
|
2356
|
-
});
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
|
-
return {
|
|
2360
|
-
exports,
|
|
2361
|
-
imports,
|
|
2362
|
-
language: "go" /* Go */,
|
|
2363
|
-
warnings: ["Parser falling back to regex-based analysis"]
|
|
2364
|
-
};
|
|
2365
|
-
}
|
|
2366
|
-
/**
|
|
2367
|
-
* Extract import information using AST walk.
|
|
2368
|
-
*
|
|
2369
|
-
* @param rootNode - Root node of the Go AST.
|
|
2370
|
-
* @returns Array of discovered FileImport objects.
|
|
2371
|
-
*/
|
|
2372
|
-
extractImportsAST(rootNode) {
|
|
2373
|
-
const imports = [];
|
|
2374
|
-
const findImports = (node) => {
|
|
2375
|
-
if (node.type === "import_spec") {
|
|
2376
|
-
const pathNode = node.children.find(
|
|
2377
|
-
(c) => c.type === "interpreted_string_literal"
|
|
2378
|
-
);
|
|
2379
|
-
if (pathNode) {
|
|
2380
|
-
const source = pathNode.text.replace(/"/g, "");
|
|
2381
|
-
imports.push({
|
|
2382
|
-
source,
|
|
2383
|
-
specifiers: [source.split("/").pop() || source],
|
|
2384
|
-
loc: {
|
|
2385
|
-
start: {
|
|
2386
|
-
line: node.startPosition.row + 1,
|
|
2387
|
-
column: node.startPosition.column
|
|
2388
|
-
},
|
|
2389
|
-
end: {
|
|
2390
|
-
line: node.endPosition.row + 1,
|
|
2391
|
-
column: node.endPosition.column
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
});
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2398
|
-
const child = node.child(i);
|
|
2399
|
-
if (child) findImports(child);
|
|
2400
|
-
}
|
|
2401
|
-
};
|
|
2402
|
-
findImports(rootNode);
|
|
2403
|
-
return imports;
|
|
2404
|
-
}
|
|
2405
|
-
/**
|
|
2406
|
-
* Extract export information (functions, types, vars) using AST walk.
|
|
2407
|
-
*
|
|
2408
|
-
* @param rootNode - Root node of the Go AST.
|
|
2409
|
-
* @param code - Source code for documentation extraction.
|
|
2410
|
-
* @returns Array of discovered ExportInfo objects.
|
|
2411
|
-
*/
|
|
2412
|
-
extractExportsAST(rootNode, code) {
|
|
2413
|
-
const exports = [];
|
|
2414
|
-
const isExported = (name) => {
|
|
2415
|
-
return /^[A-Z]/.test(name);
|
|
2416
|
-
};
|
|
2417
|
-
const traverse = (node) => {
|
|
2418
|
-
if (node.type === "function_declaration" || node.type === "method_declaration") {
|
|
2419
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "identifier");
|
|
2420
|
-
if (nameNode && isExported(nameNode.text)) {
|
|
2421
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2422
|
-
exports.push({
|
|
2423
|
-
name: nameNode.text,
|
|
2424
|
-
type: "function",
|
|
2425
|
-
loc: {
|
|
2426
|
-
start: {
|
|
2427
|
-
line: node.startPosition.row + 1,
|
|
2428
|
-
column: node.startPosition.column
|
|
2429
|
-
},
|
|
2430
|
-
end: {
|
|
2431
|
-
line: node.endPosition.row + 1,
|
|
2432
|
-
column: node.endPosition.column
|
|
2433
|
-
}
|
|
2434
|
-
},
|
|
2435
|
-
visibility: "public",
|
|
2436
|
-
parameters: this.extractParameters(node),
|
|
2437
|
-
...metadata
|
|
2438
|
-
});
|
|
2439
|
-
}
|
|
2440
|
-
} else if (node.type === "type_spec") {
|
|
2441
|
-
const nameNode = node.childForFieldName("name") || node.children.find((c) => c.type === "type_identifier");
|
|
2442
|
-
if (nameNode && isExported(nameNode.text)) {
|
|
2443
|
-
const metadata = this.analyzeMetadata(node.parent || node, code);
|
|
2444
|
-
const type = node.children.some((c) => c.type === "struct_type") ? "class" : "interface";
|
|
2445
|
-
exports.push({
|
|
2446
|
-
name: nameNode.text,
|
|
2447
|
-
type,
|
|
2448
|
-
loc: {
|
|
2449
|
-
start: {
|
|
2450
|
-
line: node.startPosition.row + 1,
|
|
2451
|
-
column: node.startPosition.column
|
|
2452
|
-
},
|
|
2453
|
-
end: {
|
|
2454
|
-
line: node.endPosition.row + 1,
|
|
2455
|
-
column: node.endPosition.column
|
|
2456
|
-
}
|
|
2457
|
-
},
|
|
2458
|
-
visibility: "public",
|
|
2459
|
-
...metadata
|
|
2460
|
-
});
|
|
2461
|
-
}
|
|
2462
|
-
} else if (node.type === "var_spec" || node.type === "const_spec") {
|
|
2463
|
-
const identifiers = node.children.filter(
|
|
2464
|
-
(c) => c.type === "identifier"
|
|
2465
|
-
);
|
|
2466
|
-
for (const idNode of identifiers) {
|
|
2467
|
-
if (isExported(idNode.text)) {
|
|
2468
|
-
const metadata = this.analyzeMetadata(node, code);
|
|
2469
|
-
exports.push({
|
|
2470
|
-
name: idNode.text,
|
|
2471
|
-
type: "variable",
|
|
2472
|
-
loc: {
|
|
2473
|
-
start: {
|
|
2474
|
-
line: idNode.startPosition.row + 1,
|
|
2475
|
-
column: idNode.startPosition.column
|
|
2476
|
-
},
|
|
2477
|
-
end: {
|
|
2478
|
-
line: idNode.endPosition.row + 1,
|
|
2479
|
-
column: idNode.endPosition.column
|
|
2480
|
-
}
|
|
2481
|
-
},
|
|
2482
|
-
visibility: "public",
|
|
2483
|
-
...metadata
|
|
2484
|
-
});
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2489
|
-
const child = node.child(i);
|
|
2490
|
-
if (child) traverse(child);
|
|
2491
|
-
}
|
|
2492
|
-
};
|
|
2493
|
-
traverse(rootNode);
|
|
2494
|
-
return exports;
|
|
2495
|
-
}
|
|
2496
|
-
extractParameters(node) {
|
|
2497
|
-
return extractParameterNames(node);
|
|
2498
|
-
}
|
|
2499
|
-
getNamingConventions() {
|
|
2500
|
-
return {
|
|
2501
|
-
variablePattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2502
|
-
functionPattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2503
|
-
classPattern: /^[a-zA-Z][a-zA-Z0-9]*$/,
|
|
2504
|
-
constantPattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
canHandle(filePath) {
|
|
2508
|
-
return filePath.toLowerCase().endsWith(".go");
|
|
2509
|
-
}
|
|
2510
|
-
};
|
|
2511
|
-
|
|
2512
|
-
// src/parsers/parser-factory.ts
|
|
2513
|
-
var ParserFactory = class _ParserFactory {
|
|
2514
|
-
/**
|
|
2515
|
-
* Create a new ParserFactory instance
|
|
2516
|
-
*/
|
|
2517
|
-
constructor() {
|
|
2518
|
-
this.parsers = /* @__PURE__ */ new Map();
|
|
2519
|
-
this.extensionMap = new Map(
|
|
2520
|
-
Object.entries(LANGUAGE_EXTENSIONS).map(([ext, lang]) => [ext, lang])
|
|
2521
|
-
);
|
|
2522
|
-
this.registerParser(new TypeScriptParser());
|
|
2523
|
-
this.registerParser(new PythonParser());
|
|
2524
|
-
this.registerParser(new JavaParser());
|
|
2525
|
-
this.registerParser(new CSharpParser());
|
|
2526
|
-
this.registerParser(new GoParser());
|
|
2527
|
-
}
|
|
2528
|
-
/**
|
|
2529
|
-
* Get the global singleton instance
|
|
2530
|
-
*
|
|
2531
|
-
* @returns The singleton ParserFactory instance
|
|
2532
|
-
*/
|
|
2533
|
-
static getInstance() {
|
|
2534
|
-
if (!_ParserFactory.instance) {
|
|
2535
|
-
_ParserFactory.instance = new _ParserFactory();
|
|
2536
|
-
}
|
|
2537
|
-
return _ParserFactory.instance;
|
|
2538
|
-
}
|
|
2539
|
-
/**
|
|
2540
|
-
* Register a language parser
|
|
2541
|
-
*/
|
|
2542
|
-
registerParser(parser) {
|
|
2543
|
-
this.parsers.set(parser.language, parser);
|
|
2544
|
-
parser.extensions.forEach((ext) => {
|
|
2545
|
-
const lang = LANGUAGE_EXTENSIONS[ext] || parser.language;
|
|
2546
|
-
this.extensionMap.set(ext, lang);
|
|
2547
|
-
this.parsers.set(lang, parser);
|
|
2548
|
-
});
|
|
2549
|
-
}
|
|
2550
|
-
/**
|
|
2551
|
-
* Get parser for a specific language
|
|
2552
|
-
*/
|
|
2553
|
-
getParserForLanguage(language) {
|
|
2554
|
-
return this.parsers.get(language) || null;
|
|
2555
|
-
}
|
|
2556
|
-
/**
|
|
2557
|
-
* Get parser for a file based on its extension
|
|
2558
|
-
*/
|
|
2559
|
-
getParserForFile(filePath) {
|
|
2560
|
-
const ext = this.getFileExtension(filePath);
|
|
2561
|
-
const language = this.extensionMap.get(ext);
|
|
2562
|
-
if (!language) {
|
|
2563
|
-
return null;
|
|
2564
|
-
}
|
|
2565
|
-
return this.parsers.get(language) || null;
|
|
2566
|
-
}
|
|
2567
|
-
/**
|
|
2568
|
-
* Check if a file is supported
|
|
2569
|
-
*/
|
|
2570
|
-
isSupported(filePath) {
|
|
2571
|
-
return this.getParserForFile(filePath) !== null;
|
|
2572
|
-
}
|
|
2573
|
-
/**
|
|
2574
|
-
* Get all registered languages
|
|
2575
|
-
*/
|
|
2576
|
-
getSupportedLanguages() {
|
|
2577
|
-
return Array.from(this.parsers.keys());
|
|
2578
|
-
}
|
|
2579
|
-
/**
|
|
2580
|
-
* Get all supported file extensions
|
|
2581
|
-
*/
|
|
2582
|
-
getSupportedExtensions() {
|
|
2583
|
-
return Array.from(this.extensionMap.keys());
|
|
2584
|
-
}
|
|
2585
|
-
/**
|
|
2586
|
-
* Get language for a file
|
|
2587
|
-
*/
|
|
2588
|
-
getLanguageForFile(filePath) {
|
|
2589
|
-
const ext = this.getFileExtension(filePath);
|
|
2590
|
-
return this.extensionMap.get(ext) || null;
|
|
2591
|
-
}
|
|
2592
|
-
/**
|
|
2593
|
-
* Extract file extension (with dot)
|
|
976
|
+
* Extract file extension (with dot)
|
|
2594
977
|
*/
|
|
2595
978
|
getFileExtension(filePath) {
|
|
2596
979
|
const match = filePath.match(/\.[^.]+$/);
|
|
@@ -2612,7 +995,7 @@ var ParserFactory = class _ParserFactory {
|
|
|
2612
995
|
await Promise.all(promises);
|
|
2613
996
|
}
|
|
2614
997
|
};
|
|
2615
|
-
function getParser(filePath) {
|
|
998
|
+
async function getParser(filePath) {
|
|
2616
999
|
return ParserFactory.getInstance().getParserForFile(filePath);
|
|
2617
1000
|
}
|
|
2618
1001
|
async function initializeParsers() {
|
|
@@ -2799,10 +1182,11 @@ function extractTypeReferences(node) {
|
|
|
2799
1182
|
}
|
|
2800
1183
|
|
|
2801
1184
|
// src/utils/dependency-analyzer.ts
|
|
2802
|
-
function parseFileExports(code, filePath) {
|
|
2803
|
-
const parser = getParser(filePath);
|
|
1185
|
+
async function parseFileExports(code, filePath) {
|
|
1186
|
+
const parser = await getParser(filePath);
|
|
2804
1187
|
if (parser && parser.language !== "typescript" /* TypeScript */ && parser.language !== "javascript" /* JavaScript */) {
|
|
2805
1188
|
try {
|
|
1189
|
+
await parser.initialize();
|
|
2806
1190
|
const result = parser.parse(code, filePath);
|
|
2807
1191
|
return {
|
|
2808
1192
|
exports: result.exports.map((e) => ({
|
|
@@ -2827,7 +1211,7 @@ function parseFileExports(code, filePath) {
|
|
|
2827
1211
|
}
|
|
2828
1212
|
}
|
|
2829
1213
|
try {
|
|
2830
|
-
const ast =
|
|
1214
|
+
const ast = parse(code, {
|
|
2831
1215
|
loc: true,
|
|
2832
1216
|
range: true,
|
|
2833
1217
|
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
@@ -2847,8 +1231,8 @@ function estimateTokens(text) {
|
|
|
2847
1231
|
}
|
|
2848
1232
|
|
|
2849
1233
|
// src/utils/config.ts
|
|
2850
|
-
import { readFileSync, existsSync as
|
|
2851
|
-
import { join as
|
|
1234
|
+
import { readFileSync, existsSync as existsSync3 } from "fs";
|
|
1235
|
+
import { join as join3, resolve, dirname as dirname3 } from "path";
|
|
2852
1236
|
import { pathToFileURL } from "url";
|
|
2853
1237
|
var CONFIG_FILES = [
|
|
2854
1238
|
"aiready.json",
|
|
@@ -2863,7 +1247,7 @@ async function loadConfig(rootDir) {
|
|
|
2863
1247
|
while (true) {
|
|
2864
1248
|
const foundConfigs = [];
|
|
2865
1249
|
for (const configFile of CONFIG_FILES) {
|
|
2866
|
-
if (
|
|
1250
|
+
if (existsSync3(join3(currentDir, configFile))) {
|
|
2867
1251
|
foundConfigs.push(configFile);
|
|
2868
1252
|
}
|
|
2869
1253
|
}
|
|
@@ -2877,7 +1261,7 @@ async function loadConfig(rootDir) {
|
|
|
2877
1261
|
} else {
|
|
2878
1262
|
}
|
|
2879
1263
|
const configFile = foundConfigs[0];
|
|
2880
|
-
const configPath =
|
|
1264
|
+
const configPath = join3(currentDir, configFile);
|
|
2881
1265
|
try {
|
|
2882
1266
|
let config;
|
|
2883
1267
|
if (configFile.endsWith(".js")) {
|
|
@@ -2919,7 +1303,7 @@ async function loadConfig(rootDir) {
|
|
|
2919
1303
|
throw configError;
|
|
2920
1304
|
}
|
|
2921
1305
|
}
|
|
2922
|
-
const parent =
|
|
1306
|
+
const parent = dirname3(currentDir);
|
|
2923
1307
|
if (parent === currentDir) {
|
|
2924
1308
|
break;
|
|
2925
1309
|
}
|
|
@@ -2951,7 +1335,7 @@ function mergeConfigWithDefaults(userConfig, defaults) {
|
|
|
2951
1335
|
return mergedConfig;
|
|
2952
1336
|
}
|
|
2953
1337
|
|
|
2954
|
-
// src/utils/
|
|
1338
|
+
// src/utils/report-formatters.ts
|
|
2955
1339
|
function generateReportHead(title) {
|
|
2956
1340
|
return `<!DOCTYPE html>
|
|
2957
1341
|
<html lang="en">
|
|
@@ -2967,6 +1351,9 @@ function generateReportHead(title) {
|
|
|
2967
1351
|
.stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
|
|
2968
1352
|
.stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
|
|
2969
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; }
|
|
2970
1357
|
table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
|
|
2971
1358
|
th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
|
|
2972
1359
|
th { background-color: #f8fafc; font-weight: 600; color: #475569; }
|
|
@@ -2974,11 +1361,23 @@ function generateReportHead(title) {
|
|
|
2974
1361
|
.critical { color: #dc2626; font-weight: bold; }
|
|
2975
1362
|
.major { color: #ea580c; font-weight: bold; }
|
|
2976
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; }
|
|
2977
1367
|
code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
|
|
2978
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; }
|
|
2979
1371
|
</style>
|
|
2980
1372
|
</head>`;
|
|
2981
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
|
+
}
|
|
2982
1381
|
function generateStatCards(cards) {
|
|
2983
1382
|
const cardsHtml = cards.map(
|
|
2984
1383
|
(card) => `
|
|
@@ -2989,6 +1388,12 @@ function generateStatCards(cards) {
|
|
|
2989
1388
|
).join("");
|
|
2990
1389
|
return `<div class="stats">${cardsHtml}</div>`;
|
|
2991
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
|
+
}
|
|
2992
1397
|
function generateTable(config) {
|
|
2993
1398
|
const headersHtml = config.headers.map((h) => `<th>${h}</th>`).join("");
|
|
2994
1399
|
const rowsHtml = config.rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
|
|
@@ -2997,6 +1402,18 @@ function generateTable(config) {
|
|
|
2997
1402
|
<tbody>${rowsHtml}</tbody>
|
|
2998
1403
|
</table>`;
|
|
2999
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
|
+
}
|
|
3000
1417
|
function generateReportFooter(options) {
|
|
3001
1418
|
const versionText = options.version ? ` v${options.version}` : "";
|
|
3002
1419
|
const links = [];
|
|
@@ -3006,26 +1423,360 @@ function generateReportFooter(options) {
|
|
|
3006
1423
|
if (options.bugUrl) {
|
|
3007
1424
|
links.push(`<a href="${options.bugUrl}">Report it here</a>`);
|
|
3008
1425
|
}
|
|
3009
|
-
const linksHtml = links.length ? links.map((l) => `<p>Like AIReady? ${l}</p>`).join("\n ") : "";
|
|
3010
|
-
return `<div class="footer">
|
|
3011
|
-
<p>Generated by <strong>@aiready/${options.packageName}</strong>${versionText}</p>
|
|
3012
|
-
${linksHtml}
|
|
3013
|
-
</div>`;
|
|
3014
|
-
}
|
|
3015
|
-
function wrapInCard(content, title) {
|
|
3016
|
-
const titleHtml = title ? `<h2>${title}</h2>` : "";
|
|
3017
|
-
return `<div class="card">
|
|
3018
|
-
${titleHtml}
|
|
3019
|
-
${content}
|
|
3020
|
-
</div>`;
|
|
3021
|
-
}
|
|
3022
|
-
function generateCompleteReport(options, bodyContent) {
|
|
3023
|
-
return `${generateReportHead(options.title)}
|
|
3024
|
-
<body>
|
|
3025
|
-
${bodyContent}
|
|
3026
|
-
${generateReportFooter(options)}
|
|
3027
|
-
</body>
|
|
3028
|
-
</html>`;
|
|
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;
|
|
3029
1780
|
}
|
|
3030
1781
|
|
|
3031
1782
|
// src/business/pricing-models.ts
|
|
@@ -3275,6 +2026,99 @@ function predictAcceptanceRate(toolOutputs) {
|
|
|
3275
2026
|
return { rate: Math.round(rate * 100) / 100, confidence, factors };
|
|
3276
2027
|
}
|
|
3277
2028
|
|
|
2029
|
+
// src/business-metrics.ts
|
|
2030
|
+
function calculateBusinessROI(params) {
|
|
2031
|
+
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
2032
|
+
const devCount = params.developerCount || 5;
|
|
2033
|
+
const budget = calculateTokenBudget({
|
|
2034
|
+
totalContextTokens: params.tokenWaste * 2.5,
|
|
2035
|
+
wastedTokens: {
|
|
2036
|
+
duplication: params.tokenWaste * 0.7,
|
|
2037
|
+
fragmentation: params.tokenWaste * 0.3,
|
|
2038
|
+
chattiness: 0
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
const cost = estimateCostFromBudget(budget, model, {
|
|
2042
|
+
developerCount: devCount
|
|
2043
|
+
});
|
|
2044
|
+
const productivity = calculateProductivityImpact(params.issues);
|
|
2045
|
+
const monthlySavings = cost.total;
|
|
2046
|
+
const productivityGainHours = productivity.totalHours;
|
|
2047
|
+
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
2048
|
+
return {
|
|
2049
|
+
monthlySavings: Math.round(monthlySavings),
|
|
2050
|
+
productivityGainHours: Math.round(productivityGainHours),
|
|
2051
|
+
annualValue: Math.round(annualValue)
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function formatCost(cost) {
|
|
2055
|
+
if (cost < 1) {
|
|
2056
|
+
return `$${cost.toFixed(2)}`;
|
|
2057
|
+
} else if (cost < 1e3) {
|
|
2058
|
+
return `$${cost.toFixed(0)}`;
|
|
2059
|
+
} else {
|
|
2060
|
+
return `$${(cost / 1e3).toFixed(1)}k`;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
function formatHours(hours) {
|
|
2064
|
+
if (hours < 1) {
|
|
2065
|
+
return `${Math.round(hours * 60)}min`;
|
|
2066
|
+
} else if (hours < 8) {
|
|
2067
|
+
return `${hours.toFixed(1)}h`;
|
|
2068
|
+
} else if (hours < 40) {
|
|
2069
|
+
return `${Math.round(hours)}h`;
|
|
2070
|
+
} else {
|
|
2071
|
+
return `${(hours / 40).toFixed(1)} weeks`;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
function formatAcceptanceRate(rate) {
|
|
2075
|
+
return `${Math.round(rate * 100)}%`;
|
|
2076
|
+
}
|
|
2077
|
+
function generateValueChain(params) {
|
|
2078
|
+
const { issueType, count, severity } = params;
|
|
2079
|
+
const impacts = {
|
|
2080
|
+
"duplicate-pattern": {
|
|
2081
|
+
ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
|
|
2082
|
+
dev: "Developers must manually resolve conflicts between suggested variants.",
|
|
2083
|
+
risk: "high"
|
|
2084
|
+
},
|
|
2085
|
+
"context-fragmentation": {
|
|
2086
|
+
ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
|
|
2087
|
+
dev: "Slower AI responses and increased need for manual context pinning.",
|
|
2088
|
+
risk: "critical"
|
|
2089
|
+
},
|
|
2090
|
+
"naming-inconsistency": {
|
|
2091
|
+
ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
|
|
2092
|
+
dev: "Increased cognitive load for new devs during onboarding.",
|
|
2093
|
+
risk: "moderate"
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
const impact = impacts[issueType] || {
|
|
2097
|
+
ai: "Reduced suggestion quality.",
|
|
2098
|
+
dev: "Slowed development velocity.",
|
|
2099
|
+
risk: "moderate"
|
|
2100
|
+
};
|
|
2101
|
+
const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
|
|
2102
|
+
return {
|
|
2103
|
+
issueType,
|
|
2104
|
+
technicalMetric: "Issue Count",
|
|
2105
|
+
technicalValue: count,
|
|
2106
|
+
aiImpact: {
|
|
2107
|
+
description: impact.ai,
|
|
2108
|
+
scoreImpact: severity === "critical" ? -15 : -5
|
|
2109
|
+
},
|
|
2110
|
+
developerImpact: {
|
|
2111
|
+
description: impact.dev,
|
|
2112
|
+
productivityLoss
|
|
2113
|
+
},
|
|
2114
|
+
businessOutcome: {
|
|
2115
|
+
directCost: count * 12,
|
|
2116
|
+
opportunityCost: productivityLoss * 15e3,
|
|
2117
|
+
riskLevel: impact.risk
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
|
|
3278
2122
|
// src/business/risk-metrics.ts
|
|
3279
2123
|
function calculateKnowledgeConcentration(params) {
|
|
3280
2124
|
const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
|
|
@@ -3369,99 +2213,6 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
|
|
|
3369
2213
|
};
|
|
3370
2214
|
}
|
|
3371
2215
|
|
|
3372
|
-
// src/business-metrics.ts
|
|
3373
|
-
function calculateBusinessROI(params) {
|
|
3374
|
-
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
3375
|
-
const devCount = params.developerCount || 5;
|
|
3376
|
-
const budget = calculateTokenBudget({
|
|
3377
|
-
totalContextTokens: params.tokenWaste * 2.5,
|
|
3378
|
-
wastedTokens: {
|
|
3379
|
-
duplication: params.tokenWaste * 0.7,
|
|
3380
|
-
fragmentation: params.tokenWaste * 0.3,
|
|
3381
|
-
chattiness: 0
|
|
3382
|
-
}
|
|
3383
|
-
});
|
|
3384
|
-
const cost = estimateCostFromBudget(budget, model, {
|
|
3385
|
-
developerCount: devCount
|
|
3386
|
-
});
|
|
3387
|
-
const productivity = calculateProductivityImpact(params.issues);
|
|
3388
|
-
const monthlySavings = cost.total;
|
|
3389
|
-
const productivityGainHours = productivity.totalHours;
|
|
3390
|
-
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
3391
|
-
return {
|
|
3392
|
-
monthlySavings: Math.round(monthlySavings),
|
|
3393
|
-
productivityGainHours: Math.round(productivityGainHours),
|
|
3394
|
-
annualValue: Math.round(annualValue)
|
|
3395
|
-
};
|
|
3396
|
-
}
|
|
3397
|
-
function formatCost(cost) {
|
|
3398
|
-
if (cost < 1) {
|
|
3399
|
-
return `$${cost.toFixed(2)}`;
|
|
3400
|
-
} else if (cost < 1e3) {
|
|
3401
|
-
return `$${cost.toFixed(0)}`;
|
|
3402
|
-
} else {
|
|
3403
|
-
return `$${(cost / 1e3).toFixed(1)}k`;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
function formatHours(hours) {
|
|
3407
|
-
if (hours < 1) {
|
|
3408
|
-
return `${Math.round(hours * 60)}min`;
|
|
3409
|
-
} else if (hours < 8) {
|
|
3410
|
-
return `${hours.toFixed(1)}h`;
|
|
3411
|
-
} else if (hours < 40) {
|
|
3412
|
-
return `${Math.round(hours)}h`;
|
|
3413
|
-
} else {
|
|
3414
|
-
return `${(hours / 40).toFixed(1)} weeks`;
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
function formatAcceptanceRate(rate) {
|
|
3418
|
-
return `${Math.round(rate * 100)}%`;
|
|
3419
|
-
}
|
|
3420
|
-
function generateValueChain(params) {
|
|
3421
|
-
const { issueType, count, severity } = params;
|
|
3422
|
-
const impacts = {
|
|
3423
|
-
"duplicate-pattern": {
|
|
3424
|
-
ai: "Ambiguous context leads to code generation variants. AI picks wrong implementation 40% of the time.",
|
|
3425
|
-
dev: "Developers must manually resolve conflicts between suggested variants.",
|
|
3426
|
-
risk: "high"
|
|
3427
|
-
},
|
|
3428
|
-
"context-fragmentation": {
|
|
3429
|
-
ai: "Context window overflow causes model to forget mid-file dependencies resulting in hallucinations.",
|
|
3430
|
-
dev: "Slower AI responses and increased need for manual context pinning.",
|
|
3431
|
-
risk: "critical"
|
|
3432
|
-
},
|
|
3433
|
-
"naming-inconsistency": {
|
|
3434
|
-
ai: "Degraded intent inference. AI misidentifies domain concepts across file boundaries.",
|
|
3435
|
-
dev: "Increased cognitive load for new devs during onboarding.",
|
|
3436
|
-
risk: "moderate"
|
|
3437
|
-
}
|
|
3438
|
-
};
|
|
3439
|
-
const impact = impacts[issueType] || {
|
|
3440
|
-
ai: "Reduced suggestion quality.",
|
|
3441
|
-
dev: "Slowed development velocity.",
|
|
3442
|
-
risk: "moderate"
|
|
3443
|
-
};
|
|
3444
|
-
const productivityLoss = severity === "critical" ? 0.25 : severity === "major" ? 0.1 : 0.05;
|
|
3445
|
-
return {
|
|
3446
|
-
issueType,
|
|
3447
|
-
technicalMetric: "Issue Count",
|
|
3448
|
-
technicalValue: count,
|
|
3449
|
-
aiImpact: {
|
|
3450
|
-
description: impact.ai,
|
|
3451
|
-
scoreImpact: severity === "critical" ? -15 : -5
|
|
3452
|
-
},
|
|
3453
|
-
developerImpact: {
|
|
3454
|
-
description: impact.dev,
|
|
3455
|
-
productivityLoss
|
|
3456
|
-
},
|
|
3457
|
-
businessOutcome: {
|
|
3458
|
-
directCost: count * 12,
|
|
3459
|
-
opportunityCost: productivityLoss * 15e3,
|
|
3460
|
-
riskLevel: impact.risk
|
|
3461
|
-
}
|
|
3462
|
-
};
|
|
3463
|
-
}
|
|
3464
|
-
|
|
3465
2216
|
// src/metrics/remediation-utils.ts
|
|
3466
2217
|
function collectFutureProofRecommendations(params) {
|
|
3467
2218
|
const recommendations = [];
|
|
@@ -3499,33 +2250,163 @@ function collectFutureProofRecommendations(params) {
|
|
|
3499
2250
|
}
|
|
3500
2251
|
}
|
|
3501
2252
|
if (params.dependencyHealth) {
|
|
3502
|
-
for (const rec of params.dependencyHealth.recommendations) {
|
|
3503
|
-
recommendations.push({
|
|
3504
|
-
action: rec,
|
|
3505
|
-
estimatedImpact: 7,
|
|
3506
|
-
priority: "medium"
|
|
3507
|
-
});
|
|
3508
|
-
}
|
|
2253
|
+
for (const rec of params.dependencyHealth.recommendations) {
|
|
2254
|
+
recommendations.push({
|
|
2255
|
+
action: rec,
|
|
2256
|
+
estimatedImpact: 7,
|
|
2257
|
+
priority: "medium"
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
return recommendations;
|
|
2262
|
+
}
|
|
2263
|
+
function collectBaseFutureProofRecommendations(params) {
|
|
2264
|
+
const recommendations = [];
|
|
2265
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2266
|
+
recommendations.push({
|
|
2267
|
+
action: rec,
|
|
2268
|
+
estimatedImpact: 5,
|
|
2269
|
+
priority: "medium"
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
2273
|
+
recommendations.push({
|
|
2274
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
2275
|
+
estimatedImpact: 8,
|
|
2276
|
+
priority: "high"
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
return recommendations;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/future-proof-metrics.ts
|
|
2283
|
+
function calculateFutureProofScore(params) {
|
|
2284
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2285
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2286
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2287
|
+
const overall = Math.round(
|
|
2288
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2289
|
+
);
|
|
2290
|
+
const factors = [
|
|
2291
|
+
{
|
|
2292
|
+
name: "Cognitive Load",
|
|
2293
|
+
impact: Math.round(loadScore - 50),
|
|
2294
|
+
description: params.cognitiveLoad.rating
|
|
2295
|
+
},
|
|
2296
|
+
{
|
|
2297
|
+
name: "Pattern Entropy",
|
|
2298
|
+
impact: Math.round(entropyScore - 50),
|
|
2299
|
+
description: params.patternEntropy.rating
|
|
2300
|
+
},
|
|
2301
|
+
{
|
|
2302
|
+
name: "Concept Cohesion",
|
|
2303
|
+
impact: Math.round(cohesionScore - 50),
|
|
2304
|
+
description: params.conceptCohesion.rating
|
|
2305
|
+
}
|
|
2306
|
+
];
|
|
2307
|
+
const recommendations = collectBaseFutureProofRecommendations({
|
|
2308
|
+
patternEntropy: params.patternEntropy,
|
|
2309
|
+
conceptCohesion: params.conceptCohesion
|
|
2310
|
+
});
|
|
2311
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2312
|
+
return {
|
|
2313
|
+
toolName: "future-proof",
|
|
2314
|
+
score: overall,
|
|
2315
|
+
rawMetrics: {
|
|
2316
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2317
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2318
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2319
|
+
semanticDistanceAvg
|
|
2320
|
+
},
|
|
2321
|
+
factors,
|
|
2322
|
+
recommendations
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function calculateExtendedFutureProofScore(params) {
|
|
2326
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2327
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2328
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2329
|
+
const aiSignalClarityScore = 100 - params.aiSignalClarity.score;
|
|
2330
|
+
const groundingScore = params.agentGrounding.score;
|
|
2331
|
+
const testabilityScore = params.testability.score;
|
|
2332
|
+
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
2333
|
+
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
2334
|
+
let totalWeight = 0.8;
|
|
2335
|
+
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
2336
|
+
if (params.docDrift) {
|
|
2337
|
+
overall += docDriftScore * 0.1;
|
|
2338
|
+
totalWeight += 0.1;
|
|
2339
|
+
}
|
|
2340
|
+
if (params.dependencyHealth) {
|
|
2341
|
+
overall += depsHealthScore * 0.1;
|
|
2342
|
+
totalWeight += 0.1;
|
|
3509
2343
|
}
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
2344
|
+
overall = Math.round(overall / totalWeight);
|
|
2345
|
+
const factors = [
|
|
2346
|
+
{
|
|
2347
|
+
name: "Cognitive Load",
|
|
2348
|
+
impact: Math.round(loadScore - 50),
|
|
2349
|
+
description: params.cognitiveLoad.rating
|
|
2350
|
+
},
|
|
2351
|
+
{
|
|
2352
|
+
name: "Pattern Entropy",
|
|
2353
|
+
impact: Math.round(entropyScore - 50),
|
|
2354
|
+
description: params.patternEntropy.rating
|
|
2355
|
+
},
|
|
2356
|
+
{
|
|
2357
|
+
name: "Concept Cohesion",
|
|
2358
|
+
impact: Math.round(cohesionScore - 50),
|
|
2359
|
+
description: params.conceptCohesion.rating
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
name: "AI Signal Clarity",
|
|
2363
|
+
impact: Math.round(aiSignalClarityScore - 50),
|
|
2364
|
+
description: `${params.aiSignalClarity.rating} risk`
|
|
2365
|
+
},
|
|
2366
|
+
{
|
|
2367
|
+
name: "Agent Grounding",
|
|
2368
|
+
impact: Math.round(groundingScore - 50),
|
|
2369
|
+
description: params.agentGrounding.rating
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
name: "Testability",
|
|
2373
|
+
impact: Math.round(testabilityScore - 50),
|
|
2374
|
+
description: params.testability.rating
|
|
2375
|
+
}
|
|
2376
|
+
];
|
|
2377
|
+
if (params.docDrift) {
|
|
2378
|
+
factors.push({
|
|
2379
|
+
name: "Documentation Drift",
|
|
2380
|
+
impact: Math.round(docDriftScore - 50),
|
|
2381
|
+
description: params.docDrift.rating
|
|
3519
2382
|
});
|
|
3520
2383
|
}
|
|
3521
|
-
if (params.
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
2384
|
+
if (params.dependencyHealth) {
|
|
2385
|
+
factors.push({
|
|
2386
|
+
name: "Dependency Health",
|
|
2387
|
+
impact: Math.round(depsHealthScore - 50),
|
|
2388
|
+
description: params.dependencyHealth.rating
|
|
3526
2389
|
});
|
|
3527
2390
|
}
|
|
3528
|
-
|
|
2391
|
+
const recommendations = collectFutureProofRecommendations(params);
|
|
2392
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2393
|
+
return {
|
|
2394
|
+
toolName: "future-proof",
|
|
2395
|
+
score: overall,
|
|
2396
|
+
rawMetrics: {
|
|
2397
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2398
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2399
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2400
|
+
aiSignalClarityScore: params.aiSignalClarity.score,
|
|
2401
|
+
agentGroundingScore: params.agentGrounding.score,
|
|
2402
|
+
testabilityScore: params.testability.score,
|
|
2403
|
+
docDriftScore: params.docDrift?.score,
|
|
2404
|
+
dependencyHealthScore: params.dependencyHealth?.score,
|
|
2405
|
+
semanticDistanceAvg
|
|
2406
|
+
},
|
|
2407
|
+
factors,
|
|
2408
|
+
recommendations
|
|
2409
|
+
};
|
|
3529
2410
|
}
|
|
3530
2411
|
|
|
3531
2412
|
// src/metrics/cognitive-load.ts
|
|
@@ -4286,145 +3167,15 @@ function calculateChangeAmplification(params) {
|
|
|
4286
3167
|
};
|
|
4287
3168
|
}
|
|
4288
3169
|
|
|
4289
|
-
// src/future-proof-metrics.ts
|
|
4290
|
-
function calculateFutureProofScore(params) {
|
|
4291
|
-
const loadScore = 100 - params.cognitiveLoad.score;
|
|
4292
|
-
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
4293
|
-
const cohesionScore = params.conceptCohesion.score * 100;
|
|
4294
|
-
const overall = Math.round(
|
|
4295
|
-
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
4296
|
-
);
|
|
4297
|
-
const factors = [
|
|
4298
|
-
{
|
|
4299
|
-
name: "Cognitive Load",
|
|
4300
|
-
impact: Math.round(loadScore - 50),
|
|
4301
|
-
description: params.cognitiveLoad.rating
|
|
4302
|
-
},
|
|
4303
|
-
{
|
|
4304
|
-
name: "Pattern Entropy",
|
|
4305
|
-
impact: Math.round(entropyScore - 50),
|
|
4306
|
-
description: params.patternEntropy.rating
|
|
4307
|
-
},
|
|
4308
|
-
{
|
|
4309
|
-
name: "Concept Cohesion",
|
|
4310
|
-
impact: Math.round(cohesionScore - 50),
|
|
4311
|
-
description: params.conceptCohesion.rating
|
|
4312
|
-
}
|
|
4313
|
-
];
|
|
4314
|
-
const recommendations = collectBaseFutureProofRecommendations({
|
|
4315
|
-
patternEntropy: params.patternEntropy,
|
|
4316
|
-
conceptCohesion: params.conceptCohesion
|
|
4317
|
-
});
|
|
4318
|
-
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
4319
|
-
return {
|
|
4320
|
-
toolName: "future-proof",
|
|
4321
|
-
score: overall,
|
|
4322
|
-
rawMetrics: {
|
|
4323
|
-
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
4324
|
-
entropyScore: params.patternEntropy.entropy,
|
|
4325
|
-
cohesionScore: params.conceptCohesion.score,
|
|
4326
|
-
semanticDistanceAvg
|
|
4327
|
-
},
|
|
4328
|
-
factors,
|
|
4329
|
-
recommendations
|
|
4330
|
-
};
|
|
4331
|
-
}
|
|
4332
|
-
function calculateExtendedFutureProofScore(params) {
|
|
4333
|
-
const loadScore = 100 - params.cognitiveLoad.score;
|
|
4334
|
-
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
4335
|
-
const cohesionScore = params.conceptCohesion.score * 100;
|
|
4336
|
-
const aiSignalClarityScore = 100 - params.aiSignalClarity.score;
|
|
4337
|
-
const groundingScore = params.agentGrounding.score;
|
|
4338
|
-
const testabilityScore = params.testability.score;
|
|
4339
|
-
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
4340
|
-
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
4341
|
-
let totalWeight = 0.8;
|
|
4342
|
-
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
4343
|
-
if (params.docDrift) {
|
|
4344
|
-
overall += docDriftScore * 0.1;
|
|
4345
|
-
totalWeight += 0.1;
|
|
4346
|
-
}
|
|
4347
|
-
if (params.dependencyHealth) {
|
|
4348
|
-
overall += depsHealthScore * 0.1;
|
|
4349
|
-
totalWeight += 0.1;
|
|
4350
|
-
}
|
|
4351
|
-
overall = Math.round(overall / totalWeight);
|
|
4352
|
-
const factors = [
|
|
4353
|
-
{
|
|
4354
|
-
name: "Cognitive Load",
|
|
4355
|
-
impact: Math.round(loadScore - 50),
|
|
4356
|
-
description: params.cognitiveLoad.rating
|
|
4357
|
-
},
|
|
4358
|
-
{
|
|
4359
|
-
name: "Pattern Entropy",
|
|
4360
|
-
impact: Math.round(entropyScore - 50),
|
|
4361
|
-
description: params.patternEntropy.rating
|
|
4362
|
-
},
|
|
4363
|
-
{
|
|
4364
|
-
name: "Concept Cohesion",
|
|
4365
|
-
impact: Math.round(cohesionScore - 50),
|
|
4366
|
-
description: params.conceptCohesion.rating
|
|
4367
|
-
},
|
|
4368
|
-
{
|
|
4369
|
-
name: "AI Signal Clarity",
|
|
4370
|
-
impact: Math.round(aiSignalClarityScore - 50),
|
|
4371
|
-
description: `${params.aiSignalClarity.rating} risk`
|
|
4372
|
-
},
|
|
4373
|
-
{
|
|
4374
|
-
name: "Agent Grounding",
|
|
4375
|
-
impact: Math.round(groundingScore - 50),
|
|
4376
|
-
description: params.agentGrounding.rating
|
|
4377
|
-
},
|
|
4378
|
-
{
|
|
4379
|
-
name: "Testability",
|
|
4380
|
-
impact: Math.round(testabilityScore - 50),
|
|
4381
|
-
description: params.testability.rating
|
|
4382
|
-
}
|
|
4383
|
-
];
|
|
4384
|
-
if (params.docDrift) {
|
|
4385
|
-
factors.push({
|
|
4386
|
-
name: "Documentation Drift",
|
|
4387
|
-
impact: Math.round(docDriftScore - 50),
|
|
4388
|
-
description: params.docDrift.rating
|
|
4389
|
-
});
|
|
4390
|
-
}
|
|
4391
|
-
if (params.dependencyHealth) {
|
|
4392
|
-
factors.push({
|
|
4393
|
-
name: "Dependency Health",
|
|
4394
|
-
impact: Math.round(depsHealthScore - 50),
|
|
4395
|
-
description: params.dependencyHealth.rating
|
|
4396
|
-
});
|
|
4397
|
-
}
|
|
4398
|
-
const recommendations = collectFutureProofRecommendations(params);
|
|
4399
|
-
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
4400
|
-
return {
|
|
4401
|
-
toolName: "future-proof",
|
|
4402
|
-
score: overall,
|
|
4403
|
-
rawMetrics: {
|
|
4404
|
-
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
4405
|
-
entropyScore: params.patternEntropy.entropy,
|
|
4406
|
-
cohesionScore: params.conceptCohesion.score,
|
|
4407
|
-
aiSignalClarityScore: params.aiSignalClarity.score,
|
|
4408
|
-
agentGroundingScore: params.agentGrounding.score,
|
|
4409
|
-
testabilityScore: params.testability.score,
|
|
4410
|
-
docDriftScore: params.docDrift?.score,
|
|
4411
|
-
dependencyHealthScore: params.dependencyHealth?.score,
|
|
4412
|
-
semanticDistanceAvg
|
|
4413
|
-
},
|
|
4414
|
-
factors,
|
|
4415
|
-
recommendations
|
|
4416
|
-
};
|
|
4417
|
-
}
|
|
4418
|
-
|
|
4419
3170
|
// src/utils/history.ts
|
|
4420
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
4421
|
-
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";
|
|
4422
3173
|
function getHistoryPath(rootDir) {
|
|
4423
|
-
return
|
|
3174
|
+
return join4(rootDir, ".aiready", "history.json");
|
|
4424
3175
|
}
|
|
4425
3176
|
function loadScoreHistory(rootDir) {
|
|
4426
3177
|
const historyPath = getHistoryPath(rootDir);
|
|
4427
|
-
if (!
|
|
3178
|
+
if (!existsSync4(historyPath)) {
|
|
4428
3179
|
return [];
|
|
4429
3180
|
}
|
|
4430
3181
|
try {
|
|
@@ -4437,8 +3188,8 @@ function loadScoreHistory(rootDir) {
|
|
|
4437
3188
|
}
|
|
4438
3189
|
function saveScoreEntry(rootDir, entry) {
|
|
4439
3190
|
const historyPath = getHistoryPath(rootDir);
|
|
4440
|
-
const historyDir =
|
|
4441
|
-
if (!
|
|
3191
|
+
const historyDir = dirname4(historyPath);
|
|
3192
|
+
if (!existsSync4(historyDir)) {
|
|
4442
3193
|
mkdirSync2(historyDir, { recursive: true });
|
|
4443
3194
|
}
|
|
4444
3195
|
const history = loadScoreHistory(rootDir);
|
|
@@ -4485,7 +3236,7 @@ function exportHistory(rootDir, format = "json") {
|
|
|
4485
3236
|
}
|
|
4486
3237
|
function clearHistory(rootDir) {
|
|
4487
3238
|
const historyPath = getHistoryPath(rootDir);
|
|
4488
|
-
if (
|
|
3239
|
+
if (existsSync4(historyPath)) {
|
|
4489
3240
|
writeFileSync2(historyPath, JSON.stringify([]));
|
|
4490
3241
|
}
|
|
4491
3242
|
}
|
|
@@ -4652,8 +3403,10 @@ export {
|
|
|
4652
3403
|
TypeScriptParser,
|
|
4653
3404
|
UnifiedReportSchema,
|
|
4654
3405
|
VAGUE_FILE_NAMES,
|
|
3406
|
+
buildFactorsFromDimensions,
|
|
4655
3407
|
buildSimpleProviderScore,
|
|
4656
3408
|
buildSpokeOutput,
|
|
3409
|
+
buildStandardToolScore,
|
|
4657
3410
|
calculateAgentGrounding,
|
|
4658
3411
|
calculateAiSignalClarity,
|
|
4659
3412
|
calculateBusinessROI,
|
|
@@ -4667,6 +3420,7 @@ export {
|
|
|
4667
3420
|
calculateDocDrift,
|
|
4668
3421
|
calculateExtendedFutureProofScore,
|
|
4669
3422
|
calculateFutureProofScore,
|
|
3423
|
+
calculateHeuristicConfidence,
|
|
4670
3424
|
calculateImportSimilarity,
|
|
4671
3425
|
calculateKnowledgeConcentration,
|
|
4672
3426
|
calculateMonthlyCost,
|
|
@@ -4674,28 +3428,37 @@ export {
|
|
|
4674
3428
|
calculatePatternEntropy,
|
|
4675
3429
|
calculateProductivityImpact,
|
|
4676
3430
|
calculateSemanticDistance,
|
|
3431
|
+
calculateStringSimilarity,
|
|
4677
3432
|
calculateTechnicalValueChain,
|
|
4678
3433
|
calculateTestabilityIndex,
|
|
4679
3434
|
calculateTokenBudget,
|
|
4680
3435
|
clearHistory,
|
|
4681
3436
|
createProvider,
|
|
3437
|
+
createStandardProgressCallback,
|
|
3438
|
+
displayStandardConsoleReport,
|
|
4682
3439
|
emitAnnotation,
|
|
4683
3440
|
emitIssuesAsAnnotations,
|
|
4684
3441
|
emitProgress,
|
|
4685
3442
|
estimateCostFromBudget,
|
|
4686
3443
|
estimateTokens,
|
|
4687
3444
|
exportHistory,
|
|
3445
|
+
extractCodeBlocks,
|
|
4688
3446
|
findLatestReport,
|
|
4689
3447
|
findLatestScanReport,
|
|
4690
3448
|
formatAcceptanceRate,
|
|
4691
3449
|
formatCost,
|
|
4692
3450
|
formatHours,
|
|
4693
3451
|
formatScore,
|
|
3452
|
+
formatStandardCliResult,
|
|
3453
|
+
formatStandardReport,
|
|
4694
3454
|
formatToolScore,
|
|
4695
3455
|
generateCompleteReport,
|
|
4696
3456
|
generateHTML,
|
|
3457
|
+
generateIssueSummary,
|
|
4697
3458
|
generateReportFooter,
|
|
4698
3459
|
generateReportHead,
|
|
3460
|
+
generateReportHero,
|
|
3461
|
+
generateScoreCard,
|
|
4699
3462
|
generateStatCards,
|
|
4700
3463
|
generateTable,
|
|
4701
3464
|
generateValueChain,
|
|
@@ -4712,24 +3475,30 @@ export {
|
|
|
4712
3475
|
getRatingDisplay,
|
|
4713
3476
|
getRatingEmoji,
|
|
4714
3477
|
getRatingLabel,
|
|
3478
|
+
getRatingMetadata,
|
|
4715
3479
|
getRatingSlug,
|
|
4716
3480
|
getRatingWithContext,
|
|
4717
3481
|
getRecommendedThreshold,
|
|
4718
3482
|
getRepoMetadata,
|
|
3483
|
+
getReportTimestamp,
|
|
4719
3484
|
getSafetyIcon,
|
|
4720
3485
|
getScoreBar,
|
|
3486
|
+
getScoreColor,
|
|
4721
3487
|
getSeverityBadge,
|
|
4722
3488
|
getSeverityColor,
|
|
4723
3489
|
getSeverityEnum,
|
|
4724
3490
|
getSeverityLevel,
|
|
4725
3491
|
getSeverityValue,
|
|
4726
3492
|
getSupportedLanguages,
|
|
3493
|
+
getTerminalDivider,
|
|
4727
3494
|
getToolEmoji,
|
|
4728
3495
|
getToolWeight,
|
|
4729
3496
|
getWasmPath,
|
|
4730
3497
|
groupIssuesByFile,
|
|
4731
3498
|
handleCLIError,
|
|
4732
3499
|
handleJSONOutput,
|
|
3500
|
+
handleStandardJSONOutput,
|
|
3501
|
+
inferPatternType,
|
|
4733
3502
|
initTreeSitter,
|
|
4734
3503
|
initializeParsers,
|
|
4735
3504
|
isFileSupported,
|
|
@@ -4746,8 +3515,12 @@ export {
|
|
|
4746
3515
|
parseFileExports,
|
|
4747
3516
|
parseWeightString,
|
|
4748
3517
|
predictAcceptanceRate,
|
|
3518
|
+
prepareActionConfig,
|
|
3519
|
+
printTerminalHeader,
|
|
4749
3520
|
readFileContent,
|
|
3521
|
+
resolveOutputFormat,
|
|
4750
3522
|
resolveOutputPath,
|
|
3523
|
+
runStandardCliAction,
|
|
4751
3524
|
saveScoreEntry,
|
|
4752
3525
|
scanEntries,
|
|
4753
3526
|
scanFiles,
|