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/dist/mcp/v2.js CHANGED
@@ -13778,8 +13778,8 @@ function date4(params) {
13778
13778
  config(en_default());
13779
13779
 
13780
13780
  // src/mcp/v2.ts
13781
- import { resolve as resolve8, join as join7 } from "path";
13782
- import { readFile as readFile8, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13781
+ import { resolve as resolve8, join as join8 } from "path";
13782
+ import { readFile as readFile8, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
13783
13783
 
13784
13784
  // src/engine/analyzer.ts
13785
13785
  import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
@@ -14716,11 +14716,13 @@ function getActiveWatchers() {
14716
14716
  }
14717
14717
 
14718
14718
  // src/engine/selector.ts
14719
- import { createHash as createHash3 } from "crypto";
14719
+ import { createHash as createHash4 } from "crypto";
14720
14720
 
14721
14721
  // src/govern/secrets.ts
14722
14722
  import { readFile as readFile3 } from "fs/promises";
14723
- import { resolve as resolve5, relative as relative4 } from "path";
14723
+ import { readFileSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
14724
+ import { resolve as resolve5, relative as relative4, join as join4, dirname as dirname2 } from "path";
14725
+ import { createHash as createHash3 } from "crypto";
14724
14726
  var BUILTIN_PATTERNS = [
14725
14727
  // API Keys
14726
14728
  { type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
@@ -14745,15 +14747,66 @@ var BUILTIN_PATTERNS = [
14745
14747
  { type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
14746
14748
  { type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
14747
14749
  // Environment variables with secrets
14748
- { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
14750
+ { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
14751
+ // Stripe
14752
+ { type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
14753
+ { type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
14754
+ { type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
14755
+ // Slack
14756
+ { type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
14757
+ { type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
14758
+ { 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" },
14759
+ // Google
14760
+ { type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
14761
+ { type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
14762
+ // Azure
14763
+ { type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
14764
+ // Twilio
14765
+ { type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
14766
+ // SendGrid
14767
+ { type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
14768
+ // JWT
14769
+ { 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" },
14770
+ // Datadog
14771
+ { type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
14772
+ { type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
14773
+ // Sentry
14774
+ { type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
14775
+ // Firebase
14776
+ { type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
14777
+ { type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
14778
+ // Supabase
14779
+ { type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
14780
+ { type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
14781
+ // Vercel
14782
+ { type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
14783
+ // Heroku
14784
+ { type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
14785
+ // DigitalOcean
14786
+ { type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
14787
+ { type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
14788
+ // Mailgun
14789
+ { type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
14790
+ // PII
14791
+ { 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)" },
14792
+ { 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)" }
14749
14793
  ];
14794
+ var _cachedBuiltinPatterns = null;
14795
+ function getBuiltinPatterns() {
14796
+ if (!_cachedBuiltinPatterns) {
14797
+ _cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
14798
+ type: def.type,
14799
+ pattern: new RegExp(def.source, def.flags),
14800
+ severity: def.severity,
14801
+ description: def.description
14802
+ }));
14803
+ }
14804
+ return _cachedBuiltinPatterns;
14805
+ }
14750
14806
  function buildPatterns(customPatterns = []) {
14751
- const patterns = BUILTIN_PATTERNS.map((def) => ({
14752
- type: def.type,
14753
- pattern: new RegExp(def.source, def.flags),
14754
- severity: def.severity,
14755
- description: def.description
14756
- }));
14807
+ const builtins = getBuiltinPatterns();
14808
+ if (customPatterns.length === 0) return builtins;
14809
+ const patterns = [...builtins];
14757
14810
  for (const custom2 of customPatterns) {
14758
14811
  try {
14759
14812
  patterns.push({
@@ -14767,7 +14820,7 @@ function buildPatterns(customPatterns = []) {
14767
14820
  }
14768
14821
  return patterns;
14769
14822
  }
14770
- function scanContentForSecrets(content, filePath, customPatterns = []) {
14823
+ function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
14771
14824
  const findings = [];
14772
14825
  const lines = content.split("\n");
14773
14826
  const allPatterns = buildPatterns(customPatterns);
@@ -14779,6 +14832,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
14779
14832
  while ((match = secretPattern.pattern.exec(line)) !== null) {
14780
14833
  const matchText = match[0];
14781
14834
  if (isTemplateOrPlaceholder(matchText)) continue;
14835
+ if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
14782
14836
  findings.push({
14783
14837
  type: secretPattern.type,
14784
14838
  file: filePath,
@@ -14837,6 +14891,36 @@ function isTemplateOrPlaceholder(value) {
14837
14891
  ];
14838
14892
  return placeholders.some((p) => p.test(value));
14839
14893
  }
14894
+ var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
14895
+ "example.com",
14896
+ "example.org",
14897
+ "example.net",
14898
+ "test.com",
14899
+ "test.org",
14900
+ "test.net",
14901
+ "localhost",
14902
+ "localhost.localdomain",
14903
+ "email.com",
14904
+ "mail.com",
14905
+ "foo.com",
14906
+ "bar.com",
14907
+ "baz.com",
14908
+ "acme.com",
14909
+ "company.com",
14910
+ "corp.com",
14911
+ "noreply.com",
14912
+ "no-reply.com",
14913
+ "users.noreply.github.com",
14914
+ "placeholder.com"
14915
+ ]);
14916
+ function isSafeEmail(value, extraDomains) {
14917
+ const match = value.match(/@([a-zA-Z0-9.-]+)$/);
14918
+ if (!match) return false;
14919
+ const domain2 = match[1].toLowerCase();
14920
+ if (PII_SAFE_EMAIL_DOMAINS.has(domain2)) return true;
14921
+ if (extraDomains && extraDomains.has(domain2)) return true;
14922
+ return false;
14923
+ }
14840
14924
  function deduplicateFindings(findings) {
14841
14925
  const seen = /* @__PURE__ */ new Set();
14842
14926
  return findings.filter((f) => {
@@ -14850,8 +14934,8 @@ function deduplicateFindings(findings) {
14850
14934
  // src/engine/pruner.ts
14851
14935
  import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
14852
14936
  import { readFile as readFile4 } from "fs/promises";
14853
- import { existsSync as existsSync2 } from "fs";
14854
- import { join as join4 } from "path";
14937
+ import { existsSync as existsSync3 } from "fs";
14938
+ import { join as join5 } from "path";
14855
14939
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
14856
14940
  async function pruneFile(file2, level) {
14857
14941
  if (level === "excluded") {
@@ -15097,9 +15181,9 @@ function addJSDoc(node, parts) {
15097
15181
  function findTsConfig(filePath) {
15098
15182
  let dir = filePath;
15099
15183
  for (let i = 0; i < 10; i++) {
15100
- dir = join4(dir, "..");
15101
- const candidate = join4(dir, "tsconfig.json");
15102
- if (existsSync2(candidate)) return candidate;
15184
+ dir = join5(dir, "..");
15185
+ const candidate = join5(dir, "tsconfig.json");
15186
+ if (existsSync3(candidate)) return candidate;
15103
15187
  }
15104
15188
  return void 0;
15105
15189
  }
@@ -15366,7 +15450,7 @@ async function selectContext(input) {
15366
15450
  );
15367
15451
  const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
15368
15452
  const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
15369
- const hash2 = createHash3("sha256").update(hashInput).digest("hex").substring(0, 16);
15453
+ const hash2 = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
15370
15454
  return {
15371
15455
  files: selectedFiles,
15372
15456
  totalTokens: usedTokens,
@@ -16866,25 +16950,25 @@ function fmt2(n) {
16866
16950
  }
16867
16951
 
16868
16952
  // src/engine/config.ts
16869
- import { readFile as readFile6, writeFile, mkdir } from "fs/promises";
16870
- import { join as join5 } from "path";
16871
- import { existsSync as existsSync3 } from "fs";
16953
+ import { readFile as readFile6, writeFile as writeFile2, mkdir } from "fs/promises";
16954
+ import { join as join6 } from "path";
16955
+ import { existsSync as existsSync4 } from "fs";
16872
16956
  import { parse as parseYAML, stringify as stringifyYAML } from "yaml";
16873
16957
  var CONFIG_DIR = ".cto";
16874
16958
  var CONFIG_FILE = "config.yml";
16875
16959
  var POLICY_FILE = "policy.yml";
16876
16960
  function getConfigPath(projectPath) {
16877
- return join5(projectPath, CONFIG_DIR, CONFIG_FILE);
16961
+ return join6(projectPath, CONFIG_DIR, CONFIG_FILE);
16878
16962
  }
16879
16963
  function getPolicyPath(projectPath) {
16880
- return join5(projectPath, CONFIG_DIR, POLICY_FILE);
16964
+ return join6(projectPath, CONFIG_DIR, POLICY_FILE);
16881
16965
  }
16882
16966
  function getCTODir(projectPath) {
16883
- return join5(projectPath, CONFIG_DIR);
16967
+ return join6(projectPath, CONFIG_DIR);
16884
16968
  }
16885
16969
  async function loadConfig(projectPath) {
16886
16970
  const configPath = getConfigPath(projectPath);
16887
- if (!existsSync3(configPath)) {
16971
+ if (!existsSync4(configPath)) {
16888
16972
  return { ...DEFAULT_CONFIG };
16889
16973
  }
16890
16974
  try {
@@ -16900,20 +16984,20 @@ async function saveConfig(projectPath, config2) {
16900
16984
  await mkdir(ctoDir, { recursive: true });
16901
16985
  const configPath = getConfigPath(projectPath);
16902
16986
  const yamlContent = stringifyYAML(config2, { indent: 2 });
16903
- await writeFile(configPath, yamlContent, "utf-8");
16987
+ await writeFile2(configPath, yamlContent, "utf-8");
16904
16988
  return configPath;
16905
16989
  }
16906
16990
  async function initProjectConfig(projectPath) {
16907
16991
  const ctoDir = getCTODir(projectPath);
16908
- await mkdir(join5(ctoDir, "snapshots"), { recursive: true });
16992
+ await mkdir(join6(ctoDir, "snapshots"), { recursive: true });
16909
16993
  const created = [];
16910
16994
  const configPath = getConfigPath(projectPath);
16911
- if (!existsSync3(configPath)) {
16995
+ if (!existsSync4(configPath)) {
16912
16996
  await saveConfig(projectPath, DEFAULT_CONFIG);
16913
16997
  created.push(configPath);
16914
16998
  }
16915
16999
  const policyPath = getPolicyPath(projectPath);
16916
- if (!existsSync3(policyPath)) {
17000
+ if (!existsSync4(policyPath)) {
16917
17001
  const defaultPolicy = {
16918
17002
  version: "1.0",
16919
17003
  name: "default",
@@ -16956,14 +17040,14 @@ async function initProjectConfig(projectPath) {
16956
17040
  ]
16957
17041
  };
16958
17042
  const yamlContent = stringifyYAML(defaultPolicy, { indent: 2 });
16959
- await writeFile(policyPath, yamlContent, "utf-8");
17043
+ await writeFile2(policyPath, yamlContent, "utf-8");
16960
17044
  created.push(policyPath);
16961
17045
  }
16962
17046
  return { configPath, policyPath, created };
16963
17047
  }
16964
17048
  async function loadPolicyFromYAML(projectPath) {
16965
17049
  const policyPath = getPolicyPath(projectPath);
16966
- if (!existsSync3(policyPath)) return null;
17050
+ if (!existsSync4(policyPath)) return null;
16967
17051
  try {
16968
17052
  const raw = await readFile6(policyPath, "utf-8");
16969
17053
  return parseYAML(raw);
@@ -17031,20 +17115,20 @@ function formatCost(cost) {
17031
17115
  }
17032
17116
 
17033
17117
  // src/govern/audit.ts
17034
- import { randomUUID, createHash as createHash4 } from "crypto";
17118
+ import { randomUUID, createHash as createHash5 } from "crypto";
17035
17119
  import { readdir as readdir3, chmod } from "fs/promises";
17036
- import { join as join6 } from "path";
17120
+ import { join as join7 } from "path";
17037
17121
  import { userInfo } from "os";
17038
17122
  import { homedir } from "os";
17039
17123
  var CTO_DIR = ".cto-ai";
17040
17124
  var AUDIT_DIR = "audit";
17041
17125
  var MAX_ENTRIES_PER_FILE = 500;
17042
17126
  function getAuditDir() {
17043
- return join6(homedir(), CTO_DIR, AUDIT_DIR);
17127
+ return join7(homedir(), CTO_DIR, AUDIT_DIR);
17044
17128
  }
17045
17129
  function getCurrentAuditFile() {
17046
17130
  const date5 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
17047
- return join6(getAuditDir(), `audit_${date5}.json`);
17131
+ return join7(getAuditDir(), `audit_${date5}.json`);
17048
17132
  }
17049
17133
  function computeIntegrityHash(entry) {
17050
17134
  const payload = JSON.stringify({
@@ -17055,7 +17139,7 @@ function computeIntegrityHash(entry) {
17055
17139
  projectPath: entry.projectPath,
17056
17140
  details: entry.details
17057
17141
  });
17058
- return createHash4("sha256").update(payload).digest("hex");
17142
+ return createHash5("sha256").update(payload).digest("hex");
17059
17143
  }
17060
17144
  async function ensureDir(dirPath) {
17061
17145
  const { mkdir: mkdir3 } = await import("fs/promises");
@@ -17071,9 +17155,9 @@ async function readJSON(filePath) {
17071
17155
  }
17072
17156
  }
17073
17157
  async function writeJSON(filePath, data) {
17074
- const { writeFile: writeFile3 } = await import("fs/promises");
17075
- await ensureDir(join6(filePath, ".."));
17076
- await writeFile3(filePath, JSON.stringify(data, null, 2), "utf-8");
17158
+ const { writeFile: writeFile4 } = await import("fs/promises");
17159
+ await ensureDir(join7(filePath, ".."));
17160
+ await writeFile4(filePath, JSON.stringify(data, null, 2), "utf-8");
17077
17161
  }
17078
17162
  async function logAudit(action, projectPath, details = {}) {
17079
17163
  const auditDir = getAuditDir();
@@ -17122,7 +17206,7 @@ async function getAuditEntries(options = {}) {
17122
17206
  const limit = options.limit ?? 100;
17123
17207
  for (const file2 of auditFiles) {
17124
17208
  if (allEntries.length >= limit) break;
17125
- const entries = await readJSON(join6(auditDir, file2));
17209
+ const entries = await readJSON(join7(auditDir, file2));
17126
17210
  if (!entries) continue;
17127
17211
  for (const entry of entries.reverse()) {
17128
17212
  if (allEntries.length >= limit) break;
@@ -17373,7 +17457,7 @@ function fileMatchesCategory(path, category) {
17373
17457
  }
17374
17458
 
17375
17459
  // src/govern/snapshot.ts
17376
- import { randomUUID as randomUUID3, createHash as createHash5 } from "crypto";
17460
+ import { randomUUID as randomUUID3, createHash as createHash6 } from "crypto";
17377
17461
  import "fs/promises";
17378
17462
  function createSnapshot(name, analysis, selection, metadata = {}) {
17379
17463
  const files = selection.files.map((f) => ({
@@ -17466,13 +17550,13 @@ function compareSnapshots(older, newer) {
17466
17550
  };
17467
17551
  }
17468
17552
  function hashString(input) {
17469
- return createHash5("sha256").update(input).digest("hex").substring(0, 16);
17553
+ return createHash6("sha256").update(input).digest("hex").substring(0, 16);
17470
17554
  }
17471
17555
 
17472
17556
  // src/mcp/v2.ts
17473
17557
  var SNAPSHOTS_DIR = ".cto/snapshots";
17474
17558
  async function loadSnapshotFile(projectPath, name) {
17475
- const filePath = join7(projectPath, SNAPSHOTS_DIR, `${name}.json`);
17559
+ const filePath = join8(projectPath, SNAPSHOTS_DIR, `${name}.json`);
17476
17560
  const content = await readFile8(filePath, "utf-8");
17477
17561
  return JSON.parse(content);
17478
17562
  }
@@ -17876,10 +17960,10 @@ server.tool(
17876
17960
  budget: budget ?? config2.interaction.defaultBudget,
17877
17961
  createdBy: process.env.USER ?? "unknown"
17878
17962
  });
17879
- const dir = join7(absPath, SNAPSHOTS_DIR);
17963
+ const dir = join8(absPath, SNAPSHOTS_DIR);
17880
17964
  await mkdir2(dir, { recursive: true });
17881
- const filePath = join7(dir, `${name}.json`);
17882
- await writeFile2(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
17965
+ const filePath = join8(dir, `${name}.json`);
17966
+ await writeFile3(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
17883
17967
  return {
17884
17968
  content: [{
17885
17969
  type: "text",