@vibekiln/cutline-mcp-cli 0.1.2 → 0.3.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.
@@ -2,4 +2,6 @@ export declare function setupCommand(options: {
2
2
  staging?: boolean;
3
3
  skipLogin?: boolean;
4
4
  projectRoot?: string;
5
+ hideAuditDimension?: string[];
6
+ hideAuditDimensions?: string;
5
7
  }): Promise<void>;
@@ -27,6 +27,7 @@ const SERVER_NAMES = [
27
27
  'output',
28
28
  'integrations',
29
29
  ];
30
+ const AUDIT_DIMENSIONS = ['engineering', 'security', 'reliability', 'scalability', 'compliance'];
30
31
  async function detectTier(options) {
31
32
  const refreshToken = await getRefreshToken();
32
33
  if (!refreshToken)
@@ -83,6 +84,67 @@ function prompt(question) {
83
84
  });
84
85
  });
85
86
  }
87
+ function parseDimensionCsv(csv) {
88
+ if (!csv)
89
+ return [];
90
+ return csv
91
+ .split(',')
92
+ .map((s) => s.trim())
93
+ .filter(Boolean);
94
+ }
95
+ function normalizeAuditDimensions(values) {
96
+ const allowed = new Set(AUDIT_DIMENSIONS);
97
+ const deduped = [...new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean))];
98
+ const valid = deduped.filter((v) => allowed.has(v));
99
+ const invalid = deduped.filter((v) => !allowed.has(v));
100
+ return { valid, invalid };
101
+ }
102
+ async function resolveHiddenAuditDimensions(options) {
103
+ const explicit = [
104
+ ...(options.hideAuditDimension ?? []),
105
+ ...parseDimensionCsv(options.hideAuditDimensions),
106
+ ];
107
+ if (explicit.length > 0) {
108
+ const { valid, invalid } = normalizeAuditDimensions(explicit);
109
+ if (invalid.length > 0) {
110
+ console.log(chalk.yellow(` Ignoring invalid audit dimensions: ${invalid.join(', ')} ` +
111
+ `(allowed: ${AUDIT_DIMENSIONS.join(', ')})`));
112
+ }
113
+ return valid;
114
+ }
115
+ const answer = await prompt(chalk.cyan(` Hide any code audit dimensions? ` +
116
+ `(comma-separated: ${AUDIT_DIMENSIONS.join(', ')}; Enter for none): `));
117
+ if (!answer)
118
+ return [];
119
+ const { valid, invalid } = normalizeAuditDimensions(parseDimensionCsv(answer));
120
+ if (invalid.length > 0) {
121
+ console.log(chalk.yellow(` Ignoring invalid audit dimensions: ${invalid.join(', ')} ` +
122
+ `(allowed: ${AUDIT_DIMENSIONS.join(', ')})`));
123
+ }
124
+ return valid;
125
+ }
126
+ function writeAuditVisibilityConfig(hiddenDimensions) {
127
+ const configDir = join(homedir(), '.cutline-mcp');
128
+ const configPath = join(configDir, 'config.json');
129
+ let existing = {};
130
+ try {
131
+ if (existsSync(configPath)) {
132
+ existing = JSON.parse(readFileSync(configPath, 'utf-8'));
133
+ }
134
+ }
135
+ catch {
136
+ existing = {};
137
+ }
138
+ const next = {
139
+ ...existing,
140
+ audit_visibility: {
141
+ ...(existing.audit_visibility ?? {}),
142
+ hidden_dimensions: hiddenDimensions,
143
+ },
144
+ };
145
+ mkdirSync(configDir, { recursive: true });
146
+ writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
147
+ }
86
148
  function resolveServeRuntime() {
87
149
  // Optional explicit override for advanced environments.
88
150
  if (process.env.CUTLINE_MCP_BIN?.trim()) {
@@ -222,6 +284,14 @@ export async function setupCommand(options) {
222
284
  }
223
285
  catch { /* ignore parse errors */ }
224
286
  }
287
+ const hiddenAuditDimensions = await resolveHiddenAuditDimensions(options);
288
+ writeAuditVisibilityConfig(hiddenAuditDimensions);
289
+ if (hiddenAuditDimensions.length > 0) {
290
+ console.log(chalk.dim(` Hidden audit dimensions: ${hiddenAuditDimensions.join(', ')}\n`));
291
+ }
292
+ else {
293
+ console.log(chalk.dim(' Hidden audit dimensions: none\n'));
294
+ }
225
295
  // ── 3. Write MCP server config to IDEs ───────────────────────────────────
226
296
  const runtime = resolveServeRuntime();
227
297
  const serverConfig = buildServerConfig(runtime);
package/dist/index.js CHANGED
@@ -54,7 +54,15 @@ program
54
54
  .option('--staging', 'Use staging environment')
55
55
  .option('--skip-login', 'Skip authentication (use existing credentials)')
56
56
  .option('--project-root <path>', 'Project root directory for IDE rules (default: cwd)')
57
- .action((opts) => setupCommand({ staging: opts.staging, skipLogin: opts.skipLogin, projectRoot: opts.projectRoot }));
57
+ .option('--hide-audit-dimension <name>', 'Hide one audit dimension in surfaced code audit output (repeatable)', (value, prev) => [...prev, value], [])
58
+ .option('--hide-audit-dimensions <csv>', 'Hide multiple audit dimensions (comma-separated: engineering,security,reliability,scalability,compliance)')
59
+ .action((opts) => setupCommand({
60
+ staging: opts.staging,
61
+ skipLogin: opts.skipLogin,
62
+ projectRoot: opts.projectRoot,
63
+ hideAuditDimension: opts.hideAuditDimension,
64
+ hideAuditDimensions: opts.hideAuditDimensions,
65
+ }));
58
66
  program
59
67
  .command('init')
60
68
  .description('Generate IDE rules only (setup runs this automatically)')
@@ -287,6 +287,24 @@ async function exchangeRefreshToken(refreshToken, firebaseApiKey, maxRetries = 3
287
287
  }
288
288
  throw lastError || new Error("Token exchange failed after retries");
289
289
  }
290
+ var AUDIT_DIMENSIONS = ["engineering", "security", "reliability", "scalability", "compliance"];
291
+ function readLocalCutlineConfig() {
292
+ try {
293
+ const configPath = path.join(os.homedir(), ".cutline-mcp", "config.json");
294
+ if (!fs.existsSync(configPath))
295
+ return null;
296
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
297
+ } catch {
298
+ return null;
299
+ }
300
+ }
301
+ function getHiddenAuditDimensions() {
302
+ const config = readLocalCutlineConfig();
303
+ const hidden = config?.audit_visibility?.hidden_dimensions ?? [];
304
+ const allowed = new Set(AUDIT_DIMENSIONS);
305
+ const normalized = [...new Set(hidden.map((d) => String(d).trim().toLowerCase()).filter((d) => allowed.has(d)))];
306
+ return normalized;
307
+ }
290
308
  function getStoredApiKey() {
291
309
  if (process.env.CUTLINE_API_KEY) {
292
310
  return {
@@ -294,15 +312,9 @@ function getStoredApiKey() {
294
312
  environment: process.env.CUTLINE_ENV === "staging" ? "staging" : "production"
295
313
  };
296
314
  }
297
- try {
298
- const configPath = path.join(os.homedir(), ".cutline-mcp", "config.json");
299
- if (fs.existsSync(configPath)) {
300
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
301
- if (config.apiKey) {
302
- return { apiKey: config.apiKey, environment: config.environment };
303
- }
304
- }
305
- } catch {
315
+ const config = readLocalCutlineConfig();
316
+ if (config?.apiKey) {
317
+ return { apiKey: config.apiKey, environment: config.environment };
306
318
  }
307
319
  return null;
308
320
  }
@@ -314,21 +326,13 @@ async function getStoredToken() {
314
326
  environment: process.env.CUTLINE_ENV === "staging" ? "staging" : "production"
315
327
  };
316
328
  }
317
- try {
318
- const configPath = path.join(os.homedir(), ".cutline-mcp", "config.json");
319
- if (fs.existsSync(configPath)) {
320
- const content = fs.readFileSync(configPath, "utf-8");
321
- const config = JSON.parse(content);
322
- if (config.refreshToken) {
323
- console.error("[MCP Auth] Using token from ~/.cutline-mcp/config.json", config.environment ? `(environment: ${config.environment})` : "");
324
- return {
325
- refreshToken: config.refreshToken,
326
- environment: config.environment
327
- };
328
- }
329
- }
330
- } catch (e) {
331
- console.error("[MCP Auth] Failed to read config file:", e);
329
+ const config = readLocalCutlineConfig();
330
+ if (config?.refreshToken) {
331
+ console.error("[MCP Auth] Using token from ~/.cutline-mcp/config.json", config.environment ? `(environment: ${config.environment})` : "");
332
+ return {
333
+ refreshToken: config.refreshToken,
334
+ environment: config.environment
335
+ };
332
336
  }
333
337
  return null;
334
338
  }
@@ -991,6 +995,7 @@ export {
991
995
  mapErrorToMcp,
992
996
  validateAuth,
993
997
  resolveAuthContext,
998
+ getHiddenAuditDimensions,
994
999
  requirePremiumWithAutoAuth,
995
1000
  resolveAuthContextFree,
996
1001
  getPublicSiteUrlForCurrentAuth,
@@ -565,7 +565,8 @@ function computeGraphMetrics(entities, edges, constraints, bindings, conflicts,
565
565
  gdpr: "GDPR/CCPA",
566
566
  owasp: "OWASP LLM Top 10",
567
567
  glba: "GLBA",
568
- ferpa: "FERPA/COPPA"
568
+ ferpa: "FERPA/COPPA",
569
+ ios: "iOS App Store"
569
570
  };
570
571
  const detectedFrameworks = /* @__PURE__ */ new Set();
571
572
  for (const c of constraints) {
@@ -879,7 +880,8 @@ function computeGenericGraphMetrics(entities, edges, constraints, bindings) {
879
880
  gdpr: "GDPR/CCPA",
880
881
  owasp: "OWASP LLM Top 10",
881
882
  glba: "GLBA",
882
- ferpa: "FERPA/COPPA"
883
+ ferpa: "FERPA/COPPA",
884
+ ios: "iOS App Store"
883
885
  };
884
886
  const detectedFrameworks = /* @__PURE__ */ new Set();
885
887
  for (const c of constraints) {
@@ -43,6 +43,7 @@ import {
43
43
  getAllNodesLight,
44
44
  getEntitiesWithEmbeddings,
45
45
  getGraphMetadata,
46
+ getHiddenAuditDimensions,
46
47
  getIdeaReport,
47
48
  getNodesByCategories,
48
49
  getNodesMissingEmbeddings,
@@ -74,13 +75,13 @@ import {
74
75
  upsertEntities,
75
76
  upsertNodes,
76
77
  validateRequestSize
77
- } from "./chunk-WWTNBUIX.js";
78
+ } from "./chunk-6Y3AEXE3.js";
78
79
  import {
79
80
  GraphTraverser,
80
81
  computeGenericGraphMetrics,
81
82
  computeMetricsFromGraph,
82
83
  detectConstraintConflicts
83
- } from "./chunk-UBBAYTW3.js";
84
+ } from "./chunk-IDSVMCGM.js";
84
85
 
85
86
  // ../mcp/dist/mcp/src/cutline-server.js
86
87
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -415,6 +416,21 @@ var PATH_PATTERNS = [
415
416
  categories: ["compliance", "security"],
416
417
  domain: "fedramp",
417
418
  priority: "high"
419
+ },
420
+ {
421
+ patterns: [
422
+ /\/ios/i,
423
+ /\/swift/i,
424
+ /\/storekit/i,
425
+ /\/appstore/i,
426
+ /\/testflight/i,
427
+ /\.swift$/i,
428
+ /Info\.plist$/i
429
+ ],
430
+ keywords: ["ios", "app-store", "storekit", "iap", "testflight", "swift", "mobile"],
431
+ categories: ["compliance", "security", "risk"],
432
+ domain: "ios_app_store",
433
+ priority: "high"
418
434
  }
419
435
  ];
420
436
  var CODE_PATTERNS = [
@@ -589,6 +605,20 @@ var CODE_PATTERNS = [
589
605
  keywords: ["csa", "ccm", "cloud", "aws", "gcp", "azure", "cloud-security"],
590
606
  categories: ["compliance", "security"],
591
607
  domain: "csa_ccm"
608
+ },
609
+ {
610
+ patterns: [
611
+ /StoreKit/i,
612
+ /SKPaymentQueue/i,
613
+ /SKProductsRequest/i,
614
+ /ASAuthorizationAppleID/i,
615
+ /UIApplicationOpenSettingsURLString/i,
616
+ /ATTrackingManager/i,
617
+ /\.swift$/i
618
+ ],
619
+ keywords: ["ios", "app-store", "storekit", "iap", "apple-signin", "tracking-consent"],
620
+ categories: ["compliance", "security"],
621
+ domain: "ios_app_store"
592
622
  }
593
623
  ];
594
624
  function analyzeFilePaths(paths) {
@@ -2977,7 +3007,8 @@ var FRAMEWORK_ID_PREFIXES = {
2977
3007
  gdpr: "GDPR/CCPA",
2978
3008
  owasp: "OWASP LLM Top 10",
2979
3009
  glba: "GLBA",
2980
- ferpa: "FERPA/COPPA"
3010
+ ferpa: "FERPA/COPPA",
3011
+ ios: "iOS App Store"
2981
3012
  };
2982
3013
  function resolveFramework(constraintId) {
2983
3014
  if (!constraintId.startsWith("constraint:blueprint:"))
@@ -5405,6 +5436,53 @@ var BLUEPRINT_RULES = [
5405
5436
  framework: "ferpa_coppa"
5406
5437
  }
5407
5438
  ]
5439
+ },
5440
+ // ── iOS App Store Review Guidelines (mobile iOS apps) ──────────────────────
5441
+ {
5442
+ trigger: (eco, ctx) => {
5443
+ const iosSignals = /ios|swift|swiftui|xcode|uikit|storekit|appstore|testflight|cocoapods|xcframework/i;
5444
+ const hasLang = eco.languages.some((l) => /swift|objective-c|objc/i.test(l));
5445
+ const hasFramework = eco.frameworks.some((f) => iosSignals.test(f));
5446
+ const hasDep = eco.all_dependencies.some((d) => iosSignals.test(d));
5447
+ const descMatch = ctx?.productDescription ? /\b(ios|iphone|ipad|app\s*store|testflight|storekit|in-app purchase|apple sign in)\b/i.test(ctx.productDescription) : false;
5448
+ const entityMatch = ctx?.existingEntityNames?.some((n) => /\b(ios|mobile|iphone|ipad|app store|iap|storekit)\b/i.test(n)) ?? false;
5449
+ return hasLang || hasFramework || hasDep || descMatch || entityMatch;
5450
+ },
5451
+ constraints: [
5452
+ {
5453
+ id_suffix: "ios_app_store_privacy_disclosure",
5454
+ category: "compliance",
5455
+ summary: "[iOS App Store 5.1] Apps collecting user data MUST provide accurate privacy disclosures and clear in-app data handling flows.",
5456
+ keywords: ["ios", "app-store", "privacy", "disclosure", "tracking", "app-privacy"],
5457
+ severity: "critical",
5458
+ action: "Document data collection in App Privacy labels and align runtime behavior. Gate tracking behind explicit consent where required.",
5459
+ checklist_ref: "IOS-5.1",
5460
+ file_patterns: ["**/ios/**", "**/*.swift", "**/privacy*", "**/tracking/**", "**/analytics/**"],
5461
+ framework: "ios_app_store"
5462
+ },
5463
+ {
5464
+ id_suffix: "ios_app_store_iap_policy",
5465
+ category: "compliance",
5466
+ summary: "[iOS App Store 3.1] Digital goods/services sold in-app MUST use Apple's In-App Purchase flows where required.",
5467
+ keywords: ["ios", "app-store", "iap", "storekit", "payments", "digital-goods"],
5468
+ severity: "warning",
5469
+ action: "Use StoreKit for digital purchases in iOS app surfaces. Avoid bypass payment links that violate App Store rules.",
5470
+ checklist_ref: "IOS-3.1",
5471
+ file_patterns: ["**/ios/**", "**/*.swift", "**/billing/**", "**/payment/**", "**/storekit/**"],
5472
+ framework: "ios_app_store"
5473
+ },
5474
+ {
5475
+ id_suffix: "ios_app_store_account_deletion",
5476
+ category: "compliance",
5477
+ summary: "[iOS App Store 5.1.1(v)] If account creation exists, apps MUST provide in-app account deletion with data handling consistent with policy.",
5478
+ keywords: ["ios", "app-store", "account-deletion", "user-account", "privacy-rights"],
5479
+ severity: "warning",
5480
+ action: "Expose account deletion in-app for iOS users and ensure backend deletion flow is implemented and verifiable.",
5481
+ checklist_ref: "IOS-5.1.1",
5482
+ file_patterns: ["**/ios/**", "**/*.swift", "**/api/account*", "**/api/user*", "**/settings/**"],
5483
+ framework: "ios_app_store"
5484
+ }
5485
+ ]
5408
5486
  }
5409
5487
  ];
5410
5488
  function buildBlueprintConstraints(ecosystem, context) {
@@ -5504,6 +5582,7 @@ Flag if the codebase contains signals that would require specific compliance fra
5504
5582
  - **OWASP LLM**: OpenAI, Anthropic, LangChain, vector DBs (Pinecone, Weaviate), RAG pipelines, AI agents
5505
5583
  - **GLBA**: Plaid, banking SDKs, KYC/AML, lending/mortgage/wealth management code
5506
5584
  - **FERPA/COPPA**: EdTech integrations (Clever, Canvas), student/minor data, classroom/school references
5585
+ - **iOS App Store**: iOS/Swift codepaths, App Store/TestFlight distribution, StoreKit/IAP, mobile app privacy policies
5507
5586
 
5508
5587
  For each detected signal, note:
5509
5588
  - H1. Which framework(s) apply and why
@@ -5551,7 +5630,7 @@ Return a JSON object with exactly these fields:
5551
5630
  - referenceClasses (string[]): Security frameworks or standards that apply (e.g., "OWASP Top 10 2021", "SOC 2 Type II").
5552
5631
  - constraints (object?): Resource constraints \u2014 team, budget_usd, deadline_days, must_ship_scope.
5553
5632
  - checklist_summary (object): Keys are checklist IDs (A1-A8, B1-B6, C1-C7, D1-D8, E1-E4, F1-F3, G-*, H1-H3, I1-I8, J1-J6, K1-K8), values are "pass"|"fail"|"warn"|"not_applicable". This forces systematic coverage.
5554
- - compliance_signals (array of {framework: "pci_dss"|"hipaa"|"fedramp"|"gdpr_ccpa"|"owasp_llm"|"glba"|"ferpa_coppa"|"csa_ccm", signal: string, confidence: number}?): Detected compliance framework signals. Return [] if none.
5633
+ - compliance_signals (array of {framework: "pci_dss"|"hipaa"|"fedramp"|"gdpr_ccpa"|"owasp_llm"|"glba"|"ferpa_coppa"|"csa_ccm"|"ios_app_store", signal: string, confidence: number}?): Detected compliance framework signals. Return [] if none.
5555
5634
 
5556
5635
  Be concrete and specific. Reference file paths and line numbers where possible. If a checklist item cannot be assessed from the provided files, mark it "not_applicable" and note why. Cover ALL sections A through K.`;
5557
5636
  var FULL_SYSTEM_PROMPT = `You are a product analyst reviewing a codebase. Given file contents, an ecosystem fingerprint, and existing constraints, extract structured product context.
@@ -6156,13 +6235,15 @@ async function seedBlueprintConstraints(productId, ecosystem, deps, blueprintCon
6156
6235
  const FRAMEWORK_LABELS = {
6157
6236
  baseline: "Security Baseline",
6158
6237
  soc2: "SOC 2",
6238
+ csa_ccm: "CSA Controls Matrix",
6159
6239
  pci_dss: "PCI-DSS",
6160
6240
  hipaa: "HIPAA",
6161
6241
  fedramp: "FedRAMP",
6162
6242
  gdpr_ccpa: "GDPR/CCPA",
6163
6243
  owasp_llm: "OWASP LLM Top 10",
6164
6244
  glba: "GLBA",
6165
- ferpa_coppa: "FERPA/COPPA"
6245
+ ferpa_coppa: "FERPA/COPPA",
6246
+ ios_app_store: "iOS App Store"
6166
6247
  };
6167
6248
  const newEntities = [];
6168
6249
  const newEdges = [];
@@ -6966,10 +7047,27 @@ function deltaStr(current, previous) {
6966
7047
  return " (no change)";
6967
7048
  return diff > 0 ? ` (**+${diff}** since last scan)` : ` (**${diff}** since last scan)`;
6968
7049
  }
6969
- function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai") {
7050
+ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai", hiddenAuditDimensions = []) {
6970
7051
  const m = result.metrics;
6971
7052
  const p = result.previousMetrics;
6972
7053
  const isRescan = !!p;
7054
+ const hiddenSet = new Set(hiddenAuditDimensions.map((d) => String(d).trim().toLowerCase()));
7055
+ const securityVisible = !hiddenSet.has("security");
7056
+ const inferFindingDimension = (category) => {
7057
+ const c = (category ?? "").toLowerCase();
7058
+ if (["reliability"].includes(c))
7059
+ return "reliability";
7060
+ if (["scalability", "performance"].includes(c))
7061
+ return "scalability";
7062
+ if (["compliance"].includes(c))
7063
+ return "compliance";
7064
+ if (["code_quality", "general"].includes(c))
7065
+ return "engineering";
7066
+ return "security";
7067
+ };
7068
+ const hasComplianceFrameworks = result.frameworksLoaded.length > 0;
7069
+ const complianceCurrent = hasComplianceFrameworks ? Math.round((m.nfr_coverage?.compliance ?? 0) * 100) : void 0;
7070
+ const compliancePrevious = hasComplianceFrameworks ? Math.round((p?.nfr_coverage?.compliance ?? 0) * 100) : void 0;
6973
7071
  const lines = [
6974
7072
  `# Cutline Code Audit`,
6975
7073
  ``,
@@ -6979,8 +7077,50 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
6979
7077
  if (result.frameworksLoaded.length > 0) {
6980
7078
  lines.push(`**Compliance frameworks:** ${result.frameworksLoaded.join(", ")}`);
6981
7079
  }
6982
- lines.push(``, `## Readiness Scores`, ``, `| Pillar | Score |${isRescan ? " Change |" : ""}`, `|--------|-------|${isRescan ? "--------|" : ""}`, `| Engineering | ${m.engineering_readiness_pct ?? 0}% |${isRescan ? deltaStr(m.engineering_readiness_pct, p?.engineering_readiness_pct) + " |" : ""}`, `| Security | ${m.security_readiness_pct ?? 0}% |${isRescan ? deltaStr(m.security_readiness_pct, p?.security_readiness_pct) + " |" : ""}`, `| Reliability | ${m.reliability_readiness_pct ?? 0}% |${isRescan ? deltaStr(m.reliability_readiness_pct, p?.reliability_readiness_pct) + " |" : ""}`, `| Scalability | ${m.scalability_readiness_pct ?? 0}% |${isRescan ? deltaStr(m.scalability_readiness_pct, p?.scalability_readiness_pct) + " |" : ""}`, ``, `**Constraints mapped:** ${m.complexity_factors?.nfr_count ?? 0} (${m.complexity_factors?.critical_nfr_count ?? 0} critical)`, `**Binding coverage:** ${result.bindingHealth.coverage_pct}%`);
6983
- if (result.scaFindings && result.scaFindings.total > 0) {
7080
+ const scoreRows = [
7081
+ {
7082
+ key: "engineering",
7083
+ label: "Engineering",
7084
+ current: m.engineering_readiness_pct ?? 0,
7085
+ previous: p?.engineering_readiness_pct
7086
+ },
7087
+ {
7088
+ key: "security",
7089
+ label: "Security",
7090
+ current: m.security_readiness_pct ?? 0,
7091
+ previous: p?.security_readiness_pct
7092
+ },
7093
+ {
7094
+ key: "reliability",
7095
+ label: "Reliability",
7096
+ current: m.reliability_readiness_pct ?? 0,
7097
+ previous: p?.reliability_readiness_pct
7098
+ },
7099
+ {
7100
+ key: "scalability",
7101
+ label: "Scalability",
7102
+ current: m.scalability_readiness_pct ?? 0,
7103
+ previous: p?.scalability_readiness_pct
7104
+ },
7105
+ {
7106
+ key: "compliance",
7107
+ label: "Compliance",
7108
+ current: complianceCurrent,
7109
+ previous: compliancePrevious,
7110
+ na: !hasComplianceFrameworks
7111
+ }
7112
+ ].filter((row) => !hiddenSet.has(row.key));
7113
+ lines.push(``, `## Readiness Scores`, ``, `| Pillar | Score |${isRescan ? " Change |" : ""}`, `|--------|-------|${isRescan ? "--------|" : ""}`);
7114
+ if (scoreRows.length > 0) {
7115
+ for (const row of scoreRows) {
7116
+ lines.push(`| ${row.label} | ${row.na ? "N/A" : `${row.current ?? 0}%`} |${isRescan ? row.na ? " (n/a) |" : deltaStr(row.current, row.previous) + " |" : ""}`);
7117
+ }
7118
+ lines.push(``);
7119
+ } else {
7120
+ lines.push(`| _Hidden by local audit visibility settings_ | - |${isRescan ? " - |" : ""}`, ``);
7121
+ }
7122
+ lines.push(`**Constraints mapped:** ${m.complexity_factors?.nfr_count ?? 0} (${m.complexity_factors?.critical_nfr_count ?? 0} critical)`, `**Binding coverage:** ${result.bindingHealth.coverage_pct}%`);
7123
+ if (securityVisible && result.scaFindings && result.scaFindings.total > 0) {
6984
7124
  const sca = result.scaFindings;
6985
7125
  lines.push(``, `## Dependency Vulnerabilities (SCA)`, ``, `**${sca.total} known vulnerabilities** found (${sca.critical} critical, ${sca.high} high)`, ``);
6986
7126
  for (const v of sca.top) {
@@ -6991,12 +7131,13 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
6991
7131
  lines.push(`- ...and ${sca.total - sca.top.length} more`);
6992
7132
  }
6993
7133
  }
6994
- const totalFindings = result.gatedGapDetails.length;
6995
- const criticalCount = result.gatedGapDetails.filter((g) => g.severity === "critical" || g.severity === "high").length;
7134
+ const visibleFindings = result.gatedGapDetails.filter((g) => !hiddenSet.has(inferFindingDimension(g.category)));
7135
+ const totalFindings = visibleFindings.length;
7136
+ const criticalCount = visibleFindings.filter((g) => g.severity === "critical" || g.severity === "high").length;
6996
7137
  if (totalFindings > 0) {
6997
- const topFinding = result.gatedGapDetails[0];
7138
+ const topFinding = visibleFindings[0];
6998
7139
  lines.push(``, `## #1 Finding \u2014 Fix This Now`, ``, `**[${topFinding.severity.toUpperCase()}] ${topFinding.title}**`, `*Category: ${topFinding.category}*`, ``, topFinding.description || "Address this finding to improve your readiness scores.", ``, `> Fix this issue, then re-run \`code_audit\` to see your scores improve.`);
6999
- const remaining = result.gatedGapDetails.slice(1);
7140
+ const remaining = visibleFindings.slice(1);
7000
7141
  if (remaining.length > 0) {
7001
7142
  lines.push(``, `## ${remaining.length} More Finding${remaining.length > 1 ? "s" : ""} Detected`);
7002
7143
  const teaserLimit = Math.min(remaining.length, 5);
@@ -8222,6 +8363,20 @@ Why AI: ${idea.whyAI}`
8222
8363
  }
8223
8364
  const freeAuth = await resolveAuthContextFree(scanArgs.auth_token);
8224
8365
  const uid = freeAuth.effectiveUid;
8366
+ const hiddenAuditDimensions = getHiddenAuditDimensions();
8367
+ const hiddenSet = new Set(hiddenAuditDimensions);
8368
+ const inferFindingDimension = (category) => {
8369
+ const c = (category ?? "").toLowerCase();
8370
+ if (["reliability"].includes(c))
8371
+ return "reliability";
8372
+ if (["scalability", "performance"].includes(c))
8373
+ return "scalability";
8374
+ if (["compliance"].includes(c))
8375
+ return "compliance";
8376
+ if (["code_quality", "general"].includes(c))
8377
+ return "engineering";
8378
+ return "security";
8379
+ };
8225
8380
  {
8226
8381
  const rateInfo = await getScanRateLimit();
8227
8382
  const now = /* @__PURE__ */ new Date();
@@ -8258,24 +8413,36 @@ Why AI: ${idea.whyAI}`
8258
8413
  let reportId;
8259
8414
  let reportSiteUrl = "https://thecutline.ai";
8260
8415
  try {
8416
+ const reportMetrics = {};
8417
+ if (!hiddenSet.has("engineering")) {
8418
+ reportMetrics.engineering_readiness_pct = result.metrics.engineering_readiness_pct ?? 0;
8419
+ }
8420
+ if (!hiddenSet.has("security")) {
8421
+ reportMetrics.security_readiness_pct = result.metrics.security_readiness_pct ?? 0;
8422
+ }
8423
+ if (!hiddenSet.has("reliability")) {
8424
+ reportMetrics.reliability_readiness_pct = result.metrics.reliability_readiness_pct ?? 0;
8425
+ }
8426
+ if (!hiddenSet.has("scalability")) {
8427
+ reportMetrics.scalability_readiness_pct = result.metrics.scalability_readiness_pct ?? 0;
8428
+ }
8429
+ if (!hiddenSet.has("compliance")) {
8430
+ reportMetrics.compliance_readiness_pct = result.frameworksLoaded.length > 0 ? Math.round((result.metrics.nfr_coverage?.compliance ?? 0) * 100) : null;
8431
+ }
8432
+ const visibleFindings = result.gatedGapDetails.filter((f) => !hiddenSet.has(inferFindingDimension(f.category)));
8261
8433
  const saved = await saveScanReport({
8262
- metrics: {
8263
- engineering_readiness_pct: result.metrics.engineering_readiness_pct ?? 0,
8264
- security_readiness_pct: result.metrics.security_readiness_pct ?? 0,
8265
- reliability_readiness_pct: result.metrics.reliability_readiness_pct ?? 0,
8266
- scalability_readiness_pct: result.metrics.scalability_readiness_pct ?? 0
8267
- },
8434
+ metrics: reportMetrics,
8268
8435
  ecosystem: result.ecosystem,
8269
8436
  binding_coverage_pct: result.bindingHealth.coverage_pct,
8270
8437
  frameworks_loaded: result.frameworksLoaded,
8271
8438
  sensitive_data_count: result.sensitiveDataCount,
8272
- findings: result.gatedGapDetails.map((f) => ({
8439
+ findings: visibleFindings.map((f) => ({
8273
8440
  title: f.title,
8274
8441
  severity: f.severity,
8275
8442
  category: f.category,
8276
8443
  description: f.description
8277
8444
  })),
8278
- sca_summary: result.scaFindings ? {
8445
+ sca_summary: !hiddenSet.has("security") && result.scaFindings ? {
8279
8446
  total: result.scaFindings.total,
8280
8447
  critical: result.scaFindings.critical,
8281
8448
  high: result.scaFindings.high
@@ -8287,7 +8454,7 @@ Why AI: ${idea.whyAI}`
8287
8454
  console.error("[code_audit] Report persistence failed (non-fatal):", e);
8288
8455
  }
8289
8456
  return {
8290
- content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl) }]
8457
+ content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl, hiddenAuditDimensions) }]
8291
8458
  };
8292
8459
  }
8293
8460
  const authCtx = await resolveAuthContext(args.auth_token);
@@ -8941,7 +9108,8 @@ Meta: ${JSON.stringify(output.meta)}` }
8941
9108
  gdpr: "GDPR/CCPA",
8942
9109
  owasp: "OWASP LLM Top 10",
8943
9110
  glba: "GLBA",
8944
- ferpa: "FERPA/COPPA"
9111
+ ferpa: "FERPA/COPPA",
9112
+ ios: "iOS App Store"
8945
9113
  };
8946
9114
  const formattedConstraints = topConstraints.map((c) => {
8947
9115
  const framework = detectFramework2(c.id);
@@ -9931,7 +10099,7 @@ ${JSON.stringify(metrics, null, 2)}` }
9931
10099
  getAllNodes(product_id),
9932
10100
  getAllBindings(product_id)
9933
10101
  ]);
9934
- const { computeMetricsFromGraph: computeMetricsFromGraph2 } = await import("./graph-metrics-DCNR7JZN.js");
10102
+ const { computeMetricsFromGraph: computeMetricsFromGraph2 } = await import("./graph-metrics-KLHCMDFT.js");
9935
10103
  const updatedMetrics = computeMetricsFromGraph2(rgrEntities, rgrEdges, rgrConstraints, rgrBindings, updatedPhases);
9936
10104
  await updateGraphMetadata(product_id, {
9937
10105
  ...meta ?? {
@@ -10357,7 +10525,8 @@ Meta: ${JSON.stringify({
10357
10525
  gdpr_ccpa: "GDPR/CCPA",
10358
10526
  owasp_llm: "OWASP LLM Top 10",
10359
10527
  glba: "GLBA",
10360
- ferpa_coppa: "FERPA/COPPA"
10528
+ ferpa_coppa: "FERPA/COPPA",
10529
+ ios_app_store: "iOS App Store"
10361
10530
  };
10362
10531
  const names = result.frameworksLoaded.map((f) => fwLabels[f] || f);
10363
10532
  sections.push(`- Compliance frameworks loaded: **${names.join(", ")}**`);
@@ -78,7 +78,7 @@ import {
78
78
  upsertEdges,
79
79
  upsertEntities,
80
80
  upsertNodes
81
- } from "./chunk-WWTNBUIX.js";
81
+ } from "./chunk-6Y3AEXE3.js";
82
82
  export {
83
83
  addEdges,
84
84
  addEntity,
@@ -14,7 +14,7 @@ import {
14
14
  requirePremiumWithAutoAuth,
15
15
  updateExplorationSession,
16
16
  validateRequestSize
17
- } from "./chunk-WWTNBUIX.js";
17
+ } from "./chunk-6Y3AEXE3.js";
18
18
 
19
19
  // ../mcp/dist/mcp/src/exploration-server.js
20
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -3,7 +3,7 @@ import {
3
3
  computeGenericGraphMetrics,
4
4
  computeGraphMetrics,
5
5
  computeMetricsFromGraph
6
- } from "./chunk-UBBAYTW3.js";
6
+ } from "./chunk-IDSVMCGM.js";
7
7
  export {
8
8
  applyGenericPrior,
9
9
  computeGenericGraphMetrics,
@@ -13,7 +13,7 @@ import {
13
13
  requirePremiumWithAutoAuth,
14
14
  validateAuth,
15
15
  validateRequestSize
16
- } from "./chunk-WWTNBUIX.js";
16
+ } from "./chunk-6Y3AEXE3.js";
17
17
 
18
18
  // ../mcp/dist/mcp/src/integrations-server.js
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -13,7 +13,7 @@ import {
13
13
  mapErrorToMcp,
14
14
  requirePremiumWithAutoAuth,
15
15
  validateRequestSize
16
- } from "./chunk-WWTNBUIX.js";
16
+ } from "./chunk-6Y3AEXE3.js";
17
17
 
18
18
  // ../mcp/dist/mcp/src/output-server.js
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -27,7 +27,7 @@ import {
27
27
  updatePremortem,
28
28
  validateAuth,
29
29
  validateRequestSize
30
- } from "./chunk-WWTNBUIX.js";
30
+ } from "./chunk-6Y3AEXE3.js";
31
31
 
32
32
  // ../mcp/dist/mcp/src/premortem-server.js
33
33
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -21,7 +21,7 @@ import {
21
21
  requirePremiumWithAutoAuth,
22
22
  validateAuth,
23
23
  validateRequestSize
24
- } from "./chunk-WWTNBUIX.js";
24
+ } from "./chunk-6Y3AEXE3.js";
25
25
 
26
26
  // ../mcp/dist/mcp/src/tools-server.js
27
27
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibekiln/cutline-mcp-cli",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",