cto-ai-cli 3.1.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 +352 -0
- package/README.md +192 -15
- package/dist/action/index.js +629 -83
- package/dist/api/dashboard.js +107 -23
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +108 -24
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +2925 -0
- package/dist/cli/score.js +3015 -237
- package/dist/cli/v2/index.js +133 -49
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +85 -1
- package/dist/engine/index.js +665 -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 +67 -3
- package/dist/govern/index.js +462 -23
- package/dist/govern/index.js.map +1 -1
- package/dist/interact/index.js +108 -24
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +130 -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" },
|
|
@@ -829,15 +831,66 @@ var BUILTIN_PATTERNS = [
|
|
|
829
831
|
{ type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
|
|
830
832
|
{ type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
|
|
831
833
|
// Environment variables with secrets
|
|
832
|
-
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
|
|
834
|
+
{ type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
|
|
835
|
+
// Stripe
|
|
836
|
+
{ type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
|
|
837
|
+
{ type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
|
|
838
|
+
{ type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
|
|
839
|
+
// Slack
|
|
840
|
+
{ type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
|
|
841
|
+
{ type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
|
|
842
|
+
{ type: "api-key", source: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+", flags: "g", severity: "high", description: "Slack Webhook URL" },
|
|
843
|
+
// Google
|
|
844
|
+
{ type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
|
|
845
|
+
{ type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
|
|
846
|
+
// Azure
|
|
847
|
+
{ type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
|
|
848
|
+
// Twilio
|
|
849
|
+
{ type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
|
|
850
|
+
// SendGrid
|
|
851
|
+
{ type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
|
|
852
|
+
// JWT
|
|
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" },
|
|
874
|
+
// PII
|
|
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)" },
|
|
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)" }
|
|
833
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
|
+
}
|
|
834
890
|
function buildPatterns(customPatterns = []) {
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
severity: def.severity,
|
|
839
|
-
description: def.description
|
|
840
|
-
}));
|
|
891
|
+
const builtins = getBuiltinPatterns();
|
|
892
|
+
if (customPatterns.length === 0) return builtins;
|
|
893
|
+
const patterns = [...builtins];
|
|
841
894
|
for (const custom of customPatterns) {
|
|
842
895
|
try {
|
|
843
896
|
patterns.push({
|
|
@@ -851,7 +904,7 @@ function buildPatterns(customPatterns = []) {
|
|
|
851
904
|
}
|
|
852
905
|
return patterns;
|
|
853
906
|
}
|
|
854
|
-
function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
907
|
+
function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
|
|
855
908
|
const findings = [];
|
|
856
909
|
const lines = content.split("\n");
|
|
857
910
|
const allPatterns = buildPatterns(customPatterns);
|
|
@@ -863,6 +916,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
|
|
|
863
916
|
while ((match = secretPattern.pattern.exec(line)) !== null) {
|
|
864
917
|
const matchText = match[0];
|
|
865
918
|
if (isTemplateOrPlaceholder(matchText)) continue;
|
|
919
|
+
if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
|
|
866
920
|
findings.push({
|
|
867
921
|
type: secretPattern.type,
|
|
868
922
|
file: filePath,
|
|
@@ -910,6 +964,36 @@ function isTemplateOrPlaceholder(value) {
|
|
|
910
964
|
];
|
|
911
965
|
return placeholders.some((p) => p.test(value));
|
|
912
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
|
+
}
|
|
913
997
|
function deduplicateFindings(findings) {
|
|
914
998
|
const seen = /* @__PURE__ */ new Set();
|
|
915
999
|
return findings.filter((f) => {
|
|
@@ -923,8 +1007,8 @@ function deduplicateFindings(findings) {
|
|
|
923
1007
|
// src/engine/pruner.ts
|
|
924
1008
|
import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
925
1009
|
import { readFile as readFile4 } from "fs/promises";
|
|
926
|
-
import { existsSync as
|
|
927
|
-
import { join as
|
|
1010
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1011
|
+
import { join as join5 } from "path";
|
|
928
1012
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
|
|
929
1013
|
async function pruneFile(file, level) {
|
|
930
1014
|
if (level === "excluded") {
|
|
@@ -1170,9 +1254,9 @@ function addJSDoc(node, parts) {
|
|
|
1170
1254
|
function findTsConfig(filePath) {
|
|
1171
1255
|
let dir = filePath;
|
|
1172
1256
|
for (let i = 0; i < 10; i++) {
|
|
1173
|
-
dir =
|
|
1174
|
-
const candidate =
|
|
1175
|
-
if (
|
|
1257
|
+
dir = join5(dir, "..");
|
|
1258
|
+
const candidate = join5(dir, "tsconfig.json");
|
|
1259
|
+
if (existsSync3(candidate)) return candidate;
|
|
1176
1260
|
}
|
|
1177
1261
|
return void 0;
|
|
1178
1262
|
}
|
|
@@ -1439,7 +1523,7 @@ async function selectContext(input) {
|
|
|
1439
1523
|
);
|
|
1440
1524
|
const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
|
|
1441
1525
|
const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
|
|
1442
|
-
const hash =
|
|
1526
|
+
const hash = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
|
|
1443
1527
|
return {
|
|
1444
1528
|
files: selectedFiles,
|
|
1445
1529
|
totalTokens: usedTokens,
|
|
@@ -1868,7 +1952,7 @@ function computeStrategyScore(strategy, budget) {
|
|
|
1868
1952
|
// src/api/dashboard.ts
|
|
1869
1953
|
async function loadHistory(ctoDir) {
|
|
1870
1954
|
try {
|
|
1871
|
-
const raw = await readFile5(
|
|
1955
|
+
const raw = await readFile5(join6(ctoDir, "history.json"), "utf-8");
|
|
1872
1956
|
return JSON.parse(raw);
|
|
1873
1957
|
} catch {
|
|
1874
1958
|
return [];
|
|
@@ -1876,11 +1960,11 @@ async function loadHistory(ctoDir) {
|
|
|
1876
1960
|
}
|
|
1877
1961
|
async function saveHistory(ctoDir, history) {
|
|
1878
1962
|
await mkdir(ctoDir, { recursive: true });
|
|
1879
|
-
await
|
|
1963
|
+
await writeFile2(join6(ctoDir, "history.json"), JSON.stringify(history, null, 2));
|
|
1880
1964
|
}
|
|
1881
1965
|
async function generateDashboard(projectPath, task = "general code review and refactoring", budget = 5e4) {
|
|
1882
1966
|
const absPath = resolve5(projectPath);
|
|
1883
|
-
const ctoDir =
|
|
1967
|
+
const ctoDir = join6(absPath, ".cto");
|
|
1884
1968
|
const analysis = await getCachedAnalysis(absPath);
|
|
1885
1969
|
const score = await computeContextScore(analysis, task, budget);
|
|
1886
1970
|
const benchmark = await runBenchmark(analysis, task, budget);
|
|
@@ -1910,9 +1994,9 @@ async function generateDashboard(projectPath, task = "general code review and re
|
|
|
1910
1994
|
generatedAt: /* @__PURE__ */ new Date()
|
|
1911
1995
|
};
|
|
1912
1996
|
const html = renderDashboardHTML(data);
|
|
1913
|
-
const htmlPath =
|
|
1997
|
+
const htmlPath = join6(ctoDir, "dashboard.html");
|
|
1914
1998
|
await mkdir(ctoDir, { recursive: true });
|
|
1915
|
-
await
|
|
1999
|
+
await writeFile2(htmlPath, html, "utf-8");
|
|
1916
2000
|
return { htmlPath, data };
|
|
1917
2001
|
}
|
|
1918
2002
|
function generateBadgeURL(score) {
|