@vibekiln/cutline-mcp-cli 0.1.2 → 0.2.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'];
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)')
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"];
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,
@@ -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,7 +75,7 @@ import {
74
75
  upsertEntities,
75
76
  upsertNodes,
76
77
  validateRequestSize
77
- } from "./chunk-WWTNBUIX.js";
78
+ } from "./chunk-LI4AZPSJ.js";
78
79
  import {
79
80
  GraphTraverser,
80
81
  computeGenericGraphMetrics,
@@ -6966,10 +6967,22 @@ function deltaStr(current, previous) {
6966
6967
  return " (no change)";
6967
6968
  return diff > 0 ? ` (**+${diff}** since last scan)` : ` (**${diff}** since last scan)`;
6968
6969
  }
6969
- function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai") {
6970
+ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai", hiddenAuditDimensions = []) {
6970
6971
  const m = result.metrics;
6971
6972
  const p = result.previousMetrics;
6972
6973
  const isRescan = !!p;
6974
+ const hiddenSet = new Set(hiddenAuditDimensions.map((d) => String(d).trim().toLowerCase()));
6975
+ const securityVisible = !hiddenSet.has("security");
6976
+ const inferFindingDimension = (category) => {
6977
+ const c = (category ?? "").toLowerCase();
6978
+ if (["reliability"].includes(c))
6979
+ return "reliability";
6980
+ if (["scalability", "performance"].includes(c))
6981
+ return "scalability";
6982
+ if (["code_quality", "general"].includes(c))
6983
+ return "engineering";
6984
+ return "security";
6985
+ };
6973
6986
  const lines = [
6974
6987
  `# Cutline Code Audit`,
6975
6988
  ``,
@@ -6979,8 +6992,43 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
6979
6992
  if (result.frameworksLoaded.length > 0) {
6980
6993
  lines.push(`**Compliance frameworks:** ${result.frameworksLoaded.join(", ")}`);
6981
6994
  }
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) {
6995
+ const scoreRows = [
6996
+ {
6997
+ key: "engineering",
6998
+ label: "Engineering",
6999
+ current: m.engineering_readiness_pct ?? 0,
7000
+ previous: p?.engineering_readiness_pct
7001
+ },
7002
+ {
7003
+ key: "security",
7004
+ label: "Security",
7005
+ current: m.security_readiness_pct ?? 0,
7006
+ previous: p?.security_readiness_pct
7007
+ },
7008
+ {
7009
+ key: "reliability",
7010
+ label: "Reliability",
7011
+ current: m.reliability_readiness_pct ?? 0,
7012
+ previous: p?.reliability_readiness_pct
7013
+ },
7014
+ {
7015
+ key: "scalability",
7016
+ label: "Scalability",
7017
+ current: m.scalability_readiness_pct ?? 0,
7018
+ previous: p?.scalability_readiness_pct
7019
+ }
7020
+ ].filter((row) => !hiddenSet.has(row.key));
7021
+ lines.push(``, `## Readiness Scores`, ``, `| Pillar | Score |${isRescan ? " Change |" : ""}`, `|--------|-------|${isRescan ? "--------|" : ""}`);
7022
+ if (scoreRows.length > 0) {
7023
+ for (const row of scoreRows) {
7024
+ lines.push(`| ${row.label} | ${row.current}% |${isRescan ? deltaStr(row.current, row.previous) + " |" : ""}`);
7025
+ }
7026
+ lines.push(``);
7027
+ } else {
7028
+ lines.push(`| _Hidden by local audit visibility settings_ | - |${isRescan ? " - |" : ""}`, ``);
7029
+ }
7030
+ lines.push(`**Constraints mapped:** ${m.complexity_factors?.nfr_count ?? 0} (${m.complexity_factors?.critical_nfr_count ?? 0} critical)`, `**Binding coverage:** ${result.bindingHealth.coverage_pct}%`);
7031
+ if (securityVisible && result.scaFindings && result.scaFindings.total > 0) {
6984
7032
  const sca = result.scaFindings;
6985
7033
  lines.push(``, `## Dependency Vulnerabilities (SCA)`, ``, `**${sca.total} known vulnerabilities** found (${sca.critical} critical, ${sca.high} high)`, ``);
6986
7034
  for (const v of sca.top) {
@@ -6991,12 +7039,13 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
6991
7039
  lines.push(`- ...and ${sca.total - sca.top.length} more`);
6992
7040
  }
6993
7041
  }
6994
- const totalFindings = result.gatedGapDetails.length;
6995
- const criticalCount = result.gatedGapDetails.filter((g) => g.severity === "critical" || g.severity === "high").length;
7042
+ const visibleFindings = result.gatedGapDetails.filter((g) => !hiddenSet.has(inferFindingDimension(g.category)));
7043
+ const totalFindings = visibleFindings.length;
7044
+ const criticalCount = visibleFindings.filter((g) => g.severity === "critical" || g.severity === "high").length;
6996
7045
  if (totalFindings > 0) {
6997
- const topFinding = result.gatedGapDetails[0];
7046
+ const topFinding = visibleFindings[0];
6998
7047
  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);
7048
+ const remaining = visibleFindings.slice(1);
7000
7049
  if (remaining.length > 0) {
7001
7050
  lines.push(``, `## ${remaining.length} More Finding${remaining.length > 1 ? "s" : ""} Detected`);
7002
7051
  const teaserLimit = Math.min(remaining.length, 5);
@@ -8222,6 +8271,18 @@ Why AI: ${idea.whyAI}`
8222
8271
  }
8223
8272
  const freeAuth = await resolveAuthContextFree(scanArgs.auth_token);
8224
8273
  const uid = freeAuth.effectiveUid;
8274
+ const hiddenAuditDimensions = getHiddenAuditDimensions();
8275
+ const hiddenSet = new Set(hiddenAuditDimensions);
8276
+ const inferFindingDimension = (category) => {
8277
+ const c = (category ?? "").toLowerCase();
8278
+ if (["reliability"].includes(c))
8279
+ return "reliability";
8280
+ if (["scalability", "performance"].includes(c))
8281
+ return "scalability";
8282
+ if (["code_quality", "general"].includes(c))
8283
+ return "engineering";
8284
+ return "security";
8285
+ };
8225
8286
  {
8226
8287
  const rateInfo = await getScanRateLimit();
8227
8288
  const now = /* @__PURE__ */ new Date();
@@ -8258,24 +8319,33 @@ Why AI: ${idea.whyAI}`
8258
8319
  let reportId;
8259
8320
  let reportSiteUrl = "https://thecutline.ai";
8260
8321
  try {
8322
+ const reportMetrics = {};
8323
+ if (!hiddenSet.has("engineering")) {
8324
+ reportMetrics.engineering_readiness_pct = result.metrics.engineering_readiness_pct ?? 0;
8325
+ }
8326
+ if (!hiddenSet.has("security")) {
8327
+ reportMetrics.security_readiness_pct = result.metrics.security_readiness_pct ?? 0;
8328
+ }
8329
+ if (!hiddenSet.has("reliability")) {
8330
+ reportMetrics.reliability_readiness_pct = result.metrics.reliability_readiness_pct ?? 0;
8331
+ }
8332
+ if (!hiddenSet.has("scalability")) {
8333
+ reportMetrics.scalability_readiness_pct = result.metrics.scalability_readiness_pct ?? 0;
8334
+ }
8335
+ const visibleFindings = result.gatedGapDetails.filter((f) => !hiddenSet.has(inferFindingDimension(f.category)));
8261
8336
  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
- },
8337
+ metrics: reportMetrics,
8268
8338
  ecosystem: result.ecosystem,
8269
8339
  binding_coverage_pct: result.bindingHealth.coverage_pct,
8270
8340
  frameworks_loaded: result.frameworksLoaded,
8271
8341
  sensitive_data_count: result.sensitiveDataCount,
8272
- findings: result.gatedGapDetails.map((f) => ({
8342
+ findings: visibleFindings.map((f) => ({
8273
8343
  title: f.title,
8274
8344
  severity: f.severity,
8275
8345
  category: f.category,
8276
8346
  description: f.description
8277
8347
  })),
8278
- sca_summary: result.scaFindings ? {
8348
+ sca_summary: !hiddenSet.has("security") && result.scaFindings ? {
8279
8349
  total: result.scaFindings.total,
8280
8350
  critical: result.scaFindings.critical,
8281
8351
  high: result.scaFindings.high
@@ -8287,7 +8357,7 @@ Why AI: ${idea.whyAI}`
8287
8357
  console.error("[code_audit] Report persistence failed (non-fatal):", e);
8288
8358
  }
8289
8359
  return {
8290
- content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl) }]
8360
+ content: [{ type: "text", text: formatAuditOutput(result, reportId, reportSiteUrl, hiddenAuditDimensions) }]
8291
8361
  };
8292
8362
  }
8293
8363
  const authCtx = await resolveAuthContext(args.auth_token);
@@ -78,7 +78,7 @@ import {
78
78
  upsertEdges,
79
79
  upsertEntities,
80
80
  upsertNodes
81
- } from "./chunk-WWTNBUIX.js";
81
+ } from "./chunk-LI4AZPSJ.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-LI4AZPSJ.js";
18
18
 
19
19
  // ../mcp/dist/mcp/src/exploration-server.js
20
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -13,7 +13,7 @@ import {
13
13
  requirePremiumWithAutoAuth,
14
14
  validateAuth,
15
15
  validateRequestSize
16
- } from "./chunk-WWTNBUIX.js";
16
+ } from "./chunk-LI4AZPSJ.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-LI4AZPSJ.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-LI4AZPSJ.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-LI4AZPSJ.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.2.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",