cto-ai-cli 3.2.0 → 4.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 +201 -0
- package/README.md +70 -2
- package/dist/action/index.js +607 -83
- package/dist/api/dashboard.js +85 -23
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +86 -24
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +2925 -0
- package/dist/cli/score.js +2656 -217
- package/dist/cli/v2/index.js +111 -49
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +85 -1
- package/dist/engine/index.js +643 -42
- package/dist/engine/index.js.map +1 -1
- package/dist/gateway/index.d.ts +281 -0
- package/dist/gateway/index.js +2803 -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 +86 -24
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +108 -46
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +3 -2
package/dist/api/dashboard.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/api/dashboard.ts
|
|
4
|
-
import { resolve as resolve5, join as
|
|
5
|
-
import { writeFile, mkdir, readFile as readFile5 } from "fs/promises";
|
|
4
|
+
import { resolve as resolve5, join as join6 } from "path";
|
|
5
|
+
import { writeFile as writeFile2, mkdir, readFile as readFile5 } from "fs/promises";
|
|
6
6
|
|
|
7
7
|
// src/engine/analyzer.ts
|
|
8
8
|
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
@@ -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) => {
|
|
@@ -945,8 +1007,8 @@ function deduplicateFindings(findings) {
|
|
|
945
1007
|
// src/engine/pruner.ts
|
|
946
1008
|
import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
947
1009
|
import { readFile as readFile4 } from "fs/promises";
|
|
948
|
-
import { existsSync as
|
|
949
|
-
import { join as
|
|
1010
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1011
|
+
import { join as join5 } from "path";
|
|
950
1012
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
|
|
951
1013
|
async function pruneFile(file, level) {
|
|
952
1014
|
if (level === "excluded") {
|
|
@@ -1192,9 +1254,9 @@ function addJSDoc(node, parts) {
|
|
|
1192
1254
|
function findTsConfig(filePath) {
|
|
1193
1255
|
let dir = filePath;
|
|
1194
1256
|
for (let i = 0; i < 10; i++) {
|
|
1195
|
-
dir =
|
|
1196
|
-
const candidate =
|
|
1197
|
-
if (
|
|
1257
|
+
dir = join5(dir, "..");
|
|
1258
|
+
const candidate = join5(dir, "tsconfig.json");
|
|
1259
|
+
if (existsSync3(candidate)) return candidate;
|
|
1198
1260
|
}
|
|
1199
1261
|
return void 0;
|
|
1200
1262
|
}
|
|
@@ -1461,7 +1523,7 @@ async function selectContext(input) {
|
|
|
1461
1523
|
);
|
|
1462
1524
|
const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
|
|
1463
1525
|
const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
|
|
1464
|
-
const hash =
|
|
1526
|
+
const hash = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
|
|
1465
1527
|
return {
|
|
1466
1528
|
files: selectedFiles,
|
|
1467
1529
|
totalTokens: usedTokens,
|
|
@@ -1890,7 +1952,7 @@ function computeStrategyScore(strategy, budget) {
|
|
|
1890
1952
|
// src/api/dashboard.ts
|
|
1891
1953
|
async function loadHistory(ctoDir) {
|
|
1892
1954
|
try {
|
|
1893
|
-
const raw = await readFile5(
|
|
1955
|
+
const raw = await readFile5(join6(ctoDir, "history.json"), "utf-8");
|
|
1894
1956
|
return JSON.parse(raw);
|
|
1895
1957
|
} catch {
|
|
1896
1958
|
return [];
|
|
@@ -1898,11 +1960,11 @@ async function loadHistory(ctoDir) {
|
|
|
1898
1960
|
}
|
|
1899
1961
|
async function saveHistory(ctoDir, history) {
|
|
1900
1962
|
await mkdir(ctoDir, { recursive: true });
|
|
1901
|
-
await
|
|
1963
|
+
await writeFile2(join6(ctoDir, "history.json"), JSON.stringify(history, null, 2));
|
|
1902
1964
|
}
|
|
1903
1965
|
async function generateDashboard(projectPath, task = "general code review and refactoring", budget = 5e4) {
|
|
1904
1966
|
const absPath = resolve5(projectPath);
|
|
1905
|
-
const ctoDir =
|
|
1967
|
+
const ctoDir = join6(absPath, ".cto");
|
|
1906
1968
|
const analysis = await getCachedAnalysis(absPath);
|
|
1907
1969
|
const score = await computeContextScore(analysis, task, budget);
|
|
1908
1970
|
const benchmark = await runBenchmark(analysis, task, budget);
|
|
@@ -1932,9 +1994,9 @@ async function generateDashboard(projectPath, task = "general code review and re
|
|
|
1932
1994
|
generatedAt: /* @__PURE__ */ new Date()
|
|
1933
1995
|
};
|
|
1934
1996
|
const html = renderDashboardHTML(data);
|
|
1935
|
-
const htmlPath =
|
|
1997
|
+
const htmlPath = join6(ctoDir, "dashboard.html");
|
|
1936
1998
|
await mkdir(ctoDir, { recursive: true });
|
|
1937
|
-
await
|
|
1999
|
+
await writeFile2(htmlPath, html, "utf-8");
|
|
1938
2000
|
return { htmlPath, data };
|
|
1939
2001
|
}
|
|
1940
2002
|
function generateBadgeURL(score) {
|