@vibecheckai/cli 3.1.2 → 3.1.4

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 (47) hide show
  1. package/README.md +60 -33
  2. package/bin/registry.js +319 -34
  3. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  4. package/bin/runners/REPORT_AUDIT.md +64 -0
  5. package/bin/runners/lib/entitlements-v2.js +97 -28
  6. package/bin/runners/lib/entitlements.js +3 -6
  7. package/bin/runners/lib/init-wizard.js +1 -1
  8. package/bin/runners/lib/report-engine.js +459 -280
  9. package/bin/runners/lib/report-html.js +1154 -1423
  10. package/bin/runners/lib/report-output.js +187 -0
  11. package/bin/runners/lib/report-templates.js +848 -850
  12. package/bin/runners/lib/scan-output.js +545 -0
  13. package/bin/runners/lib/server-usage.js +0 -12
  14. package/bin/runners/lib/ship-output.js +641 -0
  15. package/bin/runners/lib/status-output.js +253 -0
  16. package/bin/runners/lib/terminal-ui.js +853 -0
  17. package/bin/runners/runCheckpoint.js +502 -0
  18. package/bin/runners/runContracts.js +105 -0
  19. package/bin/runners/runExport.js +93 -0
  20. package/bin/runners/runFix.js +31 -24
  21. package/bin/runners/runInit.js +377 -112
  22. package/bin/runners/runInstall.js +1 -5
  23. package/bin/runners/runLabs.js +3 -3
  24. package/bin/runners/runPolish.js +2452 -0
  25. package/bin/runners/runProve.js +2 -2
  26. package/bin/runners/runReport.js +251 -200
  27. package/bin/runners/runRuntime.js +110 -0
  28. package/bin/runners/runScan.js +477 -379
  29. package/bin/runners/runSecurity.js +92 -0
  30. package/bin/runners/runShip.js +137 -207
  31. package/bin/runners/runStatus.js +16 -68
  32. package/bin/runners/utils.js +5 -5
  33. package/bin/vibecheck.js +25 -11
  34. package/mcp-server/index.js +150 -18
  35. package/mcp-server/package.json +2 -2
  36. package/mcp-server/premium-tools.js +13 -13
  37. package/mcp-server/tier-auth.js +292 -27
  38. package/mcp-server/vibecheck-tools.js +9 -9
  39. package/package.json +1 -1
  40. package/bin/runners/runClaimVerifier.js +0 -483
  41. package/bin/runners/runContextCompiler.js +0 -385
  42. package/bin/runners/runGate.js +0 -17
  43. package/bin/runners/runInitGha.js +0 -164
  44. package/bin/runners/runInteractive.js +0 -388
  45. package/bin/runners/runMdc.js +0 -204
  46. package/bin/runners/runMissionGenerator.js +0 -282
  47. package/bin/runners/runTruthpack.js +0 -636
@@ -1,385 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Context Compiler v1
4
- *
5
- * Generates task-targeted context slices from the Truth Pack.
6
- * Big context = noise. Small context = missing facts.
7
- * This compiles exactly what you need for the task.
8
- *
9
- * Usage:
10
- * compileContext({ task: 'Fix dead login button', focus: { files: [...], routes: [...] } })
11
- */
12
-
13
- import fs from 'fs/promises';
14
- import path from 'path';
15
- import crypto from 'crypto';
16
- import yaml from 'js-yaml';
17
-
18
- /**
19
- * Load the truth pack from disk
20
- */
21
- async function loadTruthpack(projectPath) {
22
- const truthpackPath = path.join(projectPath, '.vibecheck', 'truth', 'truthpack.json');
23
- try {
24
- const content = await fs.readFile(truthpackPath, 'utf8');
25
- return JSON.parse(content);
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- /**
32
- * Load invariants from disk
33
- */
34
- async function loadInvariants(projectPath) {
35
- const invariantsPath = path.join(projectPath, '.vibecheck', 'invariants.yml');
36
- try {
37
- const content = await fs.readFile(invariantsPath, 'utf8');
38
- return yaml.load(content);
39
- } catch {
40
- return { invariants: [] };
41
- }
42
- }
43
-
44
- /**
45
- * Compile context for a specific task
46
- *
47
- * @param {Object} request - Context compilation request
48
- * @param {string} request.task - What you're trying to do
49
- * @param {Object} request.focus - Optional focus areas
50
- * @param {string[]} request.focus.files - Specific files to include
51
- * @param {string[]} request.focus.routes - Specific routes to include
52
- * @param {string[]} request.constraints - Constraints to apply
53
- * @param {number} request.maxTokensHint - Max tokens hint
54
- * @param {string} projectPath - Project root path
55
- * @returns {Object} Compiled context slice
56
- */
57
- export async function compileContext(request, projectPath = process.cwd()) {
58
- const contextId = `ctxslice_${crypto.randomBytes(4).toString('hex')}`;
59
- const { task, focus = {}, constraints = [], maxTokensHint = 4000 } = request;
60
-
61
- const truthpack = await loadTruthpack(projectPath);
62
- if (!truthpack) {
63
- return {
64
- contextId,
65
- error: 'No truth pack found. Run `vibecheck ctx` first.',
66
- summary: null,
67
- truthSlice: null,
68
- evidence: [],
69
- invariants: [],
70
- };
71
- }
72
-
73
- const invariantsData = await loadInvariants(projectPath);
74
-
75
- // Analyze task to determine relevant domains
76
- const analysis = analyzeTask(task);
77
-
78
- // Filter truth pack to relevant content
79
- const truthSlice = buildTruthSlice(truthpack, analysis, focus);
80
-
81
- // Gather evidence for the slice
82
- const evidence = gatherEvidence(truthSlice);
83
-
84
- // Select applicable invariants
85
- const applicableInvariants = selectInvariants(invariantsData, analysis);
86
-
87
- // Generate summary
88
- const summary = generateSummary(task, truthSlice, analysis);
89
-
90
- // Apply constraints
91
- const appliedConstraints = applyConstraints(truthSlice, constraints);
92
-
93
- // Estimate token count
94
- const tokenEstimate = estimateTokens(truthSlice, evidence);
95
-
96
- return {
97
- contextId,
98
- summary,
99
- task,
100
- analysis: {
101
- domains: analysis.domains,
102
- keywords: analysis.keywords,
103
- action: analysis.action,
104
- },
105
- truthSlice,
106
- evidence: evidence.slice(0, 50), // Limit evidence
107
- invariants: applicableInvariants.map(i => i.id),
108
- constraints: appliedConstraints,
109
- tokenEstimate,
110
- warnings: generateWarnings(analysis, tokenEstimate, maxTokensHint),
111
- };
112
- }
113
-
114
- /**
115
- * Analyze task to determine domains, keywords, and action
116
- */
117
- function analyzeTask(task) {
118
- const taskLower = task.toLowerCase();
119
-
120
- // Detect domains
121
- const domains = [];
122
- if (/auth|login|logout|session|password|jwt|token/i.test(task)) domains.push('auth');
123
- if (/billing|payment|stripe|subscription|checkout|tier|pro|enterprise/i.test(task)) domains.push('billing');
124
- if (/route|endpoint|api|handler|fetch|axios/i.test(task)) domains.push('api');
125
- if (/component|button|form|ui|page|modal|dialog/i.test(task)) domains.push('ui');
126
- if (/database|prisma|model|schema|query|db/i.test(task)) domains.push('data');
127
- if (/test|spec|jest|vitest/i.test(task)) domains.push('testing');
128
- if (/security|xss|csrf|injection|sanitize/i.test(task)) domains.push('security');
129
- if (/env|environment|config|secret/i.test(task)) domains.push('env');
130
-
131
- if (domains.length === 0) domains.push('general');
132
-
133
- // Detect action type
134
- let action = 'unknown';
135
- if (/fix|bug|broken|error|issue|problem|dead/i.test(task)) action = 'fix';
136
- else if (/add|create|implement|build|new/i.test(task)) action = 'create';
137
- else if (/update|change|modify|refactor/i.test(task)) action = 'update';
138
- else if (/delete|remove|drop/i.test(task)) action = 'delete';
139
- else if (/wire|connect|integrate|link/i.test(task)) action = 'integrate';
140
- else if (/verify|check|validate|test/i.test(task)) action = 'verify';
141
-
142
- // Extract keywords
143
- const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'this', 'that', 'these', 'those', 'i', 'me', 'my', 'we', 'our', 'you', 'your', 'it', 'its', 'they', 'them', 'their', 'what', 'which', 'who', 'and', 'but', 'if', 'or', 'because', 'fix', 'add', 'update', 'create', 'make', 'build']);
144
-
145
- const keywords = task
146
- .toLowerCase()
147
- .replace(/[^a-z0-9\s]/g, ' ')
148
- .split(/\s+/)
149
- .filter(word => word.length > 2 && !stopWords.has(word));
150
-
151
- return { domains, action, keywords };
152
- }
153
-
154
- /**
155
- * Build a filtered truth slice based on analysis and focus
156
- */
157
- function buildTruthSlice(truthpack, analysis, focus) {
158
- const slice = {
159
- routes: { server: [], clientRefs: [] },
160
- env: { vars: [] },
161
- auth: null,
162
- billing: null,
163
- };
164
-
165
- // Filter routes
166
- if (focus.routes && focus.routes.length > 0) {
167
- slice.routes.server = truthpack.routes.server.filter(r =>
168
- focus.routes.some(fr => r.path.includes(fr) || fr.includes(r.path))
169
- );
170
- slice.routes.clientRefs = truthpack.routes.clientRefs.filter(r =>
171
- focus.routes.some(fr => r.path.includes(fr) || fr.includes(r.path))
172
- );
173
- } else if (analysis.keywords.length > 0) {
174
- slice.routes.server = truthpack.routes.server.filter(r =>
175
- analysis.keywords.some(k => r.path.includes(k) || r.handler.includes(k))
176
- );
177
- slice.routes.clientRefs = truthpack.routes.clientRefs.filter(r =>
178
- analysis.keywords.some(k => r.path.includes(k) || r.source.includes(k))
179
- );
180
- }
181
-
182
- // Include routes from focused files
183
- if (focus.files && focus.files.length > 0) {
184
- const fileRoutes = truthpack.routes.server.filter(r =>
185
- focus.files.some(f => r.handler.includes(f))
186
- );
187
- const fileClientRefs = truthpack.routes.clientRefs.filter(r =>
188
- focus.files.some(f => r.source.includes(f))
189
- );
190
-
191
- slice.routes.server.push(...fileRoutes.filter(r => !slice.routes.server.includes(r)));
192
- slice.routes.clientRefs.push(...fileClientRefs.filter(r => !slice.routes.clientRefs.includes(r)));
193
- }
194
-
195
- // Filter env vars based on keywords
196
- if (analysis.keywords.length > 0) {
197
- slice.env.vars = truthpack.env.vars.filter(v =>
198
- analysis.keywords.some(k => v.name.toLowerCase().includes(k))
199
- );
200
- }
201
-
202
- // Include auth if relevant
203
- if (analysis.domains.includes('auth')) {
204
- slice.auth = truthpack.auth;
205
- }
206
-
207
- // Include billing if relevant
208
- if (analysis.domains.includes('billing')) {
209
- slice.billing = truthpack.billing;
210
- }
211
-
212
- return slice;
213
- }
214
-
215
- /**
216
- * Gather evidence from the truth slice
217
- */
218
- function gatherEvidence(truthSlice) {
219
- const evidence = [];
220
-
221
- for (const route of truthSlice.routes.server) {
222
- if (route.evidence) evidence.push(...route.evidence);
223
- }
224
-
225
- for (const ref of truthSlice.routes.clientRefs) {
226
- if (ref.evidence) evidence.push(...ref.evidence);
227
- }
228
-
229
- for (const v of truthSlice.env.vars) {
230
- if (v.references) evidence.push(...v.references);
231
- }
232
-
233
- if (truthSlice.auth?.model?.signals) {
234
- for (const s of truthSlice.auth.model.signals) {
235
- if (s.evidence) evidence.push(...s.evidence);
236
- }
237
- }
238
-
239
- return evidence;
240
- }
241
-
242
- /**
243
- * Select applicable invariants based on task analysis
244
- */
245
- function selectInvariants(invariantsData, analysis) {
246
- const invariants = invariantsData.invariants || [];
247
-
248
- const domainToApplies = {
249
- auth: ['server', 'middleware', 'api'],
250
- billing: ['server', 'ui'],
251
- api: ['api', 'server'],
252
- ui: ['ui'],
253
- security: ['all', 'server'],
254
- };
255
-
256
- const relevantApplies = new Set();
257
- for (const domain of analysis.domains) {
258
- const applies = domainToApplies[domain] || [];
259
- applies.forEach(a => relevantApplies.add(a));
260
- }
261
-
262
- // Always include security-related invariants for fix/integrate actions
263
- if (analysis.action === 'fix' || analysis.action === 'integrate') {
264
- relevantApplies.add('server');
265
- relevantApplies.add('middleware');
266
- }
267
-
268
- return invariants.filter(inv => {
269
- if (!inv.appliesTo) return true;
270
- return inv.appliesTo.some(a => relevantApplies.has(a) || a === 'all');
271
- });
272
- }
273
-
274
- /**
275
- * Generate a summary of the context
276
- */
277
- function generateSummary(task, truthSlice, analysis) {
278
- const parts = [];
279
-
280
- parts.push(`Task: ${task}`);
281
- parts.push(`Domains: ${analysis.domains.join(', ')}`);
282
- parts.push(`Action: ${analysis.action}`);
283
-
284
- if (truthSlice.routes.server.length > 0) {
285
- parts.push(`Server routes: ${truthSlice.routes.server.length}`);
286
- }
287
-
288
- if (truthSlice.routes.clientRefs.length > 0) {
289
- parts.push(`Client refs: ${truthSlice.routes.clientRefs.length}`);
290
- }
291
-
292
- // Check for mismatches (gaps)
293
- const serverPaths = new Set(truthSlice.routes.server.map(r => r.path));
294
- const clientPaths = truthSlice.routes.clientRefs.map(r => r.path);
295
- const mismatches = clientPaths.filter(p => !serverPaths.has(p));
296
-
297
- if (mismatches.length > 0) {
298
- parts.push(`⚠️ Route mismatches: ${mismatches.join(', ')}`);
299
- }
300
-
301
- return parts.join(' | ');
302
- }
303
-
304
- /**
305
- * Apply constraints to the truth slice
306
- */
307
- function applyConstraints(truthSlice, constraints) {
308
- const applied = [];
309
-
310
- for (const constraint of constraints) {
311
- switch (constraint) {
312
- case 'do_not_invent_routes':
313
- applied.push({
314
- id: 'do_not_invent_routes',
315
- description: 'Only use routes that exist in the truth pack',
316
- enforced: true,
317
- });
318
- break;
319
-
320
- case 'no_new_env_vars_without_proof':
321
- applied.push({
322
- id: 'no_new_env_vars_without_proof',
323
- description: 'Do not add env vars that are not declared in .env.example',
324
- enforced: true,
325
- });
326
- break;
327
-
328
- case 'require_auth_verification':
329
- applied.push({
330
- id: 'require_auth_verification',
331
- description: 'Verify auth is enforced on protected routes',
332
- enforced: true,
333
- });
334
- break;
335
- }
336
- }
337
-
338
- return applied;
339
- }
340
-
341
- /**
342
- * Estimate token count for the context
343
- */
344
- function estimateTokens(truthSlice, evidence) {
345
- let tokens = 0;
346
-
347
- // Routes: ~50 tokens each
348
- tokens += truthSlice.routes.server.length * 50;
349
- tokens += truthSlice.routes.clientRefs.length * 30;
350
-
351
- // Env vars: ~20 tokens each
352
- tokens += truthSlice.env.vars.length * 20;
353
-
354
- // Auth/billing: ~200 tokens each if present
355
- if (truthSlice.auth) tokens += 200;
356
- if (truthSlice.billing) tokens += 200;
357
-
358
- // Evidence: ~100 tokens each
359
- tokens += evidence.length * 100;
360
-
361
- return tokens;
362
- }
363
-
364
- /**
365
- * Generate warnings based on context
366
- */
367
- function generateWarnings(analysis, tokenEstimate, maxTokensHint) {
368
- const warnings = [];
369
-
370
- if (analysis.domains.includes('auth') || analysis.domains.includes('billing')) {
371
- warnings.push('High-stakes domain detected - verify all claims before making changes');
372
- }
373
-
374
- if (tokenEstimate > maxTokensHint) {
375
- warnings.push(`Context exceeds ${maxTokensHint} token hint (est: ${tokenEstimate}). Consider narrowing focus.`);
376
- }
377
-
378
- if (analysis.action === 'fix' && analysis.domains.includes('auth')) {
379
- warnings.push('Auth fix - ensure changes do not weaken security');
380
- }
381
-
382
- return warnings;
383
- }
384
-
385
- export default { compileContext };
@@ -1,17 +0,0 @@
1
- // bin/runners/runGate.js
2
- // CI/CD gate command - wraps ship for pass/fail CI pipelines
3
- const { runShip } = require("./runShip");
4
-
5
- async function runGate(args) {
6
- // Gate is essentially ship with strict exit codes for CI
7
- // Pass through args to ship
8
- const exitCode = await runShip([...args, "--ci"]);
9
-
10
- // Exit codes:
11
- // 0 = SHIP (pass)
12
- // 1 = WARN (pass by default, fail with --strict)
13
- // 2 = BLOCK (fail)
14
- return exitCode;
15
- }
16
-
17
- module.exports = { runGate };
@@ -1,164 +0,0 @@
1
- // bin/runners/runInitGha.js
2
- const fs = require("fs");
3
- const path = require("path");
4
-
5
- function ensureDir(p) {
6
- fs.mkdirSync(p, { recursive: true });
7
- }
8
-
9
- function workflowYaml({ packageRunner = "npx vibecheck", failOnWarn = false, policy = "ci" } = {}) {
10
- const policyFlag = policy !== "ci" ? `--policy ${policy}` : "--policy ci";
11
- const failFlag = failOnWarn ? "--fail-on-warn" : "";
12
-
13
- return `name: vibecheck
14
-
15
- on:
16
- pull_request:
17
- types: [opened, synchronize, reopened]
18
-
19
- permissions:
20
- contents: read
21
- pull-requests: write
22
-
23
- env:
24
- VIBECHECK_CI: "true"
25
-
26
- jobs:
27
- vibecheck:
28
- runs-on: ubuntu-latest
29
- steps:
30
- - uses: actions/checkout@v4
31
-
32
- - uses: actions/setup-node@v4
33
- with:
34
- node-version: 20
35
-
36
- - name: Install deps (auto-detect package manager)
37
- run: |
38
- set -e
39
- if [ -f pnpm-lock.yaml ]; then
40
- corepack enable
41
- pnpm install --frozen-lockfile
42
- elif [ -f package-lock.json ]; then
43
- npm ci
44
- elif [ -f yarn.lock ]; then
45
- corepack enable
46
- yarn install --frozen-lockfile
47
- else
48
- npm install
49
- fi
50
-
51
- - name: Vibecheck - Build truthpack
52
- run: ${packageRunner} ctx ${policyFlag}
53
-
54
- - name: Vibecheck - Generate PR report
55
- id: vc
56
- run: |
57
- set +e
58
- mkdir -p .vibecheck
59
- ${packageRunner} pr --out .vibecheck/pr_comment.md ${policyFlag} ${failFlag}
60
- code=$?
61
- echo "code=$code" >> $GITHUB_OUTPUT
62
- echo "verdict=$code" >> $GITHUB_ENV
63
- echo "---- vibecheck exit code: $code ----"
64
- # Generate HTML report
65
- ${packageRunner} ship --html || true
66
- exit 0
67
-
68
- - name: Post PR comment
69
- if: github.event_name == 'pull_request'
70
- uses: actions/github-script@v7
71
- with:
72
- script: |
73
- const fs = require('fs');
74
- const commentPath = '.vibecheck/pr_comment.md';
75
- if (!fs.existsSync(commentPath)) {
76
- core.warning('No PR comment generated');
77
- return;
78
- }
79
- const body = fs.readFileSync(commentPath, 'utf8');
80
- const pr = context.payload.pull_request;
81
- if (!pr) {
82
- core.warning('No pull_request in context; skipping comment');
83
- return;
84
- }
85
-
86
- // Find existing vibecheck comment to update
87
- const { data: comments } = await github.rest.issues.listComments({
88
- owner: context.repo.owner,
89
- repo: context.repo.repo,
90
- issue_number: pr.number,
91
- });
92
- const existingComment = comments.find(c =>
93
- c.body.includes('Vibecheck:') && c.user.type === 'Bot'
94
- );
95
-
96
- if (existingComment) {
97
- await github.rest.issues.updateComment({
98
- owner: context.repo.owner,
99
- repo: context.repo.repo,
100
- comment_id: existingComment.id,
101
- body
102
- });
103
- } else {
104
- await github.rest.issues.createComment({
105
- owner: context.repo.owner,
106
- repo: context.repo.repo,
107
- issue_number: pr.number,
108
- body
109
- });
110
- }
111
-
112
- - name: Upload artifacts
113
- if: always()
114
- uses: actions/upload-artifact@v4
115
- with:
116
- name: vibecheck-report
117
- path: |
118
- .vibecheck/last_ship.json
119
- .vibecheck/last_ship.html
120
- .vibecheck/pr_comment.md
121
- .vibecheck/contracts_diff.json
122
- retention-days: 30
123
- if-no-files-found: ignore
124
-
125
- - name: Enforce verdict
126
- run: |
127
- code="\${{ steps.vc.outputs.code }}"
128
- echo "vibecheck verdict code: $code"
129
- if [ "$code" = "0" ]; then
130
- echo "✅ SHIP - All checks passed"
131
- exit 0
132
- fi
133
- if [ "$code" = "1" ]; then
134
- echo "⚠️ WARN - Warnings found (failing per policy)"
135
- exit 1
136
- fi
137
- if [ "$code" = "2" ]; then
138
- echo "🚫 BLOCK - Blocking issues found"
139
- exit 1
140
- fi
141
- echo "❓ Unknown exit code"
142
- exit 1
143
- `;
144
- }
145
-
146
- async function runInitGha({ repoRoot, failOnWarn = false, policy = "ci" } = {}) {
147
- const root = repoRoot || process.cwd();
148
- const wfDir = path.join(root, ".github", "workflows");
149
- ensureDir(wfDir);
150
-
151
- const wfPath = path.join(wfDir, "vibecheck.yml");
152
- fs.writeFileSync(wfPath, workflowYaml({ failOnWarn, policy }), "utf8");
153
-
154
- console.log(`✅ Wrote: ${path.relative(root, wfPath).replace(/\\/g, "/")}`);
155
- console.log(`\nPolicy: ${policy}`);
156
- console.log(`Fail on warn: ${failOnWarn}`);
157
- console.log(`\nNotes:`);
158
- console.log(`- If your CLI isn't called via "npx vibecheck", edit the workflow line.`);
159
- console.log(`- Set VIBECHECK_API_KEY as a GitHub Actions secret for cloud features.`);
160
- console.log(`- Artifacts (JSON + HTML reports) uploaded automatically.`);
161
- console.log(`- PR comments auto-updated on each push.`);
162
- }
163
-
164
- module.exports = { runInitGha, workflowYaml };