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.
@@ -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 join5 } from "path";
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 createHash3 } from "crypto";
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 { resolve as resolve4, relative as relative4 } from "path";
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}[-.]?\\d{2}[-.]?\\d{4}\\b", flags: "g", severity: "high", description: "Possible SSN (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)" }
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 patterns = BUILTIN_PATTERNS.map((def) => ({
858
- type: def.type,
859
- pattern: new RegExp(def.source, def.flags),
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 existsSync2 } from "fs";
949
- import { join as join4 } from "path";
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 = join4(dir, "..");
1196
- const candidate = join4(dir, "tsconfig.json");
1197
- if (existsSync2(candidate)) return candidate;
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 = createHash3("sha256").update(hashInput).digest("hex").substring(0, 16);
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(join5(ctoDir, "history.json"), "utf-8");
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 writeFile(join5(ctoDir, "history.json"), JSON.stringify(history, null, 2));
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 = join5(absPath, ".cto");
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 = join5(ctoDir, "dashboard.html");
1997
+ const htmlPath = join6(ctoDir, "dashboard.html");
1936
1998
  await mkdir(ctoDir, { recursive: true });
1937
- await writeFile(htmlPath, html, "utf-8");
1999
+ await writeFile2(htmlPath, html, "utf-8");
1938
2000
  return { htmlPath, data };
1939
2001
  }
1940
2002
  function generateBadgeURL(score) {