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.
@@ -1360,11 +1360,13 @@ function emptyResult(baseBranch) {
1360
1360
  }
1361
1361
 
1362
1362
  // src/engine/selector.ts
1363
- import { createHash as createHash3 } from "crypto";
1363
+ import { createHash as createHash4 } from "crypto";
1364
1364
 
1365
1365
  // src/govern/secrets.ts
1366
1366
  import { readFile as readFile4 } from "fs/promises";
1367
- import { resolve as resolve6, relative as relative4 } from "path";
1367
+ import { readFileSync, existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
1368
+ import { resolve as resolve6, relative as relative4, join as join5, dirname as dirname2 } from "path";
1369
+ import { createHash as createHash3 } from "crypto";
1368
1370
  var BUILTIN_PATTERNS = [
1369
1371
  // API Keys
1370
1372
  { type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
@@ -1389,15 +1391,66 @@ var BUILTIN_PATTERNS = [
1389
1391
  { type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
1390
1392
  { type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
1391
1393
  // Environment variables with secrets
1392
- { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
1394
+ { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
1395
+ // Stripe
1396
+ { type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
1397
+ { type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
1398
+ { type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
1399
+ // Slack
1400
+ { type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
1401
+ { type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
1402
+ { 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" },
1403
+ // Google
1404
+ { type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
1405
+ { type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
1406
+ // Azure
1407
+ { type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
1408
+ // Twilio
1409
+ { type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
1410
+ // SendGrid
1411
+ { type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
1412
+ // JWT
1413
+ { 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" },
1414
+ // Datadog
1415
+ { type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
1416
+ { type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
1417
+ // Sentry
1418
+ { type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
1419
+ // Firebase
1420
+ { type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
1421
+ { type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
1422
+ // Supabase
1423
+ { type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
1424
+ { type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
1425
+ // Vercel
1426
+ { type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
1427
+ // Heroku
1428
+ { type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
1429
+ // DigitalOcean
1430
+ { type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
1431
+ { type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
1432
+ // Mailgun
1433
+ { type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
1434
+ // PII
1435
+ { 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)" },
1436
+ { 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)" }
1393
1437
  ];
1438
+ var _cachedBuiltinPatterns = null;
1439
+ function getBuiltinPatterns() {
1440
+ if (!_cachedBuiltinPatterns) {
1441
+ _cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
1442
+ type: def.type,
1443
+ pattern: new RegExp(def.source, def.flags),
1444
+ severity: def.severity,
1445
+ description: def.description
1446
+ }));
1447
+ }
1448
+ return _cachedBuiltinPatterns;
1449
+ }
1394
1450
  function buildPatterns(customPatterns = []) {
1395
- const patterns = BUILTIN_PATTERNS.map((def) => ({
1396
- type: def.type,
1397
- pattern: new RegExp(def.source, def.flags),
1398
- severity: def.severity,
1399
- description: def.description
1400
- }));
1451
+ const builtins = getBuiltinPatterns();
1452
+ if (customPatterns.length === 0) return builtins;
1453
+ const patterns = [...builtins];
1401
1454
  for (const custom of customPatterns) {
1402
1455
  try {
1403
1456
  patterns.push({
@@ -1411,7 +1464,7 @@ function buildPatterns(customPatterns = []) {
1411
1464
  }
1412
1465
  return patterns;
1413
1466
  }
1414
- function scanContentForSecrets(content, filePath, customPatterns = []) {
1467
+ function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
1415
1468
  const findings = [];
1416
1469
  const lines = content.split("\n");
1417
1470
  const allPatterns = buildPatterns(customPatterns);
@@ -1423,6 +1476,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
1423
1476
  while ((match = secretPattern.pattern.exec(line)) !== null) {
1424
1477
  const matchText = match[0];
1425
1478
  if (isTemplateOrPlaceholder(matchText)) continue;
1479
+ if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
1426
1480
  findings.push({
1427
1481
  type: secretPattern.type,
1428
1482
  file: filePath,
@@ -1470,6 +1524,36 @@ function isTemplateOrPlaceholder(value) {
1470
1524
  ];
1471
1525
  return placeholders.some((p) => p.test(value));
1472
1526
  }
1527
+ var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
1528
+ "example.com",
1529
+ "example.org",
1530
+ "example.net",
1531
+ "test.com",
1532
+ "test.org",
1533
+ "test.net",
1534
+ "localhost",
1535
+ "localhost.localdomain",
1536
+ "email.com",
1537
+ "mail.com",
1538
+ "foo.com",
1539
+ "bar.com",
1540
+ "baz.com",
1541
+ "acme.com",
1542
+ "company.com",
1543
+ "corp.com",
1544
+ "noreply.com",
1545
+ "no-reply.com",
1546
+ "users.noreply.github.com",
1547
+ "placeholder.com"
1548
+ ]);
1549
+ function isSafeEmail(value, extraDomains) {
1550
+ const match = value.match(/@([a-zA-Z0-9.-]+)$/);
1551
+ if (!match) return false;
1552
+ const domain = match[1].toLowerCase();
1553
+ if (PII_SAFE_EMAIL_DOMAINS.has(domain)) return true;
1554
+ if (extraDomains && extraDomains.has(domain)) return true;
1555
+ return false;
1556
+ }
1473
1557
  function deduplicateFindings(findings) {
1474
1558
  const seen = /* @__PURE__ */ new Set();
1475
1559
  return findings.filter((f) => {
@@ -1483,8 +1567,8 @@ function deduplicateFindings(findings) {
1483
1567
  // src/engine/pruner.ts
1484
1568
  import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
1485
1569
  import { readFile as readFile5 } from "fs/promises";
1486
- import { existsSync as existsSync3 } from "fs";
1487
- import { join as join5 } from "path";
1570
+ import { existsSync as existsSync4 } from "fs";
1571
+ import { join as join6 } from "path";
1488
1572
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
1489
1573
  async function pruneFile(file, level) {
1490
1574
  if (level === "excluded") {
@@ -1739,9 +1823,9 @@ function addJSDoc(node, parts) {
1739
1823
  function findTsConfig(filePath) {
1740
1824
  let dir = filePath;
1741
1825
  for (let i = 0; i < 10; i++) {
1742
- dir = join5(dir, "..");
1743
- const candidate = join5(dir, "tsconfig.json");
1744
- if (existsSync3(candidate)) return candidate;
1826
+ dir = join6(dir, "..");
1827
+ const candidate = join6(dir, "tsconfig.json");
1828
+ if (existsSync4(candidate)) return candidate;
1745
1829
  }
1746
1830
  return void 0;
1747
1831
  }
@@ -2020,7 +2104,7 @@ async function selectContext(input) {
2020
2104
  );
2021
2105
  const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
2022
2106
  const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
2023
- const hash = createHash3("sha256").update(hashInput).digest("hex").substring(0, 16);
2107
+ const hash = createHash4("sha256").update(hashInput).digest("hex").substring(0, 16);
2024
2108
  return {
2025
2109
  files: selectedFiles,
2026
2110
  totalTokens: usedTokens,
@@ -3168,8 +3252,8 @@ function fmt2(n) {
3168
3252
  }
3169
3253
 
3170
3254
  // src/engine/predictor.ts
3171
- import { resolve as resolve8, join as join6 } from "path";
3172
- import { readFile as readFile7, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3255
+ import { resolve as resolve8, join as join7 } from "path";
3256
+ import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
3173
3257
  var DEFAULT_PREDICTOR_CONFIG = {
3174
3258
  maxCoSelectionPairs: 500,
3175
3259
  decayFactor: 0.95,
@@ -3178,9 +3262,9 @@ var DEFAULT_PREDICTOR_CONFIG = {
3178
3262
  // need at least 2 observations before predicting
3179
3263
  };
3180
3264
  async function getModelPath(projectPath) {
3181
- const ctoDir = join6(resolve8(projectPath), ".cto");
3265
+ const ctoDir = join7(resolve8(projectPath), ".cto");
3182
3266
  await mkdir2(ctoDir, { recursive: true });
3183
- return join6(ctoDir, "predictor.json");
3267
+ return join7(ctoDir, "predictor.json");
3184
3268
  }
3185
3269
  async function loadModel(projectPath) {
3186
3270
  try {
@@ -3193,7 +3277,7 @@ async function loadModel(projectPath) {
3193
3277
  }
3194
3278
  async function saveModel(projectPath, model) {
3195
3279
  const path = await getModelPath(projectPath);
3196
- await writeFile2(path, JSON.stringify(model, null, 2));
3280
+ await writeFile3(path, JSON.stringify(model, null, 2));
3197
3281
  }
3198
3282
  function createEmptyModel() {
3199
3283
  return {
@@ -3430,8 +3514,8 @@ function pruneCoSelection(model, maxPairs) {
3430
3514
  }
3431
3515
 
3432
3516
  // src/engine/cross-repo.ts
3433
- import { join as join7, basename as basename3 } from "path";
3434
- import { readFile as readFile8, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3517
+ import { join as join8, basename as basename3 } from "path";
3518
+ import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
3435
3519
  function computeFingerprint2(analysis) {
3436
3520
  const totalFiles = analysis.totalFiles;
3437
3521
  const sizeClass = totalFiles < 20 ? "tiny" : totalFiles < 100 ? "small" : totalFiles < 500 ? "medium" : totalFiles < 2e3 ? "large" : "huge";
@@ -3463,7 +3547,7 @@ function fingerprintHash(fp) {
3463
3547
  }
3464
3548
  function getGlobalModelPath() {
3465
3549
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
3466
- return join7(home, ".cto", "global-intelligence.json");
3550
+ return join8(home, ".cto", "global-intelligence.json");
3467
3551
  }
3468
3552
  async function loadGlobalModel() {
3469
3553
  try {
@@ -3474,9 +3558,9 @@ async function loadGlobalModel() {
3474
3558
  }
3475
3559
  }
3476
3560
  async function saveGlobalModel(model) {
3477
- const dir = join7(getGlobalModelPath(), "..");
3561
+ const dir = join8(getGlobalModelPath(), "..");
3478
3562
  await mkdir3(dir, { recursive: true });
3479
- await writeFile3(getGlobalModelPath(), JSON.stringify(model, null, 2));
3563
+ await writeFile4(getGlobalModelPath(), JSON.stringify(model, null, 2));
3480
3564
  }
3481
3565
  function createEmptyModel2() {
3482
3566
  return {
@@ -3695,17 +3779,17 @@ function getCrossRepoStats(model) {
3695
3779
  }
3696
3780
 
3697
3781
  // src/engine/feedback.ts
3698
- import { resolve as resolve10, join as join8 } from "path";
3699
- import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
3782
+ import { resolve as resolve10, join as join9 } from "path";
3783
+ import { readFile as readFile9, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
3700
3784
  async function getFeedbackPath(projectPath) {
3701
- const ctoDir = join8(resolve10(projectPath), ".cto");
3785
+ const ctoDir = join9(resolve10(projectPath), ".cto");
3702
3786
  await mkdir4(ctoDir, { recursive: true });
3703
- return join8(ctoDir, "feedback.json");
3787
+ return join9(ctoDir, "feedback.json");
3704
3788
  }
3705
3789
  async function getModelPath2(projectPath) {
3706
- const ctoDir = join8(resolve10(projectPath), ".cto");
3790
+ const ctoDir = join9(resolve10(projectPath), ".cto");
3707
3791
  await mkdir4(ctoDir, { recursive: true });
3708
- return join8(ctoDir, "feedback-model.json");
3792
+ return join9(ctoDir, "feedback-model.json");
3709
3793
  }
3710
3794
  async function loadFeedback(projectPath) {
3711
3795
  try {
@@ -3716,7 +3800,7 @@ async function loadFeedback(projectPath) {
3716
3800
  }
3717
3801
  }
3718
3802
  async function saveFeedback(projectPath, entries) {
3719
- await writeFile4(await getFeedbackPath(projectPath), JSON.stringify(entries, null, 2));
3803
+ await writeFile5(await getFeedbackPath(projectPath), JSON.stringify(entries, null, 2));
3720
3804
  }
3721
3805
  async function loadFeedbackModel(projectPath) {
3722
3806
  try {
@@ -3727,7 +3811,7 @@ async function loadFeedbackModel(projectPath) {
3727
3811
  }
3728
3812
  }
3729
3813
  async function saveFeedbackModel(projectPath, model) {
3730
- await writeFile4(await getModelPath2(projectPath), JSON.stringify(model, null, 2));
3814
+ await writeFile5(await getModelPath2(projectPath), JSON.stringify(model, null, 2));
3731
3815
  }
3732
3816
  function createEmptyModel3() {
3733
3817
  return {
@@ -4402,8 +4486,8 @@ function fmt3(n) {
4402
4486
  }
4403
4487
 
4404
4488
  // src/engine/compile-proof.ts
4405
- import { resolve as resolve11, join as join9, dirname as dirname2, basename as basename4 } from "path";
4406
- import { writeFile as writeFile5, mkdir as mkdir5, rm, cp } from "fs/promises";
4489
+ import { resolve as resolve11, join as join10, dirname as dirname3, basename as basename4 } from "path";
4490
+ import { writeFile as writeFile6, mkdir as mkdir5, rm, cp } from "fs/promises";
4407
4491
  import { execSync } from "child_process";
4408
4492
  async function runCompileProof(analysis, task, budget = 5e4) {
4409
4493
  const projectPath = resolve11(analysis.projectPath);
@@ -4604,21 +4688,21 @@ function extractKnownTypes(analysis, typePaths) {
4604
4688
  return types;
4605
4689
  }
4606
4690
  async function runTscWithContext(name, projectPath, selectedPaths, tokensUsed, typePaths, consumerCode) {
4607
- const tmpDir = join9(projectPath, ".cto", `compile-proof-${name.toLowerCase()}`);
4691
+ const tmpDir = join10(projectPath, ".cto", `compile-proof-${name.toLowerCase()}`);
4608
4692
  try {
4609
4693
  await rm(tmpDir, { recursive: true, force: true });
4610
4694
  await mkdir5(tmpDir, { recursive: true });
4611
4695
  for (const filePath of selectedPaths) {
4612
- const src = join9(projectPath, filePath);
4613
- const dest = join9(tmpDir, filePath);
4696
+ const src = join10(projectPath, filePath);
4697
+ const dest = join10(tmpDir, filePath);
4614
4698
  try {
4615
- await mkdir5(dirname2(dest), { recursive: true });
4699
+ await mkdir5(dirname3(dest), { recursive: true });
4616
4700
  await cp(src, dest);
4617
4701
  } catch {
4618
4702
  }
4619
4703
  }
4620
- await writeFile5(join9(tmpDir, "_compile_test.ts"), consumerCode);
4621
- await writeFile5(join9(tmpDir, "tsconfig.json"), JSON.stringify({
4704
+ await writeFile6(join10(tmpDir, "_compile_test.ts"), consumerCode);
4705
+ await writeFile6(join10(tmpDir, "tsconfig.json"), JSON.stringify({
4622
4706
  compilerOptions: {
4623
4707
  target: "ES2022",
4624
4708
  module: "nodenext",
@@ -4923,9 +5007,541 @@ function fmt5(n) {
4923
5007
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
4924
5008
  return n.toString();
4925
5009
  }
5010
+
5011
+ // src/engine/monorepo.ts
5012
+ import { readFile as readFile11, readdir as readdir4 } from "fs/promises";
5013
+ import { join as join11, relative as relative6, basename as basename5 } from "path";
5014
+ import { existsSync as existsSync5 } from "fs";
5015
+ async function detectMonorepoTool(rootPath) {
5016
+ const checks = [
5017
+ { file: "nx.json", tool: "nx" },
5018
+ { file: "turbo.json", tool: "turborepo" },
5019
+ { file: "lerna.json", tool: "lerna" },
5020
+ { file: "pnpm-workspace.yaml", tool: "pnpm-workspaces" },
5021
+ {
5022
+ file: "package.json",
5023
+ tool: "npm-workspaces",
5024
+ validate: (content) => {
5025
+ try {
5026
+ const pkg = JSON.parse(content);
5027
+ return Array.isArray(pkg.workspaces) || typeof pkg.workspaces?.packages !== "undefined";
5028
+ } catch {
5029
+ return false;
5030
+ }
5031
+ }
5032
+ }
5033
+ ];
5034
+ for (const check of checks) {
5035
+ const filePath = join11(rootPath, check.file);
5036
+ if (existsSync5(filePath)) {
5037
+ if (!check.validate) return check.tool;
5038
+ try {
5039
+ const content = await readFile11(filePath, "utf-8");
5040
+ if (check.validate(content)) {
5041
+ if (check.tool === "npm-workspaces") {
5042
+ if (existsSync5(join11(rootPath, "yarn.lock"))) return "yarn-workspaces";
5043
+ return "npm-workspaces";
5044
+ }
5045
+ return check.tool;
5046
+ }
5047
+ } catch {
5048
+ }
5049
+ }
5050
+ }
5051
+ return "none";
5052
+ }
5053
+ async function resolveWorkspaceGlobs(rootPath, globs) {
5054
+ const packagePaths = [];
5055
+ for (const glob of globs) {
5056
+ const cleanGlob = glob.replace(/\/?\*\*?$/, "");
5057
+ const searchDir = join11(rootPath, cleanGlob);
5058
+ if (!existsSync5(searchDir)) continue;
5059
+ try {
5060
+ const entries = await readdir4(searchDir, { withFileTypes: true });
5061
+ for (const entry of entries) {
5062
+ if (!entry.isDirectory()) continue;
5063
+ const pkgJsonPath = join11(searchDir, entry.name, "package.json");
5064
+ if (existsSync5(pkgJsonPath)) {
5065
+ packagePaths.push(join11(searchDir, entry.name));
5066
+ }
5067
+ }
5068
+ } catch {
5069
+ }
5070
+ }
5071
+ return packagePaths;
5072
+ }
5073
+ async function discoverPackages(rootPath, tool) {
5074
+ switch (tool) {
5075
+ case "npm-workspaces":
5076
+ case "yarn-workspaces": {
5077
+ const pkgJson = JSON.parse(await readFile11(join11(rootPath, "package.json"), "utf-8"));
5078
+ const workspaces = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces?.packages || [];
5079
+ return resolveWorkspaceGlobs(rootPath, workspaces);
5080
+ }
5081
+ case "pnpm-workspaces": {
5082
+ const content = await readFile11(join11(rootPath, "pnpm-workspace.yaml"), "utf-8");
5083
+ const packages = [];
5084
+ let inPackages = false;
5085
+ for (const line of content.split("\n")) {
5086
+ const trimmed = line.trim();
5087
+ if (trimmed === "packages:") {
5088
+ inPackages = true;
5089
+ continue;
5090
+ }
5091
+ if (inPackages && trimmed.startsWith("- ")) {
5092
+ packages.push(trimmed.slice(2).replace(/['"]/g, ""));
5093
+ } else if (inPackages && !trimmed.startsWith("-") && trimmed.length > 0) {
5094
+ inPackages = false;
5095
+ }
5096
+ }
5097
+ return resolveWorkspaceGlobs(rootPath, packages);
5098
+ }
5099
+ case "turborepo": {
5100
+ const pkgJson = JSON.parse(await readFile11(join11(rootPath, "package.json"), "utf-8"));
5101
+ const workspaces = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces?.packages || [];
5102
+ if (workspaces.length > 0) return resolveWorkspaceGlobs(rootPath, workspaces);
5103
+ if (existsSync5(join11(rootPath, "pnpm-workspace.yaml"))) {
5104
+ return discoverPackages(rootPath, "pnpm-workspaces");
5105
+ }
5106
+ return [];
5107
+ }
5108
+ case "nx": {
5109
+ const standardDirs = ["packages", "apps", "libs"];
5110
+ const globs = standardDirs.filter((d) => existsSync5(join11(rootPath, d)));
5111
+ if (globs.length > 0) return resolveWorkspaceGlobs(rootPath, globs);
5112
+ try {
5113
+ const pkgJson = JSON.parse(await readFile11(join11(rootPath, "package.json"), "utf-8"));
5114
+ const workspaces = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : [];
5115
+ if (workspaces.length > 0) return resolveWorkspaceGlobs(rootPath, workspaces);
5116
+ } catch {
5117
+ }
5118
+ return [];
5119
+ }
5120
+ case "lerna": {
5121
+ const lernaJson = JSON.parse(await readFile11(join11(rootPath, "lerna.json"), "utf-8"));
5122
+ const packages = lernaJson.packages || ["packages/*"];
5123
+ return resolveWorkspaceGlobs(rootPath, packages);
5124
+ }
5125
+ default:
5126
+ return [];
5127
+ }
5128
+ }
5129
+ function buildCrossPackageEdges(packages, allFiles, graphEdges, rootPath) {
5130
+ const fileToPackage = /* @__PURE__ */ new Map();
5131
+ for (const pkg of packages) {
5132
+ const pkgRel = relative6(rootPath, pkg.path);
5133
+ for (const f of allFiles) {
5134
+ if (f.relativePath.startsWith(pkgRel + "/") || f.relativePath.startsWith(pkgRel + "\\")) {
5135
+ fileToPackage.set(f.relativePath, pkg.name);
5136
+ }
5137
+ }
5138
+ }
5139
+ const edgeMap = /* @__PURE__ */ new Map();
5140
+ for (const edge of graphEdges) {
5141
+ const fromPkg = fileToPackage.get(edge.from);
5142
+ const toPkg = fileToPackage.get(edge.to);
5143
+ if (fromPkg && toPkg && fromPkg !== toPkg) {
5144
+ const key = `${fromPkg}\u2192${toPkg}`;
5145
+ if (!edgeMap.has(key)) {
5146
+ edgeMap.set(key, { files: /* @__PURE__ */ new Set(), type: "dependency" });
5147
+ }
5148
+ edgeMap.get(key).files.add(edge.from);
5149
+ }
5150
+ }
5151
+ return Array.from(edgeMap.entries()).map(([key, val]) => {
5152
+ const [from, to] = key.split("\u2192");
5153
+ return { from, to, files: val.files.size, type: val.type };
5154
+ });
5155
+ }
5156
+ async function analyzeMonorepo(rootPath, analysis) {
5157
+ const tool = await detectMonorepoTool(rootPath);
5158
+ if (tool === "none") {
5159
+ return {
5160
+ detected: false,
5161
+ tool: "none",
5162
+ rootPath,
5163
+ packages: [],
5164
+ sharedPackages: [],
5165
+ crossPackageEdges: [],
5166
+ isolationScore: 100,
5167
+ totalTokens: analysis?.totalTokens ?? 0,
5168
+ packageTokenMap: {}
5169
+ };
5170
+ }
5171
+ const packagePaths = await discoverPackages(rootPath, tool);
5172
+ const packages = [];
5173
+ const packageTokenMap = {};
5174
+ for (const pkgPath of packagePaths) {
5175
+ const pkgJsonPath = join11(pkgPath, "package.json");
5176
+ let name = basename5(pkgPath);
5177
+ let pkgDeps = [];
5178
+ try {
5179
+ const pkgJson = JSON.parse(await readFile11(pkgJsonPath, "utf-8"));
5180
+ name = pkgJson.name || name;
5181
+ const allDeps = {
5182
+ ...pkgJson.dependencies || {},
5183
+ ...pkgJson.devDependencies || {},
5184
+ ...pkgJson.peerDependencies || {}
5185
+ };
5186
+ pkgDeps = Object.keys(allDeps);
5187
+ } catch {
5188
+ }
5189
+ const relPath = relative6(rootPath, pkgPath);
5190
+ let fileCount = 0;
5191
+ let tokenCount = 0;
5192
+ const entryPoints = [];
5193
+ if (analysis) {
5194
+ for (const f of analysis.files) {
5195
+ if (f.relativePath.startsWith(relPath + "/") || f.relativePath.startsWith(relPath + "\\")) {
5196
+ fileCount++;
5197
+ tokenCount += f.tokens;
5198
+ if (f.kind === "entry") entryPoints.push(f.relativePath);
5199
+ }
5200
+ }
5201
+ }
5202
+ packageTokenMap[name] = tokenCount;
5203
+ packages.push({
5204
+ name,
5205
+ path: pkgPath,
5206
+ relativePath: relPath,
5207
+ files: fileCount,
5208
+ tokens: tokenCount,
5209
+ dependencies: [],
5210
+ // Filled below after we know all package names
5211
+ dependents: [],
5212
+ isShared: false,
5213
+ entryPoints
5214
+ });
5215
+ }
5216
+ const pkgNames = new Set(packages.map((p) => p.name));
5217
+ for (const pkg of packages) {
5218
+ const pkgJsonPath = join11(pkg.path, "package.json");
5219
+ try {
5220
+ const pkgJson = JSON.parse(await readFile11(pkgJsonPath, "utf-8"));
5221
+ const allDeps = {
5222
+ ...pkgJson.dependencies || {},
5223
+ ...pkgJson.devDependencies || {}
5224
+ };
5225
+ pkg.dependencies = Object.keys(allDeps).filter((d) => pkgNames.has(d));
5226
+ } catch {
5227
+ }
5228
+ }
5229
+ for (const pkg of packages) {
5230
+ for (const depName of pkg.dependencies) {
5231
+ const dep = packages.find((p) => p.name === depName);
5232
+ if (dep) dep.dependents.push(pkg.name);
5233
+ }
5234
+ }
5235
+ for (const pkg of packages) {
5236
+ pkg.isShared = pkg.dependents.length >= 2;
5237
+ }
5238
+ const sharedPackages = packages.filter((p) => p.isShared);
5239
+ const crossPackageEdges = analysis ? buildCrossPackageEdges(packages, analysis.files, analysis.graph.edges, rootPath) : [];
5240
+ const maxPossibleEdges = packages.length * (packages.length - 1);
5241
+ const actualEdges = crossPackageEdges.length;
5242
+ const isolationScore = maxPossibleEdges > 0 ? Math.round(100 * (1 - actualEdges / maxPossibleEdges)) : 100;
5243
+ return {
5244
+ detected: true,
5245
+ tool,
5246
+ rootPath,
5247
+ packages,
5248
+ sharedPackages,
5249
+ crossPackageEdges,
5250
+ isolationScore,
5251
+ totalTokens: analysis?.totalTokens ?? packages.reduce((s, p) => s + p.tokens, 0),
5252
+ packageTokenMap
5253
+ };
5254
+ }
5255
+ function selectPackageContext(monorepo, targetPackage) {
5256
+ const target = monorepo.packages.find((p) => p.name === targetPackage || p.relativePath === targetPackage);
5257
+ if (!target) {
5258
+ return {
5259
+ targetPackage,
5260
+ includedPackages: [],
5261
+ excludedPackages: monorepo.packages.map((p) => p.name),
5262
+ originalTokens: monorepo.totalTokens,
5263
+ optimizedTokens: 0,
5264
+ savedTokens: monorepo.totalTokens,
5265
+ savedPercent: 100
5266
+ };
5267
+ }
5268
+ const includedNames = /* @__PURE__ */ new Set([target.name]);
5269
+ for (const depName of target.dependencies) {
5270
+ includedNames.add(depName);
5271
+ const dep = monorepo.packages.find((p) => p.name === depName);
5272
+ if (dep) {
5273
+ for (const transDep of dep.dependencies) {
5274
+ const transDepPkg = monorepo.packages.find((p) => p.name === transDep);
5275
+ if (transDepPkg?.isShared) includedNames.add(transDep);
5276
+ }
5277
+ }
5278
+ }
5279
+ const includedPackages = Array.from(includedNames);
5280
+ const excludedPackages = monorepo.packages.filter((p) => !includedNames.has(p.name)).map((p) => p.name);
5281
+ const optimizedTokens = monorepo.packages.filter((p) => includedNames.has(p.name)).reduce((s, p) => s + p.tokens, 0);
5282
+ const savedTokens = monorepo.totalTokens - optimizedTokens;
5283
+ const savedPercent = monorepo.totalTokens > 0 ? Math.round(savedTokens / monorepo.totalTokens * 100) : 0;
5284
+ return {
5285
+ targetPackage: target.name,
5286
+ includedPackages,
5287
+ excludedPackages,
5288
+ originalTokens: monorepo.totalTokens,
5289
+ optimizedTokens,
5290
+ savedTokens,
5291
+ savedPercent
5292
+ };
5293
+ }
5294
+ function renderMonorepoAnalysis(mono) {
5295
+ if (!mono.detected) {
5296
+ return " \u2139\uFE0F No monorepo detected (single-package project).\n";
5297
+ }
5298
+ const lines = [];
5299
+ lines.push("");
5300
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
5301
+ lines.push(` \u{1F4E6} Monorepo Analysis \u2014 ${mono.tool}`);
5302
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
5303
+ lines.push("");
5304
+ lines.push(` Packages: ${mono.packages.length}`);
5305
+ lines.push(` Shared packages: ${mono.sharedPackages.length}`);
5306
+ lines.push(` Isolation score: ${mono.isolationScore}/100`);
5307
+ lines.push(` Total tokens: ${mono.totalTokens.toLocaleString()}`);
5308
+ lines.push("");
5309
+ lines.push(" Package breakdown:");
5310
+ lines.push("");
5311
+ const sorted = [...mono.packages].sort((a, b) => b.tokens - a.tokens);
5312
+ const maxNameLen = Math.max(...sorted.map((p) => p.name.length), 10);
5313
+ for (const pkg of sorted) {
5314
+ const name = pkg.name.padEnd(maxNameLen);
5315
+ const tokens = `${(pkg.tokens / 1e3).toFixed(1)}K`.padStart(8);
5316
+ const files = `${pkg.files} files`.padStart(10);
5317
+ const deps = pkg.dependencies.length > 0 ? ` \u2192 ${pkg.dependencies.join(", ")}` : "";
5318
+ const shared = pkg.isShared ? " [shared]" : "";
5319
+ lines.push(` ${name} ${tokens} ${files}${shared}${deps}`);
5320
+ }
5321
+ if (mono.crossPackageEdges.length > 0) {
5322
+ lines.push("");
5323
+ lines.push(" Cross-package dependencies:");
5324
+ for (const edge of mono.crossPackageEdges.slice(0, 10)) {
5325
+ lines.push(` ${edge.from} \u2192 ${edge.to} (${edge.files} files)`);
5326
+ }
5327
+ if (mono.crossPackageEdges.length > 10) {
5328
+ lines.push(` ... and ${mono.crossPackageEdges.length - 10} more`);
5329
+ }
5330
+ }
5331
+ lines.push("");
5332
+ lines.push(" \u{1F4A1} Use --monorepo --package <name> to see context savings for a specific package.");
5333
+ lines.push("");
5334
+ return lines.join("\n");
5335
+ }
5336
+ function renderPackageContext(result) {
5337
+ const lines = [];
5338
+ lines.push("");
5339
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
5340
+ lines.push(` \u{1F3AF} Package Context \u2014 ${result.targetPackage}`);
5341
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
5342
+ lines.push("");
5343
+ lines.push(` Included packages: ${result.includedPackages.length}`);
5344
+ lines.push(` Excluded packages: ${result.excludedPackages.length}`);
5345
+ lines.push("");
5346
+ lines.push(` Original tokens: ${result.originalTokens.toLocaleString()}`);
5347
+ lines.push(` Optimized tokens: ${result.optimizedTokens.toLocaleString()}`);
5348
+ lines.push(` Token savings: ${result.savedTokens.toLocaleString()} (${result.savedPercent}%)`);
5349
+ lines.push("");
5350
+ if (result.includedPackages.length > 0) {
5351
+ lines.push(" \u2705 Included:");
5352
+ for (const p of result.includedPackages) {
5353
+ lines.push(` ${p}`);
5354
+ }
5355
+ }
5356
+ if (result.excludedPackages.length > 0) {
5357
+ lines.push(" \u2B1C Excluded (not needed for this package):");
5358
+ for (const p of result.excludedPackages.slice(0, 10)) {
5359
+ lines.push(` ${p}`);
5360
+ }
5361
+ if (result.excludedPackages.length > 10) {
5362
+ lines.push(` ... and ${result.excludedPackages.length - 10} more`);
5363
+ }
5364
+ }
5365
+ lines.push("");
5366
+ return lines.join("\n");
5367
+ }
5368
+
5369
+ // src/engine/quality-gate.ts
5370
+ import { readFile as readFile12, writeFile as writeFile7, mkdir as mkdir6 } from "fs/promises";
5371
+ import { resolve as resolve13 } from "path";
5372
+ import { existsSync as existsSync6 } from "fs";
5373
+ var DEFAULT_GATE_CONFIG = {
5374
+ threshold: 70,
5375
+ failOnSecrets: true,
5376
+ failOnRegression: true,
5377
+ regressionLimit: 5,
5378
+ baselinePath: ".cto/baseline.json",
5379
+ secretSeverities: ["critical", "high"]
5380
+ };
5381
+ async function loadBaseline(projectPath, baselinePath) {
5382
+ const filePath = resolve13(projectPath, baselinePath || ".cto/baseline.json");
5383
+ if (!existsSync6(filePath)) return null;
5384
+ try {
5385
+ const content = await readFile12(filePath, "utf-8");
5386
+ return JSON.parse(content);
5387
+ } catch {
5388
+ return null;
5389
+ }
5390
+ }
5391
+ async function saveBaseline(projectPath, score, commit, branch, baselinePath) {
5392
+ const dir = resolve13(projectPath, ".cto");
5393
+ if (!existsSync6(dir)) await mkdir6(dir, { recursive: true });
5394
+ const baseline = {
5395
+ score: score.overall,
5396
+ grade: score.grade,
5397
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5398
+ commit,
5399
+ branch,
5400
+ dimensions: {
5401
+ efficiency: score.dimensions.efficiency.score,
5402
+ coverage: score.dimensions.coverage.score,
5403
+ riskControl: score.dimensions.riskControl.score,
5404
+ structure: score.dimensions.structure.score,
5405
+ governance: score.dimensions.governance.score
5406
+ }
5407
+ };
5408
+ const filePath = resolve13(projectPath, baselinePath || ".cto/baseline.json");
5409
+ await writeFile7(filePath, JSON.stringify(baseline, null, 2));
5410
+ }
5411
+ async function runQualityGate(score, analysis, secretFindings, config = {}) {
5412
+ const cfg = { ...DEFAULT_GATE_CONFIG, ...config };
5413
+ const checks = [];
5414
+ const baseline = await loadBaseline(analysis.projectPath, cfg.baselinePath);
5415
+ const previousScore = baseline?.score ?? null;
5416
+ const delta = previousScore !== null ? score.overall - previousScore : null;
5417
+ const thresholdPassed = score.overall >= cfg.threshold;
5418
+ checks.push({
5419
+ name: "Score threshold",
5420
+ passed: thresholdPassed,
5421
+ detail: thresholdPassed ? `Score ${score.overall} \u2265 threshold ${cfg.threshold}` : `Score ${score.overall} < threshold ${cfg.threshold}`,
5422
+ severity: thresholdPassed ? "info" : "error"
5423
+ });
5424
+ const dangerousSecrets = secretFindings.filter(
5425
+ (f) => cfg.secretSeverities.includes(f.severity)
5426
+ );
5427
+ const secretsPassed = !cfg.failOnSecrets || dangerousSecrets.length === 0;
5428
+ checks.push({
5429
+ name: "No secrets detected",
5430
+ passed: secretsPassed,
5431
+ detail: secretsPassed ? "No critical/high severity secrets found" : `${dangerousSecrets.length} secret(s) with ${cfg.secretSeverities.join("/")} severity`,
5432
+ severity: secretsPassed ? "info" : "error"
5433
+ });
5434
+ let regressionPassed = true;
5435
+ if (cfg.failOnRegression && delta !== null) {
5436
+ regressionPassed = delta >= -cfg.regressionLimit;
5437
+ }
5438
+ checks.push({
5439
+ name: "No score regression",
5440
+ passed: regressionPassed,
5441
+ detail: delta !== null ? regressionPassed ? `Score changed by ${delta >= 0 ? "+" : ""}${delta} (limit: -${cfg.regressionLimit})` : `Score dropped by ${Math.abs(delta)} points (limit: -${cfg.regressionLimit})` : "No baseline found (first run)",
5442
+ severity: regressionPassed ? delta !== null && delta < 0 ? "warning" : "info" : "error"
5443
+ });
5444
+ const weakDimensions = Object.entries(score.dimensions).filter(([_, d]) => d.score < 50).map(([name]) => name);
5445
+ const dimensionsPassed = weakDimensions.length === 0;
5446
+ checks.push({
5447
+ name: "Dimension health",
5448
+ passed: dimensionsPassed,
5449
+ detail: dimensionsPassed ? "All dimensions above 50%" : `Weak dimensions: ${weakDimensions.join(", ")}`,
5450
+ severity: dimensionsPassed ? "info" : "warning"
5451
+ });
5452
+ const passed = checks.filter((c) => c.severity === "error").every((c) => c.passed);
5453
+ const prComment = generatePRComment(score, analysis, checks, baseline, delta);
5454
+ const summary = generateSummary(score, checks, passed);
5455
+ return {
5456
+ passed,
5457
+ score: score.overall,
5458
+ grade: score.grade,
5459
+ previousScore,
5460
+ delta,
5461
+ checks,
5462
+ baseline,
5463
+ prComment,
5464
+ summary
5465
+ };
5466
+ }
5467
+ function generatePRComment(score, analysis, checks, baseline, delta) {
5468
+ const gradeEmoji = score.grade.startsWith("A") ? "\u{1F7E2}" : score.grade.startsWith("B") ? "\u{1F535}" : score.grade.startsWith("C") ? "\u{1F7E1}" : "\u{1F534}";
5469
+ const allPassed = checks.filter((c) => c.severity === "error").every((c) => c.passed);
5470
+ const statusIcon = allPassed ? "\u2705" : "\u274C";
5471
+ const deltaStr = delta !== null ? ` (${delta >= 0 ? "+" : ""}${delta})` : "";
5472
+ const lines = [
5473
+ `## ${statusIcon} CTO Quality Gate ${allPassed ? "Passed" : "Failed"}`,
5474
+ "",
5475
+ `### ${gradeEmoji} Context Score: ${score.overall}/100 (${score.grade})${deltaStr}`,
5476
+ "",
5477
+ `> **${analysis.projectName}** \xB7 ${analysis.totalFiles} files \xB7 ${Math.round(analysis.totalTokens / 1e3)}K tokens`,
5478
+ "",
5479
+ "### Checks",
5480
+ "",
5481
+ "| Check | Status | Detail |",
5482
+ "|-------|--------|--------|"
5483
+ ];
5484
+ for (const check of checks) {
5485
+ const icon = check.passed ? "\u2705" : check.severity === "warning" ? "\u26A0\uFE0F" : "\u274C";
5486
+ lines.push(`| ${check.name} | ${icon} | ${check.detail} |`);
5487
+ }
5488
+ lines.push("");
5489
+ lines.push("### Dimensions");
5490
+ lines.push("");
5491
+ lines.push("| Dimension | Score | vs Baseline |");
5492
+ lines.push("|-----------|-------|-------------|");
5493
+ for (const [name, dim] of Object.entries(score.dimensions)) {
5494
+ const prev = baseline?.dimensions[name];
5495
+ const diff = prev !== void 0 ? dim.score - prev : null;
5496
+ const diffStr = diff !== null ? `${diff >= 0 ? "+" : ""}${diff}` : "\u2014";
5497
+ const bar = renderBar2(dim.score);
5498
+ lines.push(`| ${name} | ${bar} ${dim.score}% | ${diffStr} |`);
5499
+ }
5500
+ lines.push("");
5501
+ lines.push("### Savings");
5502
+ lines.push("");
5503
+ lines.push(`| Metric | Value |`);
5504
+ lines.push(`|--------|-------|`);
5505
+ lines.push(`| Tokens saved | ${score.comparison.savedTokens.toLocaleString()} (${score.comparison.savedPercent}%) |`);
5506
+ lines.push(`| Monthly savings | $${score.comparison.monthlySavingsUSD.toFixed(2)} |`);
5507
+ if (score.insights.length > 0) {
5508
+ lines.push("");
5509
+ lines.push("### Insights");
5510
+ lines.push("");
5511
+ for (const insight of score.insights.slice(0, 5)) {
5512
+ const icon = insight.type === "strength" ? "\u2705" : insight.type === "weakness" ? "\u26A0\uFE0F" : "\u{1F4A1}";
5513
+ lines.push(`- ${icon} **${insight.title}** \u2014 ${insight.detail}`);
5514
+ }
5515
+ }
5516
+ lines.push("");
5517
+ lines.push("---");
5518
+ lines.push(`<sub>Generated by [CTO Quality Gate](https://npmjs.com/package/cto-ai-cli) \xB7 ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}</sub>`);
5519
+ return lines.join("\n");
5520
+ }
5521
+ function renderBar2(score) {
5522
+ const filled = Math.round(score / 10);
5523
+ return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
5524
+ }
5525
+ function generateSummary(score, checks, passed) {
5526
+ const status = passed ? "\u2705 PASSED" : "\u274C FAILED";
5527
+ const failedChecks = checks.filter((c) => !c.passed && c.severity === "error");
5528
+ const warnings = checks.filter((c) => !c.passed && c.severity === "warning");
5529
+ let summary = `Quality Gate ${status} \u2014 Score: ${score.overall}/100 (${score.grade})`;
5530
+ if (failedChecks.length > 0) {
5531
+ summary += `
5532
+ Failed: ${failedChecks.map((c) => c.name).join(", ")}`;
5533
+ }
5534
+ if (warnings.length > 0) {
5535
+ summary += `
5536
+ Warnings: ${warnings.map((c) => c.name).join(", ")}`;
5537
+ }
5538
+ return summary;
5539
+ }
4926
5540
  export {
5541
+ DEFAULT_GATE_CONFIG,
4927
5542
  MODEL_REGISTRY2 as MODEL_REGISTRY,
4928
5543
  ProjectWatcher,
5544
+ analyzeMonorepo,
4929
5545
  analyzeProject,
4930
5546
  analyzeSemantics,
4931
5547
  bfsBidirectional,
@@ -4939,6 +5555,7 @@ export {
4939
5555
  countTokensChars4,
4940
5556
  countTokensTiktoken,
4941
5557
  createProject,
5558
+ detectMonorepoTool,
4942
5559
  detectStack,
4943
5560
  estimateFileTokens,
4944
5561
  estimateTokens,
@@ -4957,6 +5574,7 @@ export {
4957
5574
  getPruneLevelForRisk,
4958
5575
  initProjectConfig,
4959
5576
  invalidateCache,
5577
+ loadBaseline,
4960
5578
  loadConfig,
4961
5579
  loadFeedbackModel,
4962
5580
  loadGlobalModel,
@@ -4977,17 +5595,22 @@ export {
4977
5595
  renderCompileProof,
4978
5596
  renderContextScore,
4979
5597
  renderFeedbackReport,
5598
+ renderMonorepoAnalysis,
4980
5599
  renderMultiModelResult,
5600
+ renderPackageContext,
4981
5601
  renderQualityBenchmark,
4982
5602
  renderSemanticAnalysis,
4983
5603
  runBenchmark,
4984
5604
  runCompilabilityBenchmark,
4985
5605
  runCompileProof,
4986
5606
  runQualityBenchmark,
5607
+ runQualityGate,
5608
+ saveBaseline,
4987
5609
  saveConfig,
4988
5610
  scoreAllFiles,
4989
5611
  scoreFile,
4990
5612
  selectContext,
5613
+ selectPackageContext,
4991
5614
  semanticBoosts,
4992
5615
  unwatchAll,
4993
5616
  unwatchProject,