cto-ai-cli 3.2.0 → 5.0.0
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/DOCS.md +402 -2
- package/README.md +224 -252
- package/dist/action/index.js +870 -231
- package/dist/api/dashboard.js +342 -165
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +349 -166
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +3040 -0
- package/dist/cli/score.js +4142 -548
- package/dist/cli/v2/index.js +353 -176
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +205 -1
- package/dist/engine/index.js +1620 -196
- package/dist/engine/index.js.map +1 -1
- package/dist/fsevents-X6WP4TKM.node +0 -0
- package/dist/gateway/index.d.ts +281 -0
- package/dist/gateway/index.js +2918 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/govern/index.d.ts +45 -4
- package/dist/govern/index.js +318 -33
- package/dist/govern/index.js.map +1 -1
- package/dist/interact/index.js +336 -159
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +352 -175
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +10 -23
package/dist/api/server.js
CHANGED
|
@@ -800,11 +800,13 @@ async function getCachedAnalysis(projectPath, config) {
|
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
// src/engine/selector.ts
|
|
803
|
-
import { createHash as
|
|
803
|
+
import { createHash as createHash4 } from "crypto";
|
|
804
804
|
|
|
805
805
|
// src/govern/secrets.ts
|
|
806
806
|
import { readFile as readFile3 } from "fs/promises";
|
|
807
|
-
import {
|
|
807
|
+
import { readFileSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
808
|
+
import { resolve as resolve4, relative as relative4, join as join4, dirname as dirname2 } from "path";
|
|
809
|
+
import { createHash as createHash3 } from "crypto";
|
|
808
810
|
var BUILTIN_PATTERNS = [
|
|
809
811
|
// API Keys
|
|
810
812
|
{ type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
|
|
@@ -849,17 +851,46 @@ var BUILTIN_PATTERNS = [
|
|
|
849
851
|
{ type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
|
|
850
852
|
// JWT
|
|
851
853
|
{ type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
|
|
854
|
+
// Datadog
|
|
855
|
+
{ type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
|
|
856
|
+
{ type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
|
|
857
|
+
// Sentry
|
|
858
|
+
{ type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
|
|
859
|
+
// Firebase
|
|
860
|
+
{ type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
|
|
861
|
+
{ type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
|
|
862
|
+
// Supabase
|
|
863
|
+
{ type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
|
|
864
|
+
{ type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
|
|
865
|
+
// Vercel
|
|
866
|
+
{ type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
|
|
867
|
+
// Heroku
|
|
868
|
+
{ type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
|
|
869
|
+
// DigitalOcean
|
|
870
|
+
{ type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
|
|
871
|
+
{ type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
|
|
872
|
+
// Mailgun
|
|
873
|
+
{ type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
|
|
852
874
|
// PII
|
|
853
875
|
{ type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
|
|
854
|
-
{ type: "pii", source: "\\b\\d{3}[-.]
|
|
876
|
+
{ type: "pii", source: "\\b(?!000|666|9\\d{2})(\\d{3})[-.]?(?!00)(\\d{2})[-.]?(?!0000)(\\d{4})\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
|
|
855
877
|
];
|
|
878
|
+
var _cachedBuiltinPatterns = null;
|
|
879
|
+
function getBuiltinPatterns() {
|
|
880
|
+
if (!_cachedBuiltinPatterns) {
|
|
881
|
+
_cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
|
|
882
|
+
type: def.type,
|
|
883
|
+
pattern: new RegExp(def.source, def.flags),
|
|
884
|
+
severity: def.severity,
|
|
885
|
+
description: def.description
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
return _cachedBuiltinPatterns;
|
|
889
|
+
}
|
|
856
890
|
function buildPatterns(customPatterns = []) {
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
severity: def.severity,
|
|
861
|
-
description: def.description
|
|
862
|
-
}));
|
|
891
|
+
const builtins = getBuiltinPatterns();
|
|
892
|
+
if (customPatterns.length === 0) return builtins;
|
|
893
|
+
const patterns = [...builtins];
|
|
863
894
|
for (const custom of customPatterns) {
|
|
864
895
|
try {
|
|
865
896
|
patterns.push({
|
|
@@ -873,7 +904,7 @@ function buildPatterns(customPatterns = []) {
|
|
|
873
904
|
}
|
|
874
905
|
return patterns;
|
|
875
906
|
}
|
|
876
|
-
function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
907
|
+
function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
|
|
877
908
|
const findings = [];
|
|
878
909
|
const lines = content.split("\n");
|
|
879
910
|
const allPatterns = buildPatterns(customPatterns);
|
|
@@ -885,6 +916,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
|
885
916
|
while ((match = secretPattern.pattern.exec(line)) !== null) {
|
|
886
917
|
const matchText = match[0];
|
|
887
918
|
if (isTemplateOrPlaceholder(matchText)) continue;
|
|
919
|
+
if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
|
|
888
920
|
findings.push({
|
|
889
921
|
type: secretPattern.type,
|
|
890
922
|
file: filePath,
|
|
@@ -932,6 +964,36 @@ function isTemplateOrPlaceholder(value) {
|
|
|
932
964
|
];
|
|
933
965
|
return placeholders.some((p) => p.test(value));
|
|
934
966
|
}
|
|
967
|
+
var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
968
|
+
"example.com",
|
|
969
|
+
"example.org",
|
|
970
|
+
"example.net",
|
|
971
|
+
"test.com",
|
|
972
|
+
"test.org",
|
|
973
|
+
"test.net",
|
|
974
|
+
"localhost",
|
|
975
|
+
"localhost.localdomain",
|
|
976
|
+
"email.com",
|
|
977
|
+
"mail.com",
|
|
978
|
+
"foo.com",
|
|
979
|
+
"bar.com",
|
|
980
|
+
"baz.com",
|
|
981
|
+
"acme.com",
|
|
982
|
+
"company.com",
|
|
983
|
+
"corp.com",
|
|
984
|
+
"noreply.com",
|
|
985
|
+
"no-reply.com",
|
|
986
|
+
"users.noreply.github.com",
|
|
987
|
+
"placeholder.com"
|
|
988
|
+
]);
|
|
989
|
+
function isSafeEmail(value, extraDomains) {
|
|
990
|
+
const match = value.match(/@([a-zA-Z0-9.-]+)$/);
|
|
991
|
+
if (!match) return false;
|
|
992
|
+
const domain = match[1].toLowerCase();
|
|
993
|
+
if (PII_SAFE_EMAIL_DOMAINS.has(domain)) return true;
|
|
994
|
+
if (extraDomains && extraDomains.has(domain)) return true;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
935
997
|
function deduplicateFindings(findings) {
|
|
936
998
|
const seen = /* @__PURE__ */ new Set();
|
|
937
999
|
return findings.filter((f) => {
|
|
@@ -943,10 +1005,7 @@ function deduplicateFindings(findings) {
|
|
|
943
1005
|
}
|
|
944
1006
|
|
|
945
1007
|
// src/engine/pruner.ts
|
|
946
|
-
import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
947
1008
|
import { readFile as readFile4 } from "fs/promises";
|
|
948
|
-
import { existsSync as existsSync2 } from "fs";
|
|
949
|
-
import { join as join4 } from "path";
|
|
950
1009
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
|
|
951
1010
|
async function pruneFile(file, level) {
|
|
952
1011
|
if (level === "excluded") {
|
|
@@ -969,23 +1028,7 @@ async function pruneTypeScript(file, level) {
|
|
|
969
1028
|
} catch {
|
|
970
1029
|
return emptyResult(file, level);
|
|
971
1030
|
}
|
|
972
|
-
|
|
973
|
-
try {
|
|
974
|
-
const tsConfigPath = findTsConfig(file.path);
|
|
975
|
-
project = new Project2({
|
|
976
|
-
tsConfigFilePath: tsConfigPath,
|
|
977
|
-
skipAddingFilesFromTsConfig: true,
|
|
978
|
-
compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
|
|
979
|
-
});
|
|
980
|
-
project.createSourceFile(file.path, content, { overwrite: true });
|
|
981
|
-
} catch {
|
|
982
|
-
return pruneGenericFromContent(file, content, level);
|
|
983
|
-
}
|
|
984
|
-
const sourceFile = project.getSourceFiles()[0];
|
|
985
|
-
if (!sourceFile) {
|
|
986
|
-
return pruneGenericFromContent(file, content, level);
|
|
987
|
-
}
|
|
988
|
-
const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
|
|
1031
|
+
const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
|
|
989
1032
|
const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
|
|
990
1033
|
const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
|
|
991
1034
|
return {
|
|
@@ -997,131 +1040,281 @@ async function pruneTypeScript(file, level) {
|
|
|
997
1040
|
savingsPercent: Math.max(0, savingsPercent)
|
|
998
1041
|
};
|
|
999
1042
|
}
|
|
1000
|
-
function
|
|
1043
|
+
function extractSignaturesRegex(content) {
|
|
1044
|
+
const lines = content.split("\n");
|
|
1001
1045
|
const parts = [];
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1046
|
+
let i = 0;
|
|
1047
|
+
while (i < lines.length) {
|
|
1048
|
+
const line = lines[i];
|
|
1049
|
+
const trimmed = line.trim();
|
|
1050
|
+
if (trimmed === "") {
|
|
1051
|
+
i++;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (trimmed.startsWith("/**")) {
|
|
1055
|
+
const docLines = [];
|
|
1056
|
+
while (i < lines.length) {
|
|
1057
|
+
docLines.push(lines[i]);
|
|
1058
|
+
if (lines[i].includes("*/")) {
|
|
1059
|
+
i++;
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
i++;
|
|
1063
|
+
}
|
|
1064
|
+
parts.push(docLines.join("\n"));
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (trimmed.startsWith("//")) {
|
|
1068
|
+
parts.push(line);
|
|
1069
|
+
i++;
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
|
|
1073
|
+
const block = collectBracedLine(lines, i);
|
|
1074
|
+
parts.push(block.text);
|
|
1075
|
+
i = block.nextIndex;
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
|
|
1079
|
+
const block = collectBracedLine(lines, i);
|
|
1080
|
+
parts.push(block.text);
|
|
1081
|
+
i = block.nextIndex;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
|
|
1085
|
+
const block = collectBalanced(lines, i);
|
|
1086
|
+
parts.push(block.text);
|
|
1087
|
+
i = block.nextIndex;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
|
|
1091
|
+
const block = collectBalanced(lines, i);
|
|
1092
|
+
parts.push(block.text);
|
|
1093
|
+
i = block.nextIndex;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
1097
|
+
const block = collectBalanced(lines, i);
|
|
1098
|
+
parts.push(block.text);
|
|
1099
|
+
i = block.nextIndex;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
1103
|
+
if (fnMatch) {
|
|
1104
|
+
const sig = extractFnSignature(lines, i);
|
|
1105
|
+
parts.push(`${sig} { /* ... */ }`);
|
|
1106
|
+
i = skipBlock(lines, i);
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
|
|
1110
|
+
if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
|
|
1111
|
+
const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
|
|
1112
|
+
if (prefix) {
|
|
1113
|
+
parts.push(`${prefix} /* ... */;`);
|
|
1045
1114
|
}
|
|
1115
|
+
i = skipBlock(lines, i);
|
|
1116
|
+
continue;
|
|
1046
1117
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
for (const prop of cls.getProperties()) {
|
|
1061
|
-
parts.push(` ${prop.getText()}`);
|
|
1062
|
-
}
|
|
1063
|
-
const ctor = cls.getConstructors()[0];
|
|
1064
|
-
if (ctor) {
|
|
1065
|
-
const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
|
|
1066
|
-
parts.push(` constructor(${ctorParams}) { /* ... */ }`);
|
|
1067
|
-
}
|
|
1068
|
-
for (const method of cls.getMethods()) {
|
|
1069
|
-
const isStatic = method.isStatic();
|
|
1070
|
-
const isAsync = method.isAsync();
|
|
1071
|
-
const methodName = method.getName();
|
|
1072
|
-
const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
|
|
1073
|
-
const returnType = method.getReturnTypeNode()?.getText();
|
|
1074
|
-
const returnStr = returnType ? `: ${returnType}` : "";
|
|
1075
|
-
const staticStr = isStatic ? "static " : "";
|
|
1076
|
-
const asyncStr = isAsync ? "async " : "";
|
|
1077
|
-
parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
|
|
1078
|
-
}
|
|
1079
|
-
parts.push("}");
|
|
1080
|
-
}
|
|
1081
|
-
for (const exp of sf.getExportDeclarations()) {
|
|
1082
|
-
parts.push(exp.getText());
|
|
1083
|
-
}
|
|
1084
|
-
for (const exp of sf.getExportAssignments()) {
|
|
1085
|
-
parts.push(exp.getText());
|
|
1118
|
+
if (arrowMatch) {
|
|
1119
|
+
const block = collectStatement(lines, i);
|
|
1120
|
+
parts.push(block.text);
|
|
1121
|
+
i = block.nextIndex;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
|
|
1125
|
+
const classOutline = extractClassOutline(lines, i);
|
|
1126
|
+
parts.push(classOutline.text);
|
|
1127
|
+
i = classOutline.nextIndex;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
i++;
|
|
1086
1131
|
}
|
|
1087
1132
|
return parts.join("\n");
|
|
1088
1133
|
}
|
|
1089
|
-
function
|
|
1134
|
+
function extractSkeletonRegex(content) {
|
|
1135
|
+
const lines = content.split("\n");
|
|
1090
1136
|
const parts = [];
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1137
|
+
let i = 0;
|
|
1138
|
+
while (i < lines.length) {
|
|
1139
|
+
const trimmed = lines[i].trim();
|
|
1140
|
+
if (/^import\s/.test(trimmed)) {
|
|
1141
|
+
const block = collectBracedLine(lines, i);
|
|
1142
|
+
parts.push(block.text);
|
|
1143
|
+
i = block.nextIndex;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
|
|
1147
|
+
const block = collectBalanced(lines, i);
|
|
1148
|
+
parts.push(block.text);
|
|
1149
|
+
i = block.nextIndex;
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
1153
|
+
const block = collectBalanced(lines, i);
|
|
1154
|
+
parts.push(block.text);
|
|
1155
|
+
i = block.nextIndex;
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
|
|
1159
|
+
const sig = extractFnSignature(lines, i);
|
|
1160
|
+
parts.push(`${sig};`);
|
|
1161
|
+
i = skipBlock(lines, i);
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
|
|
1165
|
+
const nameMatch = trimmed.match(/class\s+(\w+)/);
|
|
1166
|
+
const name = nameMatch?.[1] ?? "Unknown";
|
|
1167
|
+
const end = skipBlock(lines, i);
|
|
1168
|
+
const methods = [];
|
|
1169
|
+
for (let j = i + 1; j < end; j++) {
|
|
1170
|
+
const mt = lines[j].trim();
|
|
1171
|
+
const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
|
|
1172
|
+
if (mm && mm[1] !== "constructor") methods.push(mm[1]);
|
|
1173
|
+
}
|
|
1174
|
+
parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
|
|
1175
|
+
i = end;
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (/^export\s*(\{|\*)/.test(trimmed)) {
|
|
1179
|
+
const block = collectBracedLine(lines, i);
|
|
1180
|
+
parts.push(block.text);
|
|
1181
|
+
i = block.nextIndex;
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
i++;
|
|
1122
1185
|
}
|
|
1123
1186
|
return parts.join("\n");
|
|
1124
1187
|
}
|
|
1188
|
+
function collectBracedLine(lines, start) {
|
|
1189
|
+
let text = lines[start];
|
|
1190
|
+
let i = start + 1;
|
|
1191
|
+
while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
|
|
1192
|
+
text += "\n" + lines[i];
|
|
1193
|
+
i++;
|
|
1194
|
+
}
|
|
1195
|
+
return { text, nextIndex: i };
|
|
1196
|
+
}
|
|
1197
|
+
function collectBalanced(lines, start) {
|
|
1198
|
+
let depth = 0;
|
|
1199
|
+
let text = "";
|
|
1200
|
+
let i = start;
|
|
1201
|
+
let started = false;
|
|
1202
|
+
while (i < lines.length) {
|
|
1203
|
+
const line = lines[i];
|
|
1204
|
+
text += (text ? "\n" : "") + line;
|
|
1205
|
+
for (const ch of line) {
|
|
1206
|
+
if (ch === "{" || ch === "(") {
|
|
1207
|
+
depth++;
|
|
1208
|
+
started = true;
|
|
1209
|
+
}
|
|
1210
|
+
if (ch === "}" || ch === ")") depth--;
|
|
1211
|
+
}
|
|
1212
|
+
i++;
|
|
1213
|
+
if (started && depth <= 0) break;
|
|
1214
|
+
if (!started && line.includes(";")) break;
|
|
1215
|
+
}
|
|
1216
|
+
return { text, nextIndex: i };
|
|
1217
|
+
}
|
|
1218
|
+
function collectStatement(lines, start) {
|
|
1219
|
+
let text = lines[start];
|
|
1220
|
+
let i = start + 1;
|
|
1221
|
+
if (text.includes(";")) return { text, nextIndex: i };
|
|
1222
|
+
let depth = 0;
|
|
1223
|
+
for (const ch of text) {
|
|
1224
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
1225
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
1226
|
+
}
|
|
1227
|
+
while (i < lines.length && depth > 0) {
|
|
1228
|
+
text += "\n" + lines[i];
|
|
1229
|
+
for (const ch of lines[i]) {
|
|
1230
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
1231
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
1232
|
+
}
|
|
1233
|
+
i++;
|
|
1234
|
+
}
|
|
1235
|
+
return { text, nextIndex: i };
|
|
1236
|
+
}
|
|
1237
|
+
function extractFnSignature(lines, start) {
|
|
1238
|
+
let sig = "";
|
|
1239
|
+
let i = start;
|
|
1240
|
+
while (i < lines.length) {
|
|
1241
|
+
const line = lines[i].trim();
|
|
1242
|
+
sig += (sig ? " " : "") + line;
|
|
1243
|
+
if (line.includes("{")) {
|
|
1244
|
+
sig = sig.replace(/\s*\{[^]*$/, "").trim();
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
i++;
|
|
1248
|
+
}
|
|
1249
|
+
return sig;
|
|
1250
|
+
}
|
|
1251
|
+
function skipBlock(lines, start) {
|
|
1252
|
+
let depth = 0;
|
|
1253
|
+
let i = start;
|
|
1254
|
+
let foundBrace = false;
|
|
1255
|
+
while (i < lines.length) {
|
|
1256
|
+
for (const ch of lines[i]) {
|
|
1257
|
+
if (ch === "{") {
|
|
1258
|
+
depth++;
|
|
1259
|
+
foundBrace = true;
|
|
1260
|
+
}
|
|
1261
|
+
if (ch === "}") depth--;
|
|
1262
|
+
}
|
|
1263
|
+
i++;
|
|
1264
|
+
if (foundBrace && depth <= 0) break;
|
|
1265
|
+
if (!foundBrace && lines[i - 1].includes(";")) break;
|
|
1266
|
+
}
|
|
1267
|
+
return i;
|
|
1268
|
+
}
|
|
1269
|
+
function looksLikeFunctionDecl(lines, start) {
|
|
1270
|
+
const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
|
|
1271
|
+
return /=>/.test(chunk) || /=\s*function/.test(chunk);
|
|
1272
|
+
}
|
|
1273
|
+
function extractClassOutline(lines, start) {
|
|
1274
|
+
const header = lines[start].trim();
|
|
1275
|
+
let headerText = header;
|
|
1276
|
+
let i = start + 1;
|
|
1277
|
+
if (!header.includes("{")) {
|
|
1278
|
+
while (i < lines.length) {
|
|
1279
|
+
headerText += " " + lines[i].trim();
|
|
1280
|
+
if (lines[i].includes("{")) {
|
|
1281
|
+
i++;
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
i++;
|
|
1285
|
+
}
|
|
1286
|
+
} else {
|
|
1287
|
+
i = start + 1;
|
|
1288
|
+
}
|
|
1289
|
+
const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
|
|
1290
|
+
let depth = 1;
|
|
1291
|
+
while (i < lines.length && depth > 0) {
|
|
1292
|
+
const line = lines[i];
|
|
1293
|
+
const trimmed = line.trim();
|
|
1294
|
+
for (const ch of line) {
|
|
1295
|
+
if (ch === "{") depth++;
|
|
1296
|
+
if (ch === "}") depth--;
|
|
1297
|
+
}
|
|
1298
|
+
if (depth <= 0) {
|
|
1299
|
+
i++;
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
if (depth === 1) {
|
|
1303
|
+
if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
|
|
1304
|
+
bodyParts.push(` ${trimmed}`);
|
|
1305
|
+
} else if (/^constructor\s*\(/.test(trimmed)) {
|
|
1306
|
+
const sig = extractFnSignature(lines, i);
|
|
1307
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
1308
|
+
} else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
1309
|
+
const sig = extractFnSignature(lines, i);
|
|
1310
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
i++;
|
|
1314
|
+
}
|
|
1315
|
+
bodyParts.push("}");
|
|
1316
|
+
return { text: bodyParts.join("\n"), nextIndex: i };
|
|
1317
|
+
}
|
|
1125
1318
|
async function pruneGeneric(file, level) {
|
|
1126
1319
|
let content;
|
|
1127
1320
|
try {
|
|
@@ -1182,22 +1375,6 @@ function emptyResult(file, level) {
|
|
|
1182
1375
|
savingsPercent: 100
|
|
1183
1376
|
};
|
|
1184
1377
|
}
|
|
1185
|
-
function addJSDoc(node, parts) {
|
|
1186
|
-
if (!node.getJsDocs) return;
|
|
1187
|
-
const docs = node.getJsDocs();
|
|
1188
|
-
if (docs.length > 0) {
|
|
1189
|
-
parts.push(docs[0].getText());
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
function findTsConfig(filePath) {
|
|
1193
|
-
let dir = filePath;
|
|
1194
|
-
for (let i = 0; i < 10; i++) {
|
|
1195
|
-
dir = join4(dir, "..");
|
|
1196
|
-
const candidate = join4(dir, "tsconfig.json");
|
|
1197
|
-
if (existsSync2(candidate)) return candidate;
|
|
1198
|
-
}
|
|
1199
|
-
return void 0;
|
|
1200
|
-
}
|
|
1201
1378
|
|
|
1202
1379
|
// src/engine/graph-utils.ts
|
|
1203
1380
|
function buildAdjacencyList(edges) {
|
|
@@ -1461,7 +1638,7 @@ async function selectContext(input) {
|
|
|
1461
1638
|
);
|
|
1462
1639
|
const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
|
|
1463
1640
|
const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
|
|
1464
|
-
const hash =
|
|
1641
|
+
const hash = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
|
|
1465
1642
|
return {
|
|
1466
1643
|
files: selectedFiles,
|
|
1467
1644
|
totalTokens: usedTokens,
|
|
@@ -2858,7 +3035,7 @@ function formatCost(cost) {
|
|
|
2858
3035
|
}
|
|
2859
3036
|
|
|
2860
3037
|
// src/govern/audit.ts
|
|
2861
|
-
import { randomUUID, createHash as
|
|
3038
|
+
import { randomUUID, createHash as createHash5 } from "crypto";
|
|
2862
3039
|
import { readdir as readdir3, chmod } from "fs/promises";
|
|
2863
3040
|
import { join as join5 } from "path";
|
|
2864
3041
|
import { userInfo } from "os";
|
|
@@ -2882,7 +3059,7 @@ function computeIntegrityHash(entry) {
|
|
|
2882
3059
|
projectPath: entry.projectPath,
|
|
2883
3060
|
details: entry.details
|
|
2884
3061
|
});
|
|
2885
|
-
return
|
|
3062
|
+
return createHash5("sha256").update(payload).digest("hex");
|
|
2886
3063
|
}
|
|
2887
3064
|
async function ensureDir(dirPath) {
|
|
2888
3065
|
const { mkdir } = await import("fs/promises");
|
|
@@ -2898,9 +3075,9 @@ async function readJSON(filePath) {
|
|
|
2898
3075
|
}
|
|
2899
3076
|
}
|
|
2900
3077
|
async function writeJSON(filePath, data) {
|
|
2901
|
-
const { writeFile } = await import("fs/promises");
|
|
3078
|
+
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
2902
3079
|
await ensureDir(join5(filePath, ".."));
|
|
2903
|
-
await
|
|
3080
|
+
await writeFile2(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
2904
3081
|
}
|
|
2905
3082
|
async function logAudit(action, projectPath, details = {}) {
|
|
2906
3083
|
const auditDir = getAuditDir();
|
|
@@ -3072,6 +3249,12 @@ function checkRateLimit(ip) {
|
|
|
3072
3249
|
entry.count++;
|
|
3073
3250
|
return true;
|
|
3074
3251
|
}
|
|
3252
|
+
setInterval(() => {
|
|
3253
|
+
const now = Date.now();
|
|
3254
|
+
for (const [ip, entry] of requestCounts) {
|
|
3255
|
+
if (now > entry.reset) requestCounts.delete(ip);
|
|
3256
|
+
}
|
|
3257
|
+
}, 5 * 6e4).unref();
|
|
3075
3258
|
function getIP(req) {
|
|
3076
3259
|
return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ?? req.socket.remoteAddress ?? "unknown";
|
|
3077
3260
|
}
|