@vibecheckai/cli 3.2.6 → 3.4.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.
Files changed (89) hide show
  1. package/bin/registry.js +306 -90
  2. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  3. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  4. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  5. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  6. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  7. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  8. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  11. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  12. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  14. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  15. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  16. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  17. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  18. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  19. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  20. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  21. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  22. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  23. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  24. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  25. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  26. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  27. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  28. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  29. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  30. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  31. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  32. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  35. package/bin/runners/lib/analyzers.js +136 -141
  36. package/bin/runners/lib/authority-badge.js +425 -0
  37. package/bin/runners/lib/cli-output.js +7 -1
  38. package/bin/runners/lib/entitlements-v2.js +96 -505
  39. package/bin/runners/lib/error-handler.js +16 -9
  40. package/bin/runners/lib/exit-codes.js +275 -0
  41. package/bin/runners/lib/global-flags.js +37 -0
  42. package/bin/runners/lib/help-formatter.js +413 -0
  43. package/bin/runners/lib/logger.js +38 -0
  44. package/bin/runners/lib/scan-output.js +18 -19
  45. package/bin/runners/lib/ship-output.js +18 -25
  46. package/bin/runners/lib/unified-cli-output.js +604 -0
  47. package/bin/runners/lib/upsell.js +105 -205
  48. package/bin/runners/runApprove.js +1200 -0
  49. package/bin/runners/runAuth.js +324 -95
  50. package/bin/runners/runCheckpoint.js +39 -21
  51. package/bin/runners/runClassify.js +859 -0
  52. package/bin/runners/runContext.js +136 -24
  53. package/bin/runners/runDoctor.js +108 -68
  54. package/bin/runners/runFix.js +6 -5
  55. package/bin/runners/runGuard.js +212 -118
  56. package/bin/runners/runInit.js +3 -2
  57. package/bin/runners/runMcp.js +130 -52
  58. package/bin/runners/runPolish.js +43 -20
  59. package/bin/runners/runProve.js +1 -2
  60. package/bin/runners/runReport.js +3 -2
  61. package/bin/runners/runScan.js +77 -45
  62. package/bin/runners/runShip.js +3 -4
  63. package/bin/runners/runValidate.js +19 -2
  64. package/bin/runners/runWatch.js +104 -53
  65. package/bin/vibecheck.js +103 -21
  66. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  67. package/mcp-server/agent-firewall-interceptor.js +367 -31
  68. package/mcp-server/authority-tools.js +569 -0
  69. package/mcp-server/conductor/conflict-resolver.js +588 -0
  70. package/mcp-server/conductor/execution-planner.js +544 -0
  71. package/mcp-server/conductor/index.js +377 -0
  72. package/mcp-server/conductor/lock-manager.js +615 -0
  73. package/mcp-server/conductor/request-queue.js +550 -0
  74. package/mcp-server/conductor/session-manager.js +500 -0
  75. package/mcp-server/conductor/tools.js +510 -0
  76. package/mcp-server/index.js +1152 -856
  77. package/mcp-server/lib/api-client.cjs +13 -0
  78. package/mcp-server/lib/logger.cjs +30 -0
  79. package/mcp-server/logger.js +173 -0
  80. package/mcp-server/package.json +2 -2
  81. package/mcp-server/premium-tools.js +2 -2
  82. package/mcp-server/tier-auth.js +194 -383
  83. package/mcp-server/tools-v3.js +495 -533
  84. package/mcp-server/truth-firewall-tools.js +145 -15
  85. package/mcp-server/vibecheck-tools.js +2 -2
  86. package/package.json +2 -3
  87. package/mcp-server/index.old.js +0 -4137
  88. package/mcp-server/lib/api-client.js +0 -269
  89. package/mcp-server/package-lock.json +0 -165
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Upsell Copy Module - Central copy generator for tier upgrades
3
3
  *
4
- * This is the SINGLE SOURCE OF TRUTH for all upsell/upgrade messaging.
5
- * All upgrade nudges flow through these functions.
4
+ * Simple 2-tier model: FREE and PRO ($69/mo)
6
5
  *
7
6
  * Rules:
8
7
  * - Blunt, confident, minimal tone
@@ -49,163 +48,86 @@ const sym = {
49
48
  };
50
49
 
51
50
  // ═══════════════════════════════════════════════════════════════════════════════
52
- // TIER CONFIGURATION
51
+ // SIMPLE 2-TIER CONFIG: FREE and PRO ($69/mo)
53
52
  // ═══════════════════════════════════════════════════════════════════════════════
54
53
  const TIER_LABELS = {
55
54
  free: "FREE",
56
- starter: "STARTER",
57
55
  pro: "PRO",
58
- complete: "COMPLETE",
59
56
  };
60
57
 
61
58
  const TIER_COLORS = {
62
59
  free: c.green,
63
- starter: c.cyan,
64
60
  pro: c.magenta,
65
- complete: c.yellow,
66
61
  };
67
62
 
68
- const PRICING_URL = "https://vibecheckai.dev";
63
+ const PRICING_URL = "https://vibecheckai.dev/pricing";
64
+ const PRO_PRICE = "$69/mo";
69
65
 
70
66
  // ═══════════════════════════════════════════════════════════════════════════════
71
67
  // DENIAL COPY - Command-specific reasons and alternatives
72
68
  // ═══════════════════════════════════════════════════════════════════════════════
73
69
  const DENIAL_COPY = {
74
70
  ship: {
75
- feature: "release gate",
76
- why: "Full ship analysis with runtime verification",
71
+ feature: "release verdict",
72
+ why: "SHIP/WARN/BLOCK verdict with evidence",
77
73
  freeAlt: "vibecheck scan",
78
- freeAltDesc: "static analysis",
74
+ freeAltDesc: "static analysis without verdict",
79
75
  },
80
76
  prove: {
81
- feature: "proof loop",
82
- why: "Complete ctx reality → ship → fix cycle",
83
- freeAlt: "vibecheck ship",
84
- freeAltDesc: "static verdict",
85
- },
86
- permissions: {
87
- feature: "auth boundary / IDOR detection",
88
- why: "Deep authorization matrix and IDOR analysis",
89
- freeAlt: "vibecheck reality --verify-auth",
90
- freeAltDesc: "basic auth boundary check",
91
- },
92
- "fix.apply_patches": {
93
- feature: "patch generator / PR-ready diff",
94
- why: "Apply LLM-generated patches automatically",
95
- freeAlt: "vibecheck fix",
96
- freeAltDesc: "plan-only mode",
77
+ feature: "runtime proof",
78
+ why: "Full runtime verification with evidence",
79
+ freeAlt: "vibecheck scan",
80
+ freeAltDesc: "static analysis",
97
81
  },
98
82
  fix: {
99
- feature: "auto-fix with patches",
100
- why: "Generate and apply fixes to your codebase",
101
- freeAlt: "vibecheck fix --plan-only",
102
- freeAltDesc: "view fix plan without applying",
103
- },
104
- badge: {
105
- feature: "status artifact",
106
- why: "Verified ship badge for README",
107
- freeAlt: "vibecheck report",
108
- freeAltDesc: "HTML/MD report",
83
+ feature: "auto-fix",
84
+ why: "AI-powered fixes with --apply",
85
+ freeAlt: "vibecheck scan",
86
+ freeAltDesc: "view issues only",
109
87
  },
110
88
  gate: {
111
89
  feature: "CI/CD gate",
112
90
  why: "Block deploys on verification failures",
113
- freeAlt: "vibecheck ship --ci",
114
- freeAltDesc: "exit codes for scripts",
115
- },
116
- pr: {
117
- feature: "PR comment generator",
118
- why: "Auto-generated PR comments with findings",
119
- freeAlt: "vibecheck report --format md",
120
- freeAltDesc: "markdown report",
91
+ freeAlt: "vibecheck scan",
92
+ freeAltDesc: "check before deploy",
121
93
  },
122
- launch: {
123
- feature: "pre-launch checklist",
124
- why: "Guided launch readiness wizard",
125
- freeAlt: "vibecheck ship",
126
- freeAltDesc: "verdict check",
94
+ badge: {
95
+ feature: "ship badge",
96
+ why: "Verified ship badge for README",
97
+ freeAlt: "vibecheck report",
98
+ freeAltDesc: "HTML/MD report",
127
99
  },
128
- mcp: {
129
- feature: "MCP server for AI IDEs",
130
- why: "Real-time AI IDE integration",
131
- freeAlt: "vibecheck ctx",
132
- freeAltDesc: "generate truthpack manually",
100
+ reality: {
101
+ feature: "full runtime verification",
102
+ why: "Unlimited pages + auth boundary testing",
103
+ freeAlt: "vibecheck scan",
104
+ freeAltDesc: "static analysis",
133
105
  },
134
106
  share: {
135
- feature: "share pack generator",
107
+ feature: "share pack",
136
108
  why: "Shareable proof bundle for PRs/docs",
137
109
  freeAlt: "vibecheck report",
138
110
  freeAltDesc: "local report",
139
111
  },
140
112
  "ai-test": {
141
- feature: "autonomous test generation",
113
+ feature: "AI testing",
142
114
  why: "AI-generated test coverage",
143
115
  freeAlt: "vibecheck scan",
144
116
  freeAltDesc: "static analysis",
145
117
  },
146
- replay: {
147
- feature: "session replay",
148
- why: "Record and replay user sessions",
149
- freeAlt: "vibecheck reality",
150
- freeAltDesc: "one-time runtime proof",
151
- },
152
- graph: {
153
- feature: "reality proof graph",
154
- why: "Visual dependency and proof graph",
155
- freeAlt: "vibecheck ctx",
156
- freeAltDesc: "truthpack",
157
- },
158
- };
159
-
160
- // ═══════════════════════════════════════════════════════════════════════════════
161
- // CAPS COPY - Downgrade mode descriptions
162
- // ═══════════════════════════════════════════════════════════════════════════════
163
- const CAPS_COPY = {
164
- "reality.preview": {
165
- short: "5 pages, no auth boundary",
166
- full: "Preview mode: 5 pages max, 20 clicks, no auth boundary verification",
167
- upgradeBenefit: "Unlimited pages + full auth boundary testing",
168
- },
169
- "fix.plan_only": {
170
- short: "plan-only, no apply",
171
- full: "Plan mode: generates fix missions but cannot apply patches",
172
- upgradeBenefit: "Apply patches automatically with --apply",
173
- },
174
- "report.html_md": {
175
- short: "HTML/MD only",
176
- full: "Basic formats: HTML and Markdown reports only",
177
- upgradeBenefit: "SARIF, CSV, and compliance pack exports",
178
- },
179
- "ship.static": {
180
- short: "static-only",
181
- full: "Static analysis only, no runtime verification",
182
- upgradeBenefit: "Full runtime + static verification",
183
- },
184
- "mcp.help_only": {
185
- short: "help and print-config only",
186
- full: "MCP server limited to help and config commands",
187
- upgradeBenefit: "Full MCP server with all tools",
188
- },
189
118
  };
190
119
 
191
120
  // ═══════════════════════════════════════════════════════════════════════════════
192
121
  // formatDenied() - Hard denial message
193
122
  // ═══════════════════════════════════════════════════════════════════════════════
194
123
  /**
195
- * Format a denial message for a command that requires a higher tier.
124
+ * Format a denial message for a command that requires PRO.
196
125
  *
197
126
  * @param {string} cmd - The command that was denied
198
127
  * @param {object} opts - Options
199
- * @param {string} opts.currentTier - User's current tier
200
- * @param {string} opts.requiredTier - Required tier for the command
201
- * @param {string} [opts.reason] - Additional context
202
- * @param {string} [opts.suggestedNext] - Suggested next command
203
- * @param {string} [opts.freeAlternative] - Free alternative command
204
128
  * @returns {string} Formatted denial message
205
129
  */
206
130
  function formatDenied(cmd, opts = {}) {
207
- const { currentTier = "free", requiredTier = "pro" } = opts;
208
-
209
131
  const copy = DENIAL_COPY[cmd] || {
210
132
  feature: cmd,
211
133
  why: `${cmd} command`,
@@ -213,80 +135,42 @@ function formatDenied(cmd, opts = {}) {
213
135
  freeAltDesc: null,
214
136
  };
215
137
 
216
- const reqColor = TIER_COLORS[requiredTier] || c.yellow;
217
- const reqLabel = TIER_LABELS[requiredTier] || requiredTier.toUpperCase();
218
- const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
219
-
220
138
  let msg = `
221
- ${c.red}${c.bold}${sym.lock} LOCKED${c.reset}
139
+ ${c.red}${c.bold}${sym.lock} PRO FEATURE${c.reset}
222
140
 
223
- ${c.bold}What:${c.reset} ${c.yellow}${cmd}${c.reset} ${c.dim}(${copy.feature})${c.reset}
224
- ${c.bold}Why:${c.reset} ${copy.why}
225
- ${c.bold}Requires:${c.reset} ${reqColor}${reqLabel}${c.reset} plan
226
- ${c.bold}You have:${c.reset} ${c.dim}${curLabel}${c.reset}
141
+ ${c.bold}Command:${c.reset} ${c.yellow}${cmd}${c.reset}
142
+ ${c.bold}Feature:${c.reset} ${copy.feature}
143
+ ${c.bold}Why:${c.reset} ${copy.why}
227
144
  `;
228
145
 
229
146
  // Free alternative
230
147
  if (copy.freeAlt) {
231
148
  msg += `
232
- ${c.green}${sym.arrow} Free path:${c.reset} ${c.cyan}${copy.freeAlt}${c.reset} ${c.dim}(${copy.freeAltDesc})${c.reset}`;
149
+ ${c.green}${sym.arrow} Free:${c.reset} ${c.cyan}${copy.freeAlt}${c.reset} ${c.dim}(${copy.freeAltDesc})${c.reset}`;
233
150
  }
234
151
 
235
152
  // Upgrade CTA
236
153
  msg += `
237
- ${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
154
+ ${c.bold}${sym.arrow} Upgrade:${c.reset} PRO ${PRO_PRICE} ${sym.arrow} ${PRICING_URL}
238
155
  `;
239
156
 
240
157
  return msg;
241
158
  }
242
159
 
243
160
  // ═══════════════════════════════════════════════════════════════════════════════
244
- // formatDowngrade() - Caps/downgrade notice
161
+ // formatCapHit() - Limit reached notice
245
162
  // ═══════════════════════════════════════════════════════════════════════════════
246
163
  /**
247
- * Format a downgrade notice for a command running in capped mode.
164
+ * Format a cap-hit notice when user reaches a limit.
248
165
  *
249
166
  * @param {string} cmd - The command being run
250
167
  * @param {object} opts - Options
251
- * @param {string} opts.currentTier - User's current tier
252
- * @param {string} opts.effectiveMode - The downgraded mode being used
253
- * @param {object} [opts.caps] - Specific caps applied
254
- * @returns {string} Formatted downgrade notice
255
- */
256
- function formatDowngrade(cmd, opts = {}) {
257
- const { currentTier = "free", effectiveMode, caps } = opts;
258
-
259
- const copy = CAPS_COPY[effectiveMode] || {
260
- short: effectiveMode || "limited mode",
261
- full: `Running in ${effectiveMode || "limited"} mode`,
262
- upgradeBenefit: "Full access",
263
- };
264
-
265
- const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
266
-
267
- // Single line notice for start of run
268
- let shortNotice = `${c.yellow}${sym.warning}${c.reset} Running in ${c.yellow}${curLabel}${c.reset} mode: ${c.dim}${copy.short}${c.reset}`;
269
-
270
- return shortNotice;
271
- }
272
-
273
- /**
274
- * Format a cap-hit notice when user reaches a limit during execution.
275
- *
276
- * @param {string} cmd - The command being run
277
- * @param {object} opts - Options
278
- * @param {string} opts.limitType - Type of limit hit (e.g., "pages", "clicks")
279
- * @param {number} opts.limitValue - The limit value
280
- * @param {string} opts.upgradeTier - Tier to upgrade to
281
168
  * @returns {string} Formatted cap-hit message
282
169
  */
283
170
  function formatCapHit(cmd, opts = {}) {
284
- const { limitType = "limit", limitValue, upgradeTier = "pro" } = opts;
285
-
286
- const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
287
- const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
171
+ const { limitType = "limit", limitValue } = opts;
288
172
 
289
- return `${c.yellow}${sym.warning}${c.reset} Hit FREE ${limitType} cap (${limitValue}). Upgrade to ${tierColor}${tierLabel}${c.reset} ${sym.arrow} ${PRICING_URL}`;
173
+ return `${c.yellow}${sym.warning}${c.reset} FREE ${limitType} cap (${limitValue}). Upgrade to ${c.magenta}PRO${c.reset} (${PRO_PRICE}) ${sym.arrow} ${PRICING_URL}`;
290
174
  }
291
175
 
292
176
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -296,12 +180,6 @@ function formatCapHit(cmd, opts = {}) {
296
180
  * Format an earned upsell shown at end of run.
297
181
  *
298
182
  * @param {object} opts - Options
299
- * @param {string} opts.cmd - The command that was run
300
- * @param {string} opts.verdict - The verdict (SHIP/WARN/BLOCK)
301
- * @param {Array} [opts.topIssues] - Top 3 issues to show
302
- * @param {string} [opts.withheldArtifact] - Artifact that was withheld (e.g., "badge")
303
- * @param {string} [opts.upgradeTier] - Tier to suggest upgrading to
304
- * @param {string} [opts.why] - Why this upsell is relevant
305
183
  * @returns {string} Formatted earned upsell message
306
184
  */
307
185
  function formatEarnedUpsell(opts = {}) {
@@ -310,15 +188,11 @@ function formatEarnedUpsell(opts = {}) {
310
188
  verdict,
311
189
  topIssues = [],
312
190
  withheldArtifact,
313
- upgradeTier = "pro",
314
191
  why,
315
192
  currentTier = "free",
316
193
  suggestedCmd,
317
194
  } = opts;
318
195
 
319
- const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
320
- const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
321
-
322
196
  let msg = "";
323
197
 
324
198
  // Badge withheld case
@@ -344,16 +218,16 @@ ${c.yellow}${sym.badge} Badge withheld${c.reset} ${c.dim}(verdict: ${verdict})${
344
218
  return msg;
345
219
  }
346
220
 
347
- // Fix plan-only case
221
+ // Fix withheld case
348
222
  if (cmd === "fix" && withheldArtifact === "apply") {
349
223
  msg += `
350
224
  ${c.dim}─────────────────────────────────────────────────────────────${c.reset}
351
225
  ${c.yellow}${sym.lightning} Fix plan generated${c.reset}
352
226
 
353
- Patches ready but ${c.yellow}--apply${c.reset} requires ${tierColor}${tierLabel}${c.reset} plan.
227
+ Patches ready but ${c.yellow}--apply${c.reset} requires ${c.magenta}PRO${c.reset}.
354
228
 
355
229
  ${c.green}${sym.arrow} Review:${c.reset} .vibecheck/missions/
356
- ${c.bold}${sym.arrow} Apply:${c.reset} Upgrade ${sym.arrow} ${PRICING_URL}
230
+ ${c.bold}${sym.arrow} Apply:${c.reset} Upgrade to PRO ${sym.arrow} ${PRICING_URL}
357
231
  `;
358
232
  return msg;
359
233
  }
@@ -365,7 +239,7 @@ ${c.dim}────────────────────────
365
239
  ${c.yellow}${sym.warning} Preview complete${c.reset}
366
240
 
367
241
  Crawled ${topIssues.length || 5} pages (FREE limit).
368
- ${tierColor}${tierLabel}${c.reset} unlocks unlimited pages + auth boundary testing.
242
+ ${c.magenta}PRO${c.reset} unlocks unlimited pages + auth boundary testing.
369
243
 
370
244
  ${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
371
245
  `;
@@ -378,7 +252,7 @@ ${c.yellow}${sym.warning} Preview complete${c.reset}
378
252
  ${c.dim}─────────────────────────────────────────────────────────────${c.reset}
379
253
  ${c.cyan}${sym.star} ${why}${c.reset}
380
254
 
381
- ${tierColor}${tierLabel}${c.reset} unlocks this feature.
255
+ ${c.magenta}PRO${c.reset} (${PRO_PRICE}) unlocks this feature.
382
256
  ${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
383
257
  `;
384
258
  }
@@ -391,11 +265,6 @@ ${c.cyan}${sym.star} ${why}${c.reset}
391
265
  // ═══════════════════════════════════════════════════════════════════════════════
392
266
  /**
393
267
  * Format badge withheld message with top issues.
394
- *
395
- * @param {string} verdict - Current verdict
396
- * @param {Array} findings - All findings
397
- * @param {string} currentTier - User's tier
398
- * @returns {string} Formatted message
399
268
  */
400
269
  function formatBadgeWithheld(verdict, findings = [], currentTier = "free") {
401
270
  const blockers = findings.filter(f => f.severity === "BLOCK").slice(0, 3);
@@ -418,12 +287,11 @@ ${c.yellow}${sym.badge} Badge withheld${c.reset}
418
287
  });
419
288
  }
420
289
 
421
- // Suggest fix command if available
422
- const canFix = currentTier !== "free";
423
- if (canFix) {
290
+ // Suggest fix command
291
+ if (currentTier === "pro") {
424
292
  msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix${c.reset}\n`;
425
293
  } else {
426
- msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix --plan-only${c.reset} ${c.dim}(view fix plan)${c.reset}\n`;
294
+ msg += `\n ${c.dim}Upgrade to PRO for auto-fix ${sym.arrow} ${PRICING_URL}${c.reset}\n`;
427
295
  }
428
296
 
429
297
  return msg;
@@ -434,46 +302,34 @@ ${c.yellow}${sym.badge} Badge withheld${c.reset}
434
302
  // ═══════════════════════════════════════════════════════════════════════════════
435
303
  /**
436
304
  * Format next step suggestions based on current command and result.
437
- *
438
- * @param {string} cmd - Command just run
439
- * @param {string} verdict - Result/verdict
440
- * @param {string} currentTier - User's tier
441
- * @returns {string} Next step suggestion
442
305
  */
443
306
  function formatNextSteps(cmd, verdict, currentTier = "free") {
444
307
  const steps = [];
308
+ const isPro = currentTier === "pro";
445
309
 
446
310
  switch (cmd) {
447
311
  case "scan":
448
- if (currentTier === "free") {
312
+ if (isPro) {
449
313
  steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
314
+ steps.push({ cmd: "vibecheck fix", desc: "auto-fix issues" });
450
315
  } else {
451
- steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
452
- steps.push({ cmd: "vibecheck prove --url <url>", desc: "full proof loop" });
316
+ steps.push({ cmd: "vibecheck report", desc: "export report" });
453
317
  }
454
318
  break;
455
319
 
456
320
  case "ship":
457
- if (verdict === "SHIP") {
458
- if (currentTier !== "free") {
459
- steps.push({ cmd: "vibecheck badge", desc: "generate badge" });
460
- }
461
- steps.push({ cmd: "vibecheck report", desc: "export report" });
462
- } else {
463
- if (currentTier === "free") {
464
- steps.push({ cmd: "vibecheck fix --plan-only", desc: "view fix plan" });
465
- } else {
466
- steps.push({ cmd: "vibecheck fix", desc: "auto-fix issues" });
467
- }
321
+ if (verdict === "SHIP" && isPro) {
322
+ steps.push({ cmd: "vibecheck badge", desc: "generate badge" });
323
+ } else if (isPro) {
324
+ steps.push({ cmd: "vibecheck fix", desc: "auto-fix issues" });
468
325
  }
326
+ steps.push({ cmd: "vibecheck report", desc: "export report" });
469
327
  break;
470
328
 
471
329
  case "fix":
472
- steps.push({ cmd: "vibecheck ship", desc: "verify fixes" });
473
- break;
474
-
475
- case "reality":
476
- steps.push({ cmd: "vibecheck ship", desc: "get verdict with runtime proof" });
330
+ if (isPro) {
331
+ steps.push({ cmd: "vibecheck ship", desc: "verify fixes" });
332
+ }
477
333
  break;
478
334
  }
479
335
 
@@ -485,6 +341,48 @@ function formatNextSteps(cmd, verdict, currentTier = "free") {
485
341
  return msg;
486
342
  }
487
343
 
344
+ // ═══════════════════════════════════════════════════════════════════════════════
345
+ // formatDowngrade() - Caps/downgrade notice (for free tier)
346
+ // ═══════════════════════════════════════════════════════════════════════════════
347
+ function formatDowngrade(cmd, opts = {}) {
348
+ const { currentTier = "free" } = opts;
349
+
350
+ if (currentTier === "pro") return "";
351
+
352
+ return `${c.yellow}${sym.warning}${c.reset} Running in ${c.yellow}FREE${c.reset} mode ${c.dim}(limited features)${c.reset}`;
353
+ }
354
+
355
+ // ═══════════════════════════════════════════════════════════════════════════════
356
+ // formatSoftUpsell() - Non-intrusive end-of-run upsell
357
+ // ═══════════════════════════════════════════════════════════════════════════════
358
+ function formatSoftUpsell(cmd, opts = {}) {
359
+ const { currentTier = "free" } = opts;
360
+
361
+ if (currentTier === "pro") return "";
362
+
363
+ return `${c.dim}${sym.star} ${c.magenta}PRO${c.reset}${c.dim} (${PRO_PRICE}): Fix, Prove & Enforce ${sym.arrow} ${PRICING_URL}${c.reset}`;
364
+ }
365
+
366
+ // ═══════════════════════════════════════════════════════════════════════════════
367
+ // formatWorkflowUpsell() - Workflow suggestion with upsell
368
+ // ═══════════════════════════════════════════════════════════════════════════════
369
+ function formatWorkflowUpsell(completedCmd, currentTier = "free") {
370
+ if (currentTier === "pro") {
371
+ // PRO users get next command suggestion without upsell
372
+ const NEXT_CMDS = {
373
+ scan: "vibecheck ship",
374
+ ship: "vibecheck badge",
375
+ fix: "vibecheck ship",
376
+ reality: "vibecheck prove",
377
+ };
378
+ const next = NEXT_CMDS[completedCmd];
379
+ return next ? `${c.dim}Next:${c.reset} ${c.cyan}${next}${c.reset}` : "";
380
+ }
381
+
382
+ // FREE users get upsell
383
+ return `${c.dim}Next:${c.reset} ${c.cyan}vibecheck report${c.reset} ${c.dim}or${c.reset} ${c.magenta}PRO${c.reset}${c.dim}: ship, fix, prove, gate, badge${c.reset}`;
384
+ }
385
+
488
386
  // ═══════════════════════════════════════════════════════════════════════════════
489
387
  // EXPORTS
490
388
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -496,13 +394,15 @@ module.exports = {
496
394
  formatEarnedUpsell,
497
395
  formatBadgeWithheld,
498
396
  formatNextSteps,
397
+ formatSoftUpsell,
398
+ formatWorkflowUpsell,
499
399
 
500
400
  // Copy data (for testing/docs)
501
401
  DENIAL_COPY,
502
- CAPS_COPY,
503
402
  TIER_LABELS,
504
403
  TIER_COLORS,
505
404
  PRICING_URL,
405
+ PRO_PRICE,
506
406
 
507
407
  // Styling (for consistent use)
508
408
  c,