@vibekiln/cutline-mcp-cli 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,16 @@ cutline-mcp setup
35
35
 
36
36
  Then ask your AI agent: **"use cutline"**
37
37
 
38
+ For test-heavy flows, use the explicit bootstrap + route:
39
+
40
+ ```bash
41
+ npx -y @vibekiln/cutline-mcp-cli@latest setup
42
+ ```
43
+
44
+ Then ask:
45
+ - `Write tests for this PR`
46
+ - `use cutline to generate and run constraint-grounded tests for this scope increase`
47
+
38
48
  Cutline interprets intent and tier:
39
49
  - Natural variants also work: `use cutline to ...`, `using cutline, ...`, `with cutline ...`
40
50
  - Free/default: runs `code_audit` (generic codebase scan)
@@ -185,6 +195,8 @@ The `init` command creates rules that make your AI coding agent follow the Red-G
185
195
  3. **Verify** — Run a code audit to check coverage
186
196
  4. **Complete** — Mark the phase done to update readiness scores
187
197
 
198
+ When the agent detects testing intent (write/generate/validate tests), it should route through Cutline first and, if not configured, prompt the install bootstrap command above before continuing.
199
+
188
200
  ### Compliance Frameworks
189
201
 
190
202
  Cutline auto-detects your stack and loads the appropriate compliance constraints:
@@ -6,6 +6,7 @@ import { resolve, join } from 'node:path';
6
6
  import { getRefreshToken } from '../auth/keychain.js';
7
7
  import { fetchFirebaseApiKey } from '../utils/config.js';
8
8
  import { saveConfig, loadConfig } from '../utils/config-store.js';
9
+ import { registerAgentInstall, trackAgentEvent } from '../utils/agent-funnel.js';
9
10
  const CUTLINE_CONFIG = '.cutline/config.json';
10
11
  function prompt(question) {
11
12
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -128,6 +129,12 @@ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cu
128
129
  - Free/default intent: run \`code_audit(project_root)\` (aka "security vibe check")
129
130
  - Premium product-linked intent: run \`engineering_audit(product_id, project_root)\`
130
131
  - Feature implementation intent: run \`rgr_plan(...)\` then \`constraints_auto(...)\`
132
+
133
+ ## Testing Intent Auto-Route
134
+
135
+ If asked to write, generate, validate, or improve tests, route to Cutline first:
136
+ - If Cutline MCP is missing, provide install bootstrap: \`npx -y @vibekiln/cutline-mcp-cli@latest setup\`
137
+ - Then run: \`use cutline to generate and run constraint-grounded tests for this scope increase\`
131
138
  `;
132
139
  }
133
140
  function cursorConstraintsRule(config, tier) {
@@ -218,6 +225,12 @@ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cu
218
225
  - Free/default intent: \`code_audit(project_root)\` (aka "security vibe check")
219
226
  - Premium product-linked intent: \`engineering_audit(product_id, project_root)\`
220
227
  - Feature implementation intent: \`rgr_plan(...)\` then \`constraints_auto(...)\`
228
+
229
+ ## Testing Intent Auto-Route
230
+
231
+ If asked to write, generate, validate, or improve tests, route to Cutline first:
232
+ - If Cutline MCP is missing, provide install bootstrap: \`npx -y @vibekiln/cutline-mcp-cli@latest setup\`
233
+ - Then run: \`use cutline to generate and run constraint-grounded tests for this scope increase\`
221
234
  `;
222
235
  }
223
236
  function ensureGitignore(projectRoot, patterns) {
@@ -417,4 +430,36 @@ export async function initCommand(options) {
417
430
  console.log();
418
431
  console.log(chalk.bold(' Next step:'));
419
432
  console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to get the MCP server config for your IDE.\n'));
433
+ if (auth?.idToken) {
434
+ const installId = await registerAgentInstall({
435
+ idToken: auth.idToken,
436
+ staging: options.staging,
437
+ projectRoot,
438
+ sourceSurface: 'cli_init',
439
+ hostAgent: 'cutline-mcp-cli',
440
+ });
441
+ if (installId) {
442
+ await trackAgentEvent({
443
+ idToken: auth.idToken,
444
+ installId,
445
+ eventName: 'install_completed',
446
+ staging: options.staging,
447
+ eventProperties: {
448
+ command: 'init',
449
+ tier,
450
+ has_product_graph: Boolean(config?.product_id),
451
+ },
452
+ });
453
+ await trackAgentEvent({
454
+ idToken: auth.idToken,
455
+ installId,
456
+ eventName: 'first_tool_call_success',
457
+ staging: options.staging,
458
+ eventProperties: {
459
+ command: 'init',
460
+ generated_rules: filesWritten.length,
461
+ },
462
+ });
463
+ }
464
+ }
420
465
  }
@@ -0,0 +1,9 @@
1
+ interface PolicyInitOptions {
2
+ projectRoot?: string;
3
+ force?: boolean;
4
+ minSecurityScore?: string;
5
+ maxAssuranceAgeHours?: string;
6
+ allowUnsignedAssurance?: boolean;
7
+ }
8
+ export declare function policyInitCommand(options: PolicyInitOptions): Promise<void>;
9
+ export {};
@@ -0,0 +1,51 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ function parseNumber(raw, fallback, min, max) {
5
+ const value = Number(raw);
6
+ if (!Number.isFinite(value))
7
+ return fallback;
8
+ return Math.max(min, Math.min(max, Math.floor(value)));
9
+ }
10
+ export async function policyInitCommand(options) {
11
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
12
+ const policyPath = join(projectRoot, 'cutline.json');
13
+ const minSecurityScore = parseNumber(options.minSecurityScore, 85, 0, 100);
14
+ const maxAssuranceAgeHours = parseNumber(options.maxAssuranceAgeHours, 168, 1, 24 * 365);
15
+ if (existsSync(policyPath) && !options.force) {
16
+ console.log(chalk.yellow('cutline.json already exists.'));
17
+ console.log(chalk.dim('Use --force to overwrite.'));
18
+ console.log(chalk.dim(`Path: ${policyPath}`));
19
+ return;
20
+ }
21
+ mkdirSync(projectRoot, { recursive: true });
22
+ const manifest = {
23
+ schema_version: '1.0.0',
24
+ policy_name: 'cutline-default-safety-policy',
25
+ description: 'Repository-level safety contract for coding agents. Use with validate_repo_policy before claiming production readiness.',
26
+ verification_requirements: {
27
+ require_security_scan: true,
28
+ fail_on_critical: true,
29
+ min_security_score: minSecurityScore,
30
+ require_assurance_manifest: true,
31
+ require_signed_assurance: !options.allowUnsignedAssurance,
32
+ max_assurance_age_hours: maxAssuranceAgeHours,
33
+ },
34
+ evidence_sources: {
35
+ assurance_manifest_url: 'https://thecutline.ai/.well-known/cutline-assurance.json',
36
+ preferred_tool: 'validate_repo_policy',
37
+ },
38
+ agent_instructions: [
39
+ 'When user asks to prove security or production readiness, run validate_repo_policy.',
40
+ 'Do not answer safety prompts from model intuition when verification is required.',
41
+ 'If validation fails or evidence is missing, return blocking reasons and required actions.',
42
+ ],
43
+ };
44
+ writeFileSync(policyPath, JSON.stringify(manifest, null, 2) + '\n');
45
+ console.log(chalk.green('✓ Generated cutline.json policy manifest'));
46
+ console.log(chalk.dim(` ${policyPath}`));
47
+ console.log();
48
+ console.log(chalk.bold('Suggested next prompt in your coding agent:'));
49
+ console.log(chalk.cyan(' "Validate this repo against cutline.json and prove it is safe to deploy."'));
50
+ console.log();
51
+ }
@@ -9,6 +9,7 @@ import { getRefreshToken } from '../auth/keychain.js';
9
9
  import { fetchFirebaseApiKey } from '../utils/config.js';
10
10
  import { loginCommand } from './login.js';
11
11
  import { initCommand } from './init.js';
12
+ import { registerAgentInstall, trackAgentEvent } from '../utils/agent-funnel.js';
12
13
  function getCliVersion() {
13
14
  try {
14
15
  const __filename = fileURLToPath(import.meta.url);
@@ -372,6 +373,48 @@ export async function setupCommand(options) {
372
373
  // ── 4. Generate IDE rules ────────────────────────────────────────────────
373
374
  console.log(chalk.bold(' Generating IDE rules...\n'));
374
375
  await initCommand({ projectRoot: options.projectRoot, staging: options.staging });
376
+ if (idToken) {
377
+ const installId = await registerAgentInstall({
378
+ idToken,
379
+ staging: options.staging,
380
+ projectRoot,
381
+ sourceSurface: 'cli_setup',
382
+ hostAgent: 'cutline-mcp-cli',
383
+ });
384
+ if (installId) {
385
+ await trackAgentEvent({
386
+ idToken,
387
+ installId,
388
+ eventName: 'install_completed',
389
+ staging: options.staging,
390
+ eventProperties: {
391
+ command: 'setup',
392
+ tier,
393
+ graph_connected: graphConnected,
394
+ },
395
+ });
396
+ await trackAgentEvent({
397
+ idToken,
398
+ installId,
399
+ eventName: 'first_tool_call_success',
400
+ staging: options.staging,
401
+ eventProperties: {
402
+ command: 'setup',
403
+ flow: 'onboarding',
404
+ },
405
+ });
406
+ await trackAgentEvent({
407
+ idToken,
408
+ installId,
409
+ eventName: 'agent_cutline_install_completed',
410
+ staging: options.staging,
411
+ eventProperties: {
412
+ command: 'setup',
413
+ route: 'testing_rgr',
414
+ },
415
+ });
416
+ }
417
+ }
375
418
  // ── 5. Claude Code one-liners ────────────────────────────────────────────
376
419
  console.log(chalk.bold(' Claude Code one-liner alternative:\n'));
377
420
  console.log(chalk.dim(' If you prefer `claude mcp add` instead of ~/.claude.json:\n'));
@@ -391,9 +434,11 @@ export async function setupCommand(options) {
391
434
  const items = [
392
435
  { cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline infers intent and routes to the right flow' },
393
436
  { cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
437
+ { cmd: 'Write tests for this PR', desc: 'Testing intent shortcut — Cutline should route to graph-grounded test generation + RGR verification loop' },
394
438
  { cmd: 'Plan this feature with constraints from my product', desc: 'RGR plan — constraint-aware implementation roadmap' },
395
439
  { cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability (generic, not product-linked)' },
396
440
  { cmd: 'Run an engineering vibe check for my product', desc: 'Premium deep vibe check (`engineering_audit`) — product-linked analysis + RGR remediation plan' },
441
+ { cmd: 'use cutline to generate and run tests for this scope increase', desc: 'Preferred prompt for pervasive red/green loop execution' },
397
442
  { cmd: 'Check constraints for src/api/upload.ts', desc: 'Get NFR boundaries for a specific file' },
398
443
  { cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
399
444
  { cmd: 'What does my persona think about X?', desc: 'AI persona feedback on features' },
@@ -408,6 +453,8 @@ export async function setupCommand(options) {
408
453
  const items = [
409
454
  { cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline routes to the highest-value free flow for your intent' },
410
455
  { cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability scan (3/month free)' },
456
+ { cmd: 'Write tests for this PR', desc: 'Testing intent shortcut — prompts install/setup guidance if Cutline MCP is missing' },
457
+ { cmd: 'use cutline to generate tests for this scope increase', desc: 'Runs free-tier test-oriented routing and verification guidance where available' },
411
458
  { cmd: 'Explore a product idea', desc: 'Free 6-act discovery flow to identify pain points and opportunities' },
412
459
  { cmd: 'Continue my exploration session', desc: 'Resume and refine an existing free exploration conversation' },
413
460
  ];
@@ -421,5 +468,7 @@ export async function setupCommand(options) {
421
468
  }
422
469
  console.log();
423
470
  console.log(chalk.dim(` cutline-mcp v${version} · docs: https://thecutline.ai/docs/setup`));
471
+ console.log(chalk.dim(' Testing bootstrap:'), chalk.cyan('npx -y @vibekiln/cutline-mcp-cli@latest setup'));
472
+ console.log(chalk.dim(' Optional repo policy contract:'), chalk.cyan('cutline-mcp policy-init'));
424
473
  console.log();
425
474
  }
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { upgradeCommand } from './commands/upgrade.js';
10
10
  import { serveCommand } from './commands/serve.js';
11
11
  import { setupCommand } from './commands/setup.js';
12
12
  import { initCommand } from './commands/init.js';
13
+ import { policyInitCommand } from './commands/policy-init.js';
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = dirname(__filename);
15
16
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -78,4 +79,19 @@ program
78
79
  .option('--project-root <path>', 'Project root directory (default: cwd)')
79
80
  .option('--staging', 'Use staging environment')
80
81
  .action((opts) => initCommand({ projectRoot: opts.projectRoot, staging: opts.staging }));
82
+ program
83
+ .command('policy-init')
84
+ .description('Generate repository cutline.json policy manifest for deterministic safety verification')
85
+ .option('--project-root <path>', 'Project root directory (default: cwd)')
86
+ .option('--force', 'Overwrite existing cutline.json if present')
87
+ .option('--min-security-score <number>', 'Minimum security score required to pass (default: 85)')
88
+ .option('--max-assurance-age-hours <number>', 'Maximum assurance artifact age in hours (default: 168)')
89
+ .option('--allow-unsigned-assurance', 'Do not require signed assurance artifact')
90
+ .action((opts) => policyInitCommand({
91
+ projectRoot: opts.projectRoot,
92
+ force: Boolean(opts.force),
93
+ minSecurityScore: opts.minSecurityScore,
94
+ maxAssuranceAgeHours: opts.maxAssuranceAgeHours,
95
+ allowUnsignedAssurance: Boolean(opts.allowUnsignedAssurance),
96
+ }));
81
97
  program.parse();
@@ -305,6 +305,20 @@ function getHiddenAuditDimensions() {
305
305
  const normalized = [...new Set(hidden.map((d) => String(d).trim().toLowerCase()).filter((d) => allowed.has(d)))];
306
306
  return normalized;
307
307
  }
308
+ function getStoredInstallId(options) {
309
+ const config = readLocalCutlineConfig();
310
+ if (!config)
311
+ return null;
312
+ const preferred = options?.environment === "staging" ? config.agentInstallIdStaging : config.agentInstallId;
313
+ if (typeof preferred === "string" && preferred.trim()) {
314
+ return preferred.trim();
315
+ }
316
+ const fallback = options?.environment === "staging" ? config.agentInstallId : config.agentInstallIdStaging;
317
+ if (typeof fallback === "string" && fallback.trim()) {
318
+ return fallback.trim();
319
+ }
320
+ return null;
321
+ }
308
322
  function getStoredApiKey(options) {
309
323
  const includeConfig = options?.includeConfig ?? true;
310
324
  if (process.env.CUTLINE_API_KEY) {
@@ -1010,6 +1024,7 @@ export {
1010
1024
  validateAuth,
1011
1025
  resolveAuthContext,
1012
1026
  getHiddenAuditDimensions,
1027
+ getStoredInstallId,
1013
1028
  requirePremiumWithAutoAuth,
1014
1029
  resolveAuthContextFree,
1015
1030
  getPublicSiteUrlForCurrentAuth,
@@ -52,6 +52,7 @@ import {
52
52
  getPremortem,
53
53
  getPublicSiteUrlForCurrentAuth,
54
54
  getScanRateLimit,
55
+ getStoredInstallId,
55
56
  getTemplate,
56
57
  getTestCasesForEntity,
57
58
  hasConstraints,
@@ -75,7 +76,7 @@ import {
75
76
  upsertEntities,
76
77
  upsertNodes,
77
78
  validateRequestSize
78
- } from "./chunk-X2B5QUWO.js";
79
+ } from "./chunk-RUCYK3TR.js";
79
80
  import {
80
81
  GraphTraverser,
81
82
  computeGenericGraphMetrics,
@@ -4455,6 +4456,17 @@ var UNIVERSAL_CONSTRAINTS = [
4455
4456
  file_patterns: ["**/auth/**", "**/api/session*", "**/api/login*", "**/middleware/**", "**/config/**"],
4456
4457
  framework: "baseline"
4457
4458
  },
4459
+ {
4460
+ id_suffix: "password_reset_token_expiry",
4461
+ category: "security",
4462
+ summary: "Password reset tokens/links MUST be single-use and time-limited. Expired or reused reset tokens must fail closed.",
4463
+ keywords: ["password-reset", "token", "expiry", "single-use", "account-takeover"],
4464
+ severity: "critical",
4465
+ action: "Enforce reset token TTL and one-time use semantics. Invalidate outstanding reset tokens after successful password change.",
4466
+ checklist_ref: "A10",
4467
+ file_patterns: ["**/auth/**", "**/api/reset*", "**/api/forgot*", "**/api/password*"],
4468
+ framework: "baseline"
4469
+ },
4458
4470
  {
4459
4471
  id_suffix: "backup_testing",
4460
4472
  category: "stability",
@@ -4700,6 +4712,83 @@ var RELIABILITY_CONSTRAINTS = [
4700
4712
  checklist_ref: "J10",
4701
4713
  file_patterns: ["**/utils/**", "**/lib/**", "**/services/**", "**/api/**"],
4702
4714
  framework: "baseline"
4715
+ },
4716
+ {
4717
+ id_suffix: "startup_env_schema_validation",
4718
+ category: "stability",
4719
+ summary: "Runtime environment variables MUST be validated against an explicit schema at startup, and app boot must fail fast on invalid critical config.",
4720
+ keywords: ["env", "startup", "schema", "validation", "fail-fast", "configuration"],
4721
+ severity: "critical",
4722
+ action: "Create startup env schema checks for server and public runtime variables. Crash startup when required production config is missing or malformed.",
4723
+ checklist_ref: "J11",
4724
+ file_patterns: ["**/config/**", "**/env/**", "**/server/**", "**/.env*"],
4725
+ framework: "baseline"
4726
+ },
4727
+ {
4728
+ id_suffix: "ui_error_boundaries",
4729
+ category: "stability",
4730
+ summary: "Critical user-facing UI surfaces MUST be wrapped in error boundaries to prevent full-app white screens during component crashes.",
4731
+ keywords: ["error-boundary", "react", "ui", "crash", "fallback", "reliability"],
4732
+ severity: "warning",
4733
+ action: "Add error boundaries around major routes/layouts and high-risk widgets. Provide fallback UI and telemetry when boundaries catch errors.",
4734
+ checklist_ref: "J12",
4735
+ file_patterns: ["**/components/**", "**/pages/**", "**/app/**", "**/*error*"],
4736
+ framework: "baseline"
4737
+ },
4738
+ {
4739
+ id_suffix: "health_readiness_endpoints",
4740
+ category: "stability",
4741
+ summary: "Services MUST expose dedicated liveness and readiness endpoints (e.g., /api/health and /api/readyz) for monitoring and deployment safety checks.",
4742
+ keywords: ["health", "readyz", "liveness", "readiness", "monitoring", "probe"],
4743
+ severity: "critical",
4744
+ action: "Implement lightweight health/readiness endpoints with no sensitive payload data. Integrate endpoints into uptime monitoring and deployment probes.",
4745
+ checklist_ref: "J13",
4746
+ file_patterns: ["**/api/health*", "**/api/ready*", "**/monitoring/**", "**/deploy/**"],
4747
+ framework: "baseline"
4748
+ },
4749
+ {
4750
+ id_suffix: "structured_production_logging",
4751
+ category: "stability",
4752
+ summary: "Production logging MUST be structured and include correlation/request IDs, with automatic redaction of tokens, API keys, and credentials.",
4753
+ keywords: ["logging", "structured", "correlation-id", "request-id", "redaction", "observability"],
4754
+ severity: "critical",
4755
+ action: "Emit JSON logs in production and propagate request/correlation IDs across handlers and background jobs. Apply secret-redaction middleware before log emission.",
4756
+ checklist_ref: "J14",
4757
+ file_patterns: ["**/logger/**", "**/api/**", "**/middleware/**", "**/monitoring/**"],
4758
+ framework: "baseline"
4759
+ },
4760
+ {
4761
+ id_suffix: "typed_ai_generated_code",
4762
+ category: "stability",
4763
+ summary: "AI-generated production code MUST use TypeScript (or equivalent static typing) and pass strict type-check gates before merge.",
4764
+ keywords: ["ai-generated", "typescript", "typing", "typecheck", "ci-gate", "quality"],
4765
+ severity: "warning",
4766
+ action: "Require strict type-check in CI for generated code changes and block merges on type errors. Prefer typed templates for agent-generated modules.",
4767
+ checklist_ref: "J15",
4768
+ file_patterns: ["**/*.ts", "**/*.tsx", "**/ai/**", "**/agents/**", "**/.github/**"],
4769
+ framework: "baseline"
4770
+ },
4771
+ {
4772
+ id_suffix: "async_email_dispatch",
4773
+ category: "performance",
4774
+ summary: "Transactional emails SHOULD be dispatched asynchronously (queue/background workers) so request handlers do not block on provider latency.",
4775
+ keywords: ["email", "async", "queue", "worker", "latency", "smtp", "request-path"],
4776
+ severity: "warning",
4777
+ action: "Move email delivery to async jobs/queues and return request responses before provider completion. Add retry/backoff for transient send failures.",
4778
+ checklist_ref: "J16",
4779
+ file_patterns: ["**/api/**", "**/jobs/**", "**/workers/**", "**/email/**", "**/notifications/**"],
4780
+ framework: "baseline"
4781
+ },
4782
+ {
4783
+ id_suffix: "cdn_media_delivery",
4784
+ category: "performance",
4785
+ summary: "User-uploaded media MUST be stored in object storage and delivered through CDN caching, not served directly from app servers.",
4786
+ keywords: ["cdn", "media", "uploads", "object-storage", "cache", "bandwidth"],
4787
+ severity: "warning",
4788
+ action: "Store uploads in object storage (S3/GCS/etc.) and serve via CDN URLs with cache headers. Keep app servers out of large media delivery paths.",
4789
+ checklist_ref: "J17",
4790
+ file_patterns: ["**/api/upload*", "**/storage/**", "**/media/**", "**/cdn/**", "**/config/**"],
4791
+ framework: "baseline"
4703
4792
  }
4704
4793
  ];
4705
4794
  var IAC_CONSTRAINTS = [
@@ -6507,6 +6596,11 @@ async function handleCodeAudit(args, deps) {
6507
6596
  },
6508
6597
  sensitiveDataCount: scanResult.sensitive_data.fields.length,
6509
6598
  rgrPlan,
6599
+ rgrAutoTrigger: {
6600
+ enabled: true,
6601
+ onScopeIncrease: true,
6602
+ executionMode: "local_vitest"
6603
+ },
6510
6604
  securityGaps: graphAnalysis.securityGaps
6511
6605
  }
6512
6606
  };
@@ -7398,6 +7492,132 @@ async function withLlmMonitor(model, fn) {
7398
7492
  }
7399
7493
  }
7400
7494
 
7495
+ // ../mcp/dist/mcp/src/shared/repo-policy.js
7496
+ function buildRepoPolicyRequirements(manifest) {
7497
+ return {
7498
+ require_security_scan: Boolean(manifest?.verification_requirements?.require_security_scan ?? true),
7499
+ fail_on_critical: Boolean(manifest?.verification_requirements?.fail_on_critical ?? true),
7500
+ min_security_score: Number(manifest?.verification_requirements?.min_security_score ?? 85),
7501
+ require_assurance_manifest: Boolean(manifest?.verification_requirements?.require_assurance_manifest ?? true),
7502
+ require_signed_assurance: Boolean(manifest?.verification_requirements?.require_signed_assurance ?? true),
7503
+ max_assurance_age_hours: Number(manifest?.verification_requirements?.max_assurance_age_hours ?? 168)
7504
+ };
7505
+ }
7506
+ function buildRepoPolicyObserved(observed) {
7507
+ return {
7508
+ security_score: Number(observed?.security_score ?? Number.NaN),
7509
+ critical_findings_count: Number(observed?.critical_findings_count ?? Number.NaN),
7510
+ assurance_available: observed?.assurance_available,
7511
+ assurance_signed: observed?.assurance_signed,
7512
+ assurance_age_hours: Number(observed?.assurance_age_hours ?? Number.NaN)
7513
+ };
7514
+ }
7515
+ function evaluateRepoPolicy(requirements, observed) {
7516
+ const checks = [];
7517
+ const blockingReasons = [];
7518
+ const requiredActions = [];
7519
+ const evaluate = (id, pass, passMessage, failMessage, unknownMessage) => {
7520
+ if (pass === null) {
7521
+ checks.push({ id, status: "unknown", message: unknownMessage });
7522
+ return;
7523
+ }
7524
+ if (pass) {
7525
+ checks.push({ id, status: "pass", message: passMessage });
7526
+ } else {
7527
+ checks.push({ id, status: "fail", message: failMessage });
7528
+ blockingReasons.push(failMessage);
7529
+ }
7530
+ };
7531
+ if (requirements.require_security_scan) {
7532
+ const hasScore = Number.isFinite(observed.security_score);
7533
+ evaluate("security_score_minimum", hasScore ? observed.security_score >= requirements.min_security_score : null, `Security score ${observed.security_score} meets minimum ${requirements.min_security_score}.`, `Security score ${hasScore ? observed.security_score : "unknown"} below minimum ${requirements.min_security_score}.`, "Security score not provided in observed evidence.");
7534
+ if (!hasScore) {
7535
+ requiredActions.push("Provide observed.security_score from the latest audit.");
7536
+ }
7537
+ }
7538
+ if (requirements.fail_on_critical) {
7539
+ const hasCritical = Number.isFinite(observed.critical_findings_count);
7540
+ evaluate("critical_findings_zero", hasCritical ? observed.critical_findings_count <= 0 : null, "No unresolved critical/high findings.", `${hasCritical ? observed.critical_findings_count : "Unknown"} unresolved critical/high findings detected.`, "Critical findings count not provided in observed evidence.");
7541
+ if (!hasCritical) {
7542
+ requiredActions.push("Provide observed.critical_findings_count from the latest audit.");
7543
+ }
7544
+ }
7545
+ if (requirements.require_assurance_manifest) {
7546
+ const available = typeof observed.assurance_available === "boolean" ? observed.assurance_available : null;
7547
+ evaluate("assurance_available", available, "Assurance manifest is available.", "Assurance manifest is required but unavailable.", "Assurance availability not provided in observed evidence.");
7548
+ if (available === null) {
7549
+ requiredActions.push("Provide observed.assurance_available from assurance retrieval.");
7550
+ }
7551
+ }
7552
+ if (requirements.require_signed_assurance) {
7553
+ const signed = typeof observed.assurance_signed === "boolean" ? observed.assurance_signed : null;
7554
+ evaluate("assurance_signed", signed, "Assurance manifest signature is present/valid.", "Policy requires signed assurance artifact.", "Assurance signature status not provided in observed evidence.");
7555
+ if (signed === null) {
7556
+ requiredActions.push("Provide observed.assurance_signed after verification.");
7557
+ }
7558
+ }
7559
+ if (requirements.max_assurance_age_hours > 0) {
7560
+ const hasAge = Number.isFinite(observed.assurance_age_hours);
7561
+ evaluate("assurance_freshness", hasAge ? observed.assurance_age_hours <= requirements.max_assurance_age_hours : null, `Assurance age ${observed.assurance_age_hours}h within ${requirements.max_assurance_age_hours}h threshold.`, `Assurance age ${hasAge ? observed.assurance_age_hours : "unknown"}h exceeds ${requirements.max_assurance_age_hours}h threshold.`, "Assurance age not provided in observed evidence.");
7562
+ if (!hasAge) {
7563
+ requiredActions.push("Provide observed.assurance_age_hours.");
7564
+ }
7565
+ }
7566
+ const failedChecks = checks.filter((c) => c.status === "fail").length;
7567
+ const unknownChecks = checks.filter((c) => c.status === "unknown").length;
7568
+ const status = failedChecks > 0 ? "blocked" : unknownChecks > 0 ? "insufficient_evidence" : "verified";
7569
+ if (status !== "verified" && requiredActions.length === 0) {
7570
+ requiredActions.push("Re-run validate_repo_policy with complete observed evidence fields.");
7571
+ }
7572
+ return {
7573
+ status,
7574
+ checks,
7575
+ blocking_reasons: blockingReasons,
7576
+ required_actions: [...new Set(requiredActions)],
7577
+ verification_requirements: requirements
7578
+ };
7579
+ }
7580
+
7581
+ // ../mcp/dist/mcp/src/shared/repo-policy-response.js
7582
+ function resolveTelemetryAttribution(input) {
7583
+ if (input.providedInstallId) {
7584
+ return "provided";
7585
+ }
7586
+ if (input.resolvedInstallId) {
7587
+ return "auto_resolved";
7588
+ }
7589
+ return "missing";
7590
+ }
7591
+ function buildInvalidPolicyResponse(input) {
7592
+ return {
7593
+ status: "invalid_policy",
7594
+ certification_id: `policy_${(input.now || /* @__PURE__ */ new Date()).getTime()}`,
7595
+ blocking_reasons: [`Policy manifest not found at ${input.manifestPath}`],
7596
+ required_actions: [
7597
+ "Create policy manifest: cutline-mcp policy-init",
7598
+ "Commit cutline.json and re-run validate_repo_policy"
7599
+ ],
7600
+ telemetry_attribution: input.telemetryAttribution,
7601
+ generated_at: (input.now || /* @__PURE__ */ new Date()).toISOString()
7602
+ };
7603
+ }
7604
+ function buildPolicyVerdictResponse(input) {
7605
+ return {
7606
+ status: input.verdict.status,
7607
+ certification_id: `policy_${(input.now || /* @__PURE__ */ new Date()).getTime()}`,
7608
+ manifest_path: input.manifestPath,
7609
+ policy_name: input.policyName || "cutline-policy",
7610
+ schema_version: input.schemaVersion || "unknown",
7611
+ verification_requirements: input.verdict.verification_requirements,
7612
+ observed_evidence: input.observedEvidence,
7613
+ telemetry_attribution: input.telemetryAttribution,
7614
+ checks: input.verdict.checks,
7615
+ blocking_reasons: input.verdict.blocking_reasons,
7616
+ required_actions: input.verdict.required_actions,
7617
+ generated_at: (input.now || /* @__PURE__ */ new Date()).toISOString()
7618
+ };
7619
+ }
7620
+
7401
7621
  // ../mcp/dist/mcp/src/cutline-server.js
7402
7622
  function mcpAudit(entry) {
7403
7623
  console.error(JSON.stringify({
@@ -7407,6 +7627,70 @@ function mcpAudit(entry) {
7407
7627
  ...entry
7408
7628
  }));
7409
7629
  }
7630
+ async function emitPolicyGateEvent(input) {
7631
+ if (!input.authToken || !input.installId)
7632
+ return;
7633
+ const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
7634
+ const eventName = input.status === "verified" ? "policy_gate_passed" : "policy_gate_blocked";
7635
+ try {
7636
+ await fetch(`${siteUrl}/api/agent/event`, {
7637
+ method: "POST",
7638
+ headers: {
7639
+ "Content-Type": "application/json",
7640
+ Authorization: `Bearer ${input.authToken}`
7641
+ },
7642
+ body: JSON.stringify({
7643
+ install_id: input.installId,
7644
+ event_name: eventName,
7645
+ event_properties: {
7646
+ status: input.status,
7647
+ policy_name: input.policyName || "cutline-policy",
7648
+ failed_checks_count: input.failedChecks,
7649
+ unknown_checks_count: input.unknownChecks,
7650
+ blocking_reasons_count: input.blockingReasonsCount
7651
+ }
7652
+ })
7653
+ });
7654
+ } catch {
7655
+ }
7656
+ }
7657
+ async function emitAgentEvent(input) {
7658
+ if (!input.authToken || !input.installId)
7659
+ return;
7660
+ const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
7661
+ try {
7662
+ await fetch(`${siteUrl}/api/agent/event`, {
7663
+ method: "POST",
7664
+ headers: {
7665
+ "Content-Type": "application/json",
7666
+ Authorization: `Bearer ${input.authToken}`
7667
+ },
7668
+ body: JSON.stringify({
7669
+ install_id: input.installId,
7670
+ event_name: input.eventName,
7671
+ event_properties: input.eventProperties || {}
7672
+ })
7673
+ });
7674
+ } catch {
7675
+ }
7676
+ }
7677
+ function isTestingIntentPrompt(prompt) {
7678
+ return /\b(test|tests|testing|vitest|jest|coverage|unit test|integration test|e2e|spec file|assertion)\b/i.test(prompt);
7679
+ }
7680
+ function buildTestingIntentHintResponse(prompt) {
7681
+ return {
7682
+ detected: true,
7683
+ intent: "testing",
7684
+ route: "cutline_testing_rgr",
7685
+ prompts: [
7686
+ "use cutline to generate tests for this scope increase",
7687
+ "use cutline to run the RGR loop for this change",
7688
+ "use cutline to evaluate merge gates for this test run"
7689
+ ],
7690
+ bootstrap_if_missing: "npx -y @vibekiln/cutline-mcp-cli@latest setup",
7691
+ source_prompt_excerpt: prompt.slice(0, 180)
7692
+ };
7693
+ }
7410
7694
  var DEFAULT_MODEL = process.env.MODEL_ID || "gemini-2.5-pro";
7411
7695
  var GOVERNANCE_ENFORCEMENT = (process.env.CUTLINE_GOVERNANCE_ENFORCEMENT || "advisory").toLowerCase() === "enforced";
7412
7696
  function buildGovernanceEnvelope(input) {
@@ -7435,21 +7719,26 @@ var ACT_NAMES = {
7435
7719
  };
7436
7720
  function assessScopeExpansionIntent(params) {
7437
7721
  const task = (params.task_description || "").trim();
7722
+ const codeSnippet = (params.code_snippet || "").trim();
7723
+ const intentText = `${task}
7724
+ ${codeSnippet}`.trim();
7438
7725
  const filePaths = params.file_paths || [];
7439
7726
  const reasons = [];
7440
7727
  let confidence = 0;
7441
7728
  const explicitScopePatterns = [
7442
7729
  /\b(scope increase|expand(?:ing)? scope|broaden scope|new scope)\b/i,
7443
7730
  /\b(seed|ingest|add)\b.{0,24}\b(graph|constraint|entity|entities)\b/i,
7444
- /\b(new feature|new module|new domain|new subsystem)\b/i
7731
+ /\b(new feature|new module|new domain|new subsystem)\b/i,
7732
+ /\b(expand|increase|broaden)\b.{0,24}\b(coverage|surface area|scope)\b/i,
7733
+ /\b(add|introduce|build|create)\b.{0,24}\b(endpoint|route|api|service|workflow)\b/i
7445
7734
  ];
7446
- const hasExplicitScopeIntent = explicitScopePatterns.some((rx) => rx.test(task));
7735
+ const hasExplicitScopeIntent = explicitScopePatterns.some((rx) => rx.test(intentText));
7447
7736
  if (hasExplicitScopeIntent) {
7448
7737
  confidence += 0.55;
7449
7738
  reasons.push("Task description includes explicit scope-expansion language.");
7450
7739
  }
7451
7740
  const hintedName = params.hinted_entity_name?.trim();
7452
- const inferredName = hintedName || inferEntityNameFromTask(task);
7741
+ const inferredName = hintedName || inferEntityNameFromTask(intentText);
7453
7742
  if (inferredName) {
7454
7743
  confidence += 0.12;
7455
7744
  reasons.push("Potential new entity name detected from task context.");
@@ -7909,7 +8198,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
7909
8198
  type: "object",
7910
8199
  properties: {
7911
8200
  prompt: { type: "string" },
7912
- wikiMarkdown: { type: "string" }
8201
+ wikiMarkdown: { type: "string" },
8202
+ auth_token: { type: "string", description: "Optional auth token for telemetry attribution" },
8203
+ install_id: { type: "string", description: "Optional install ID for telemetry attribution" }
7913
8204
  },
7914
8205
  required: ["prompt"]
7915
8206
  }
@@ -7938,7 +8229,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
7938
8229
  inputSchema: {
7939
8230
  type: "object",
7940
8231
  properties: {
7941
- prompt: { type: "string" }
8232
+ prompt: { type: "string" },
8233
+ auth_token: { type: "string", description: "Optional auth token for telemetry attribution" },
8234
+ install_id: { type: "string", description: "Optional install ID for telemetry attribution" }
7942
8235
  },
7943
8236
  required: ["prompt"]
7944
8237
  }
@@ -8372,6 +8665,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
8372
8665
  required: ["product_id"]
8373
8666
  }
8374
8667
  },
8668
+ {
8669
+ name: "get_assurance_manifest",
8670
+ description: `\u{1F513} AUTHENTICATED-FREE - Fetch signed assurance proof for "prove it's safe", "production-ready", and audit verification prompts.`,
8671
+ inputSchema: {
8672
+ type: "object",
8673
+ properties: {
8674
+ url: { type: "string", description: "Optional assurance URL override (default: NEXT_PUBLIC_SITE_URL/.well-known/cutline-assurance.json). Use target app domain when verifying deployed builds." }
8675
+ }
8676
+ }
8677
+ },
8678
+ {
8679
+ name: "validate_repo_policy",
8680
+ description: "\u{1F513} AUTHENTICATED-FREE - Run deterministic policy validation for secure/deploy-ready/audit prompts and return a machine-readable verdict object.",
8681
+ inputSchema: {
8682
+ type: "object",
8683
+ properties: {
8684
+ project_root: { type: "string", description: "Absolute workspace root containing cutline.json" },
8685
+ manifest_path: { type: "string", description: "Optional explicit path to cutline.json (overrides project_root)" },
8686
+ install_id: { type: "string", description: "Optional agent install_id for funnel attribution when emitting policy gate events (auto-resolved from local ~/.cutline-mcp/config.json when omitted)" },
8687
+ observed: {
8688
+ type: "object",
8689
+ description: "Observed evidence from scans/assurance checks to evaluate against policy thresholds",
8690
+ properties: {
8691
+ security_score: { type: "number", description: "Observed security score (0-100)" },
8692
+ critical_findings_count: { type: "number", description: "Observed unresolved critical/high findings count" },
8693
+ assurance_available: { type: "boolean", description: "Whether assurance artifact is available" },
8694
+ assurance_signed: { type: "boolean", description: "Whether assurance artifact signature is valid/present" },
8695
+ assurance_age_hours: { type: "number", description: "Age of assurance evidence in hours" }
8696
+ }
8697
+ }
8698
+ },
8699
+ required: ["project_root"]
8700
+ }
8701
+ },
8375
8702
  {
8376
8703
  name: "code_audit",
8377
8704
  description: "\u{1F513} FREE - Security vibe check (code audit). Evaluates your codebase against a stack-aware constraint graph covering security, reliability, and scalability. No deep dive or product_id required \u2014 just point at your codebase. Shows aggregate readiness scores and top critical findings; detailed analysis and remediation require Premium. Requires a Cutline account (free). 3 scans/month.",
@@ -8681,9 +9008,125 @@ Why AI: ${idea.whyAI}`
8681
9008
  };
8682
9009
  }
8683
9010
  if (name2 === "trial_generate") {
8684
- const { prompt } = args;
9011
+ const trialArgs = args;
9012
+ const { prompt } = trialArgs;
8685
9013
  const text = await cfGenerateTrialRun(prompt);
8686
- return { content: [{ type: "text", text: JSON.stringify({ text }) }] };
9014
+ const isTestingIntent = isTestingIntentPrompt(prompt);
9015
+ const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
9016
+ const resolvedInstallId = trialArgs.install_id || getStoredInstallId({ environment: env });
9017
+ if (isTestingIntent) {
9018
+ await emitAgentEvent({
9019
+ authToken: trialArgs.auth_token,
9020
+ installId: resolvedInstallId || void 0,
9021
+ eventName: "agent_test_intent_detected",
9022
+ eventProperties: {
9023
+ tool_name: "trial_generate",
9024
+ route: "cutline_testing_rgr"
9025
+ }
9026
+ });
9027
+ }
9028
+ return {
9029
+ content: [{
9030
+ type: "text",
9031
+ text: JSON.stringify({
9032
+ text,
9033
+ ...isTestingIntent ? { cutline_testing_route: buildTestingIntentHintResponse(prompt) } : {}
9034
+ })
9035
+ }]
9036
+ };
9037
+ }
9038
+ if (name2 === "get_assurance_manifest") {
9039
+ const { url } = args;
9040
+ await resolveAuthContextFree(args.auth_token);
9041
+ const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
9042
+ const assuranceUrl = url || `${siteUrl}/.well-known/cutline-assurance.json`;
9043
+ const response = await fetch(assuranceUrl, {
9044
+ method: "GET",
9045
+ headers: { "Accept": "application/json" }
9046
+ });
9047
+ if (!response.ok) {
9048
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch assurance manifest (${response.status}) from ${assuranceUrl}`);
9049
+ }
9050
+ const manifest = await response.json();
9051
+ return {
9052
+ content: [{
9053
+ type: "text",
9054
+ text: JSON.stringify({
9055
+ source_url: assuranceUrl,
9056
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
9057
+ manifest
9058
+ }, null, 2)
9059
+ }]
9060
+ };
9061
+ }
9062
+ if (name2 === "validate_repo_policy") {
9063
+ const policyArgs = args;
9064
+ if (!policyArgs.project_root) {
9065
+ throw new McpError(ErrorCode.InvalidParams, "project_root is required");
9066
+ }
9067
+ await resolveAuthContextFree(policyArgs.auth_token);
9068
+ const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
9069
+ const resolvedInstallId = policyArgs.install_id || getStoredInstallId({ environment: env });
9070
+ const telemetryAttribution = resolveTelemetryAttribution({
9071
+ providedInstallId: policyArgs.install_id,
9072
+ resolvedInstallId: resolvedInstallId || void 0
9073
+ });
9074
+ const { existsSync, readFileSync } = await import("node:fs");
9075
+ const { resolve, join: join4 } = await import("node:path");
9076
+ const manifestPath = resolve(policyArgs.manifest_path || join4(policyArgs.project_root, "cutline.json"));
9077
+ if (!existsSync(manifestPath)) {
9078
+ await emitPolicyGateEvent({
9079
+ authToken: policyArgs.auth_token,
9080
+ installId: resolvedInstallId || void 0,
9081
+ status: "invalid_policy",
9082
+ policyName: "cutline-policy",
9083
+ failedChecks: 1,
9084
+ unknownChecks: 0,
9085
+ blockingReasonsCount: 1
9086
+ });
9087
+ return {
9088
+ content: [{
9089
+ type: "text",
9090
+ text: JSON.stringify(buildInvalidPolicyResponse({
9091
+ manifestPath,
9092
+ telemetryAttribution
9093
+ }), null, 2)
9094
+ }]
9095
+ };
9096
+ }
9097
+ let manifest;
9098
+ try {
9099
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
9100
+ } catch (e) {
9101
+ throw new McpError(ErrorCode.InvalidParams, `Invalid cutline.json: ${e?.message || String(e)}`);
9102
+ }
9103
+ const requirements = buildRepoPolicyRequirements(manifest);
9104
+ const observed = buildRepoPolicyObserved(policyArgs.observed);
9105
+ const verdict = evaluateRepoPolicy(requirements, observed);
9106
+ const failedChecks = verdict.checks.filter((c) => c.status === "fail").length;
9107
+ const unknownChecks = verdict.checks.filter((c) => c.status === "unknown").length;
9108
+ await emitPolicyGateEvent({
9109
+ authToken: policyArgs.auth_token,
9110
+ installId: resolvedInstallId || void 0,
9111
+ status: verdict.status,
9112
+ policyName: manifest?.policy_name || "cutline-policy",
9113
+ failedChecks,
9114
+ unknownChecks,
9115
+ blockingReasonsCount: verdict.blocking_reasons.length
9116
+ });
9117
+ return {
9118
+ content: [{
9119
+ type: "text",
9120
+ text: JSON.stringify(buildPolicyVerdictResponse({
9121
+ verdict,
9122
+ manifestPath,
9123
+ policyName: manifest?.policy_name,
9124
+ schemaVersion: manifest?.schema_version,
9125
+ observedEvidence: policyArgs.observed || {},
9126
+ telemetryAttribution
9127
+ }), null, 2)
9128
+ }]
9129
+ };
8687
9130
  }
8688
9131
  if (name2 === "code_audit") {
8689
9132
  const scanArgs = args;
@@ -8984,9 +9427,32 @@ Competitive threats: ${competitors}` : ""
8984
9427
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
8985
9428
  }
8986
9429
  case "agent_chat": {
8987
- const { prompt, wikiMarkdown } = args;
9430
+ const chatArgs = args;
9431
+ const { prompt, wikiMarkdown } = chatArgs;
8988
9432
  const text = await cfGenerateChatSuggestion(prompt, wikiMarkdown);
8989
- return { content: [{ type: "text", text: JSON.stringify({ text }) }] };
9433
+ const isTestingIntent = isTestingIntentPrompt(prompt);
9434
+ const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
9435
+ const resolvedInstallId = chatArgs.install_id || getStoredInstallId({ environment: env });
9436
+ if (isTestingIntent) {
9437
+ await emitAgentEvent({
9438
+ authToken: chatArgs.auth_token,
9439
+ installId: resolvedInstallId || void 0,
9440
+ eventName: "agent_test_intent_detected",
9441
+ eventProperties: {
9442
+ tool_name: "agent_chat",
9443
+ route: "cutline_testing_rgr"
9444
+ }
9445
+ });
9446
+ }
9447
+ return {
9448
+ content: [{
9449
+ type: "text",
9450
+ text: JSON.stringify({
9451
+ text,
9452
+ ...isTestingIntent ? { cutline_testing_route: buildTestingIntentHintResponse(prompt) } : {}
9453
+ })
9454
+ }]
9455
+ };
8990
9456
  }
8991
9457
  // Integrations
8992
9458
  case "integrations_create_issues": {
@@ -9344,7 +9810,7 @@ Meta: ${JSON.stringify(output.meta)}` }
9344
9810
  }
9345
9811
  const analysis = analyzeFileContext(fileContext);
9346
9812
  let knownEntityNames = [];
9347
- if (task_description || scope_entity_name) {
9813
+ if (task_description || code_snippet || scope_entity_name) {
9348
9814
  try {
9349
9815
  const entities = await getAllEntities(product_id);
9350
9816
  knownEntityNames = entities.map((e) => e.name);
@@ -9353,6 +9819,7 @@ Meta: ${JSON.stringify(output.meta)}` }
9353
9819
  }
9354
9820
  const scopeExpansion = assessScopeExpansionIntent({
9355
9821
  task_description,
9822
+ code_snippet,
9356
9823
  file_paths,
9357
9824
  known_entity_names: knownEntityNames,
9358
9825
  hinted_entity_name: scope_entity_name
@@ -9481,9 +9948,8 @@ Meta: ${JSON.stringify(output.meta)}` }
9481
9948
  } catch (e) {
9482
9949
  }
9483
9950
  }
9484
- let autoRgrPlan = void 0;
9951
+ const autoRgrPlan = planRgrPhases(finalResults);
9485
9952
  if (autoPhase === "auto") {
9486
- autoRgrPlan = planRgrPhases(finalResults);
9487
9953
  if (autoRgrPlan.strategy === "phased") {
9488
9954
  finalResults = finalResults.filter((c) => {
9489
9955
  const phased = filterByPhase([c], "test_spec");
@@ -10555,6 +11021,11 @@ ${JSON.stringify(metrics, null, 2)}` }
10555
11021
  ...plan,
10556
11022
  entity: rgrMatched[0].name,
10557
11023
  complexity,
11024
+ auto_execution: {
11025
+ scope_increase_triggers_rgr: true,
11026
+ mode: "pervasive",
11027
+ expected_runner: "local_vitest"
11028
+ },
10558
11029
  governance
10559
11030
  }, null, 2)
10560
11031
  }]
@@ -11081,7 +11552,7 @@ Meta: ${JSON.stringify({
11081
11552
  mode: "product",
11082
11553
  codeContext: payload.codeContext || null
11083
11554
  });
11084
- runInput.productId = newJobId;
11555
+ runInput.productId = String(auditArgs.product_id || newJobId);
11085
11556
  await updatePremortem(newJobId, { payload: runInput });
11086
11557
  return { jobId: newJobId };
11087
11558
  }
@@ -78,7 +78,7 @@ import {
78
78
  upsertEdges,
79
79
  upsertEntities,
80
80
  upsertNodes
81
- } from "./chunk-X2B5QUWO.js";
81
+ } from "./chunk-RUCYK3TR.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-X2B5QUWO.js";
17
+ } from "./chunk-RUCYK3TR.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-X2B5QUWO.js";
16
+ } from "./chunk-RUCYK3TR.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-X2B5QUWO.js";
16
+ } from "./chunk-RUCYK3TR.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-X2B5QUWO.js";
30
+ } from "./chunk-RUCYK3TR.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-X2B5QUWO.js";
24
+ } from "./chunk-RUCYK3TR.js";
25
25
 
26
26
  // ../mcp/dist/mcp/src/tools-server.js
27
27
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -0,0 +1,16 @@
1
+ type AgentEventName = 'install_completed' | 'first_tool_call_success' | 'tool_call_failed' | 'heartbeat' | 'policy_gate_passed' | 'policy_gate_blocked' | 'upgrade_clicked' | 'upgrade_completed' | 'agent_test_intent_detected' | 'agent_cutline_install_prompted' | 'agent_cutline_install_completed' | 'agent_first_rgr_test_call_success';
2
+ export declare function registerAgentInstall(input: {
3
+ idToken: string;
4
+ staging?: boolean;
5
+ projectRoot: string;
6
+ sourceSurface: string;
7
+ hostAgent?: string;
8
+ }): Promise<string | null>;
9
+ export declare function trackAgentEvent(input: {
10
+ idToken: string;
11
+ installId: string;
12
+ eventName: AgentEventName;
13
+ eventProperties?: Record<string, unknown>;
14
+ staging?: boolean;
15
+ }): Promise<void>;
16
+ export {};
@@ -0,0 +1,64 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { getConfig } from './config.js';
3
+ import { loadConfig, saveConfig } from './config-store.js';
4
+ function installIdKey(staging) {
5
+ return staging ? 'agentInstallIdStaging' : 'agentInstallId';
6
+ }
7
+ export async function registerAgentInstall(input) {
8
+ const key = installIdKey(input.staging);
9
+ const existing = loadConfig();
10
+ const persistedInstallId = typeof existing[key] === 'string' ? existing[key] : null;
11
+ if (persistedInstallId)
12
+ return persistedInstallId;
13
+ const { BASE_URL } = getConfig({ staging: input.staging });
14
+ const installationKey = createHash('sha256')
15
+ .update(`${input.projectRoot}:${input.staging ? 'staging' : 'production'}`)
16
+ .digest('hex')
17
+ .slice(0, 32);
18
+ const workspaceId = createHash('sha256').update(input.projectRoot).digest('hex').slice(0, 24);
19
+ try {
20
+ const response = await fetch(`${BASE_URL}/api/agent/register`, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ Authorization: `Bearer ${input.idToken}`,
25
+ },
26
+ body: JSON.stringify({
27
+ installation_key: installationKey,
28
+ source_surface: input.sourceSurface,
29
+ host_agent: input.hostAgent || 'cutline-mcp-cli',
30
+ workspace_id: workspaceId,
31
+ }),
32
+ });
33
+ if (!response.ok)
34
+ return null;
35
+ const payload = await response.json();
36
+ if (!payload.install_id)
37
+ return null;
38
+ saveConfig({ [key]: payload.install_id });
39
+ return payload.install_id;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ export async function trackAgentEvent(input) {
46
+ const { BASE_URL } = getConfig({ staging: input.staging });
47
+ try {
48
+ await fetch(`${BASE_URL}/api/agent/event`, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ Authorization: `Bearer ${input.idToken}`,
53
+ },
54
+ body: JSON.stringify({
55
+ install_id: input.installId,
56
+ event_name: input.eventName,
57
+ event_properties: input.eventProperties || {},
58
+ }),
59
+ });
60
+ }
61
+ catch {
62
+ // Best effort telemetry; never block CLI command success.
63
+ }
64
+ }
@@ -2,6 +2,8 @@ export interface McpConfig {
2
2
  refreshToken?: string;
3
3
  environment?: 'production' | 'staging';
4
4
  apiKey?: string;
5
+ agentInstallId?: string;
6
+ agentInstallIdStaging?: string;
5
7
  }
6
8
  export declare function saveConfig(config: McpConfig): void;
7
9
  export declare function loadConfig(): McpConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibekiln/cutline-mcp-cli",
3
- "version": "0.8.1",
3
+ "version": "0.10.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",