@vibecheckai/cli 3.7.0 → 3.9.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 (118) hide show
  1. package/README.md +135 -63
  2. package/bin/_deprecations.js +447 -19
  3. package/bin/_router.js +1 -1
  4. package/bin/registry.js +347 -280
  5. package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
  6. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  7. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  8. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  9. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  10. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  11. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  12. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  13. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  14. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  15. package/bin/runners/lib/agent-firewall/index.js +200 -0
  16. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  17. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  18. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  19. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  20. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  21. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  22. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  23. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  24. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  25. package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
  26. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  27. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  28. package/bin/runners/lib/artifact-envelope.js +540 -0
  29. package/bin/runners/lib/auth-shared.js +977 -0
  30. package/bin/runners/lib/checkpoint.js +941 -0
  31. package/bin/runners/lib/cleanup/engine.js +571 -0
  32. package/bin/runners/lib/cleanup/index.js +53 -0
  33. package/bin/runners/lib/cleanup/output.js +375 -0
  34. package/bin/runners/lib/cleanup/rules.js +1060 -0
  35. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  36. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  37. package/bin/runners/lib/doctor/fix-script.js +336 -0
  38. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  39. package/bin/runners/lib/doctor/modules/index.js +62 -3
  40. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  41. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  42. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  43. package/bin/runners/lib/engine/ast-cache.js +210 -210
  44. package/bin/runners/lib/engine/auth-extractor.js +211 -211
  45. package/bin/runners/lib/engine/billing-extractor.js +112 -112
  46. package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
  47. package/bin/runners/lib/engine/env-extractor.js +207 -207
  48. package/bin/runners/lib/engine/express-extractor.js +208 -208
  49. package/bin/runners/lib/engine/extractors.js +849 -849
  50. package/bin/runners/lib/engine/index.js +207 -207
  51. package/bin/runners/lib/engine/repo-index.js +514 -514
  52. package/bin/runners/lib/engine/types.js +124 -124
  53. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  54. package/bin/runners/lib/entitlements-v2.js +2 -2
  55. package/bin/runners/lib/missions/briefing.js +427 -0
  56. package/bin/runners/lib/missions/checkpoint.js +753 -0
  57. package/bin/runners/lib/missions/hardening.js +851 -0
  58. package/bin/runners/lib/missions/plan.js +421 -32
  59. package/bin/runners/lib/missions/safety-gates.js +645 -0
  60. package/bin/runners/lib/missions/schema.js +478 -0
  61. package/bin/runners/lib/packs/bundle.js +675 -0
  62. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  63. package/bin/runners/lib/packs/pack-factory.js +837 -0
  64. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  65. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  66. package/bin/runners/lib/safelist/index.js +96 -0
  67. package/bin/runners/lib/safelist/integration.js +334 -0
  68. package/bin/runners/lib/safelist/matcher.js +696 -0
  69. package/bin/runners/lib/safelist/schema.js +948 -0
  70. package/bin/runners/lib/safelist/store.js +438 -0
  71. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  72. package/bin/runners/lib/ship-gate.js +832 -0
  73. package/bin/runners/lib/ship-manifest.js +1153 -0
  74. package/bin/runners/lib/ship-output.js +1 -1
  75. package/bin/runners/lib/unified-cli-output.js +710 -383
  76. package/bin/runners/lib/upsell.js +3 -3
  77. package/bin/runners/lib/why-tree.js +650 -0
  78. package/bin/runners/runAllowlist.js +33 -4
  79. package/bin/runners/runApprove.js +240 -1122
  80. package/bin/runners/runAudit.js +692 -0
  81. package/bin/runners/runAuth.js +325 -29
  82. package/bin/runners/runCheckpoint.js +442 -494
  83. package/bin/runners/runCleanup.js +343 -0
  84. package/bin/runners/runDoctor.js +269 -19
  85. package/bin/runners/runFix.js +411 -32
  86. package/bin/runners/runForge.js +411 -0
  87. package/bin/runners/runIntent.js +906 -0
  88. package/bin/runners/runKickoff.js +878 -0
  89. package/bin/runners/runLaunch.js +2000 -0
  90. package/bin/runners/runLink.js +785 -0
  91. package/bin/runners/runMcp.js +1741 -837
  92. package/bin/runners/runPacks.js +2089 -0
  93. package/bin/runners/runPolish.js +41 -0
  94. package/bin/runners/runReality.js +178 -1
  95. package/bin/runners/runSafelist.js +1190 -0
  96. package/bin/runners/runScan.js +21 -9
  97. package/bin/runners/runShield.js +1282 -0
  98. package/bin/runners/runShip.js +395 -16
  99. package/bin/vibecheck.js +34 -6
  100. package/mcp-server/README.md +117 -158
  101. package/mcp-server/handlers/index.ts +2 -2
  102. package/mcp-server/handlers/tool-handler.ts +50 -11
  103. package/mcp-server/index.js +16 -0
  104. package/mcp-server/intent-firewall-interceptor.js +529 -0
  105. package/mcp-server/lib/executor.ts +5 -5
  106. package/mcp-server/lib/index.ts +14 -4
  107. package/mcp-server/lib/sandbox.test.ts +4 -4
  108. package/mcp-server/lib/sandbox.ts +2 -2
  109. package/mcp-server/manifest.json +473 -0
  110. package/mcp-server/package.json +1 -1
  111. package/mcp-server/registry/tool-registry.js +315 -523
  112. package/mcp-server/registry/tools.json +442 -428
  113. package/mcp-server/registry.test.ts +18 -12
  114. package/mcp-server/tier-auth.js +68 -11
  115. package/mcp-server/tools-v3.js +70 -16
  116. package/mcp-server/tsconfig.json +1 -0
  117. package/package.json +2 -1
  118. package/bin/runners/runProof.zip +0 -0
@@ -0,0 +1,438 @@
1
+ /**
2
+ * vibecheck safelist - Storage & Persistence
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * Handles loading, saving, and migrating safelist files
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ */
8
+
9
+ "use strict";
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const {
14
+ SCHEMA_VERSION,
15
+ SCOPE_TYPES,
16
+ validateSafelist,
17
+ createEmptySafelist,
18
+ } = require("./schema");
19
+
20
+ // ═══════════════════════════════════════════════════════════════════════════════
21
+ // FILE PATHS
22
+ // ═══════════════════════════════════════════════════════════════════════════════
23
+
24
+ /**
25
+ * Get safelist file paths for a project
26
+ * @param {string} projectRoot - Project root directory
27
+ * @returns {Object} Paths for repo and local safelists
28
+ */
29
+ function getSafelistPaths(projectRoot) {
30
+ const vibecheckDir = path.join(projectRoot, ".vibecheck");
31
+
32
+ return {
33
+ repo: path.join(vibecheckDir, "safelist.json"),
34
+ local: path.join(vibecheckDir, "safelist.local.json"),
35
+ legacy: path.join(vibecheckDir, "allowlist.json"),
36
+ dir: vibecheckDir,
37
+ };
38
+ }
39
+
40
+ // ═══════════════════════════════════════════════════════════════════════════════
41
+ // LOADING
42
+ // ═══════════════════════════════════════════════════════════════════════════════
43
+
44
+ /**
45
+ * Load a safelist file with error handling
46
+ * @param {string} filePath - Path to safelist file
47
+ * @returns {{ success: boolean, data: Object|null, error: string|null }}
48
+ */
49
+ function loadSafelistFile(filePath) {
50
+ if (!fs.existsSync(filePath)) {
51
+ return { success: true, data: null, error: null };
52
+ }
53
+
54
+ try {
55
+ const content = fs.readFileSync(filePath, "utf8");
56
+ const data = JSON.parse(content);
57
+ return { success: true, data, error: null };
58
+ } catch (err) {
59
+ return {
60
+ success: false,
61
+ data: null,
62
+ error: `Failed to parse ${path.basename(filePath)}: ${err.message}`
63
+ };
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Load all safelists for a project (repo + local, merged)
69
+ * @param {string} projectRoot - Project root directory
70
+ * @returns {{ safelist: Object, warnings: string[], errors: string[] }}
71
+ */
72
+ function loadSafelist(projectRoot) {
73
+ const paths = getSafelistPaths(projectRoot);
74
+ const warnings = [];
75
+ const errors = [];
76
+
77
+ // Try to load repo safelist
78
+ let repoSafelist = createEmptySafelist();
79
+ const repoResult = loadSafelistFile(paths.repo);
80
+
81
+ if (!repoResult.success) {
82
+ errors.push(repoResult.error);
83
+ } else if (repoResult.data) {
84
+ // Validate
85
+ const validation = validateSafelist(repoResult.data);
86
+ warnings.push(...validation.warnings);
87
+
88
+ if (validation.valid) {
89
+ repoSafelist = repoResult.data;
90
+ } else {
91
+ errors.push(...validation.errors.map(e => `[repo] ${e}`));
92
+ }
93
+ }
94
+
95
+ // Try to load local safelist
96
+ let localSafelist = createEmptySafelist();
97
+ const localResult = loadSafelistFile(paths.local);
98
+
99
+ if (!localResult.success) {
100
+ errors.push(localResult.error);
101
+ } else if (localResult.data) {
102
+ const validation = validateSafelist(localResult.data);
103
+ warnings.push(...validation.warnings);
104
+
105
+ if (validation.valid) {
106
+ localSafelist = localResult.data;
107
+ } else {
108
+ errors.push(...validation.errors.map(e => `[local] ${e}`));
109
+ }
110
+ }
111
+
112
+ // Check for legacy allowlist.json and migrate
113
+ if (fs.existsSync(paths.legacy) && !fs.existsSync(paths.repo)) {
114
+ const legacyResult = loadSafelistFile(paths.legacy);
115
+ if (legacyResult.success && legacyResult.data) {
116
+ warnings.push("Found legacy allowlist.json - consider migrating with 'vibecheck safelist migrate'");
117
+ }
118
+ }
119
+
120
+ // Merge safelists (local entries override/extend repo entries)
121
+ const mergedSafelist = {
122
+ version: SCHEMA_VERSION,
123
+ metadata: {
124
+ ...repoSafelist.metadata,
125
+ loadedAt: new Date().toISOString(),
126
+ },
127
+ entries: [
128
+ ...repoSafelist.entries.map(e => ({ ...e, _source: "repo" })),
129
+ ...localSafelist.entries.map(e => ({ ...e, _source: "local" })),
130
+ ],
131
+ };
132
+
133
+ return {
134
+ safelist: mergedSafelist,
135
+ repoSafelist,
136
+ localSafelist,
137
+ warnings,
138
+ errors,
139
+ paths,
140
+ };
141
+ }
142
+
143
+ // ═══════════════════════════════════════════════════════════════════════════════
144
+ // SAVING
145
+ // ═══════════════════════════════════════════════════════════════════════════════
146
+
147
+ /**
148
+ * Save safelist to file
149
+ * @param {string} filePath - Path to save to
150
+ * @param {Object} safelist - Safelist data
151
+ * @returns {{ success: boolean, error: string|null }}
152
+ */
153
+ function saveSafelistFile(filePath, safelist) {
154
+ try {
155
+ // Ensure directory exists
156
+ const dir = path.dirname(filePath);
157
+ fs.mkdirSync(dir, { recursive: true });
158
+
159
+ // Remove internal properties before saving
160
+ const cleaned = {
161
+ ...safelist,
162
+ entries: safelist.entries.map(e => {
163
+ const { _source, ...rest } = e;
164
+ return rest;
165
+ }),
166
+ };
167
+
168
+ // Update metadata
169
+ cleaned.metadata = {
170
+ ...cleaned.metadata,
171
+ lastModified: new Date().toISOString(),
172
+ };
173
+
174
+ fs.writeFileSync(filePath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
175
+ return { success: true, error: null };
176
+ } catch (err) {
177
+ return { success: false, error: err.message };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Save an entry to the appropriate safelist file
183
+ * @param {string} projectRoot - Project root
184
+ * @param {Object} entry - Entry to save
185
+ * @param {string} scope - "repo" or "local"
186
+ * @returns {{ success: boolean, error: string|null }}
187
+ */
188
+ function saveEntry(projectRoot, entry, scope = "repo") {
189
+ const paths = getSafelistPaths(projectRoot);
190
+ const filePath = scope === "local" ? paths.local : paths.repo;
191
+
192
+ // Load existing
193
+ const result = loadSafelistFile(filePath);
194
+ const safelist = result.data || createEmptySafelist();
195
+
196
+ // Add entry
197
+ safelist.entries.push(entry);
198
+
199
+ // Save
200
+ return saveSafelistFile(filePath, safelist);
201
+ }
202
+
203
+ /**
204
+ * Remove an entry from safelist
205
+ * @param {string} projectRoot - Project root
206
+ * @param {string} entryId - Entry ID to remove
207
+ * @returns {{ success: boolean, removed: boolean, source: string|null, error: string|null }}
208
+ */
209
+ function removeEntry(projectRoot, entryId) {
210
+ const paths = getSafelistPaths(projectRoot);
211
+ let removed = false;
212
+ let source = null;
213
+
214
+ // Try repo safelist first
215
+ const repoResult = loadSafelistFile(paths.repo);
216
+ if (repoResult.data) {
217
+ const before = repoResult.data.entries.length;
218
+ repoResult.data.entries = repoResult.data.entries.filter(e => e.id !== entryId);
219
+ if (repoResult.data.entries.length < before) {
220
+ removed = true;
221
+ source = "repo";
222
+ const saveResult = saveSafelistFile(paths.repo, repoResult.data);
223
+ if (!saveResult.success) {
224
+ return { success: false, removed: false, source: null, error: saveResult.error };
225
+ }
226
+ }
227
+ }
228
+
229
+ // Try local safelist
230
+ if (!removed) {
231
+ const localResult = loadSafelistFile(paths.local);
232
+ if (localResult.data) {
233
+ const before = localResult.data.entries.length;
234
+ localResult.data.entries = localResult.data.entries.filter(e => e.id !== entryId);
235
+ if (localResult.data.entries.length < before) {
236
+ removed = true;
237
+ source = "local";
238
+ const saveResult = saveSafelistFile(paths.local, localResult.data);
239
+ if (!saveResult.success) {
240
+ return { success: false, removed: false, source: null, error: saveResult.error };
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ return { success: true, removed, source, error: null };
247
+ }
248
+
249
+ /**
250
+ * Update an existing entry
251
+ * @param {string} projectRoot - Project root
252
+ * @param {string} entryId - Entry ID to update
253
+ * @param {Object} updates - Fields to update
254
+ * @returns {{ success: boolean, updated: boolean, error: string|null }}
255
+ */
256
+ function updateEntry(projectRoot, entryId, updates) {
257
+ const { safelist, paths } = loadSafelist(projectRoot);
258
+
259
+ const entry = safelist.entries.find(e => e.id === entryId);
260
+ if (!entry) {
261
+ return { success: false, updated: false, error: "Entry not found" };
262
+ }
263
+
264
+ // Apply updates (deep merge)
265
+ const updated = deepMerge(entry, updates);
266
+
267
+ // Add audit trail
268
+ if (!updated.audit) updated.audit = { history: [] };
269
+ updated.audit.history.push({
270
+ action: "update",
271
+ timestamp: new Date().toISOString(),
272
+ changes: Object.keys(updates),
273
+ by: process.env.USER || process.env.USERNAME || "cli",
274
+ });
275
+
276
+ // Determine which file to update
277
+ const filePath = entry._source === "local" ? paths.local : paths.repo;
278
+ const result = loadSafelistFile(filePath);
279
+
280
+ if (!result.data) {
281
+ return { success: false, updated: false, error: "Source file not found" };
282
+ }
283
+
284
+ // Update in place
285
+ const idx = result.data.entries.findIndex(e => e.id === entryId);
286
+ if (idx === -1) {
287
+ return { success: false, updated: false, error: "Entry not found in source file" };
288
+ }
289
+
290
+ result.data.entries[idx] = updated;
291
+
292
+ const saveResult = saveSafelistFile(filePath, result.data);
293
+ return {
294
+ success: saveResult.success,
295
+ updated: saveResult.success,
296
+ error: saveResult.error
297
+ };
298
+ }
299
+
300
+ // ═══════════════════════════════════════════════════════════════════════════════
301
+ // MIGRATION
302
+ // ═══════════════════════════════════════════════════════════════════════════════
303
+
304
+ /**
305
+ * Migrate legacy allowlist.json to new safelist format
306
+ * @param {string} projectRoot - Project root
307
+ * @returns {{ success: boolean, migrated: number, error: string|null }}
308
+ */
309
+ function migrateLegacyAllowlist(projectRoot) {
310
+ const paths = getSafelistPaths(projectRoot);
311
+
312
+ if (!fs.existsSync(paths.legacy)) {
313
+ return { success: true, migrated: 0, error: null };
314
+ }
315
+
316
+ const legacyResult = loadSafelistFile(paths.legacy);
317
+ if (!legacyResult.success || !legacyResult.data) {
318
+ return { success: false, migrated: 0, error: legacyResult.error || "No data" };
319
+ }
320
+
321
+ const legacy = legacyResult.data;
322
+ const newSafelist = createEmptySafelist();
323
+
324
+ // Convert each legacy entry
325
+ for (const old of (legacy.entries || [])) {
326
+ const newEntry = {
327
+ id: old.id || `SL_migrated_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
328
+ type: old.pattern ? "pattern" : "finding",
329
+ target: {},
330
+ justification: {
331
+ reason: old.reason || "Migrated from legacy allowlist",
332
+ category: "false-positive",
333
+ },
334
+ owner: {
335
+ name: old.addedBy || "migrated",
336
+ },
337
+ scope: {
338
+ type: old.scope === "global" ? "repo" : (old.scope || "repo"),
339
+ },
340
+ lifecycle: {
341
+ createdAt: old.addedAt || new Date().toISOString(),
342
+ createdBy: "migration",
343
+ matchCount: 0,
344
+ },
345
+ };
346
+
347
+ // Map target
348
+ if (old.findingId) newEntry.target.findingId = old.findingId;
349
+ if (old.pattern) newEntry.target.pattern = old.pattern;
350
+ if (old.file) newEntry.target.file = old.file;
351
+ if (old.lines) newEntry.target.lines = old.lines;
352
+
353
+ // Map expiration
354
+ if (old.expiresAt) {
355
+ newEntry.lifecycle.expiresAt = old.expiresAt;
356
+ }
357
+
358
+ newSafelist.entries.push(newEntry);
359
+ }
360
+
361
+ // Save new safelist
362
+ const saveResult = saveSafelistFile(paths.repo, newSafelist);
363
+ if (!saveResult.success) {
364
+ return { success: false, migrated: 0, error: saveResult.error };
365
+ }
366
+
367
+ // Rename legacy file
368
+ try {
369
+ fs.renameSync(paths.legacy, paths.legacy + ".migrated");
370
+ } catch {
371
+ // Ignore rename errors
372
+ }
373
+
374
+ return { success: true, migrated: newSafelist.entries.length, error: null };
375
+ }
376
+
377
+ // ═══════════════════════════════════════════════════════════════════════════════
378
+ // UTILITIES
379
+ // ═══════════════════════════════════════════════════════════════════════════════
380
+
381
+ /**
382
+ * Deep merge objects
383
+ */
384
+ function deepMerge(target, source) {
385
+ const result = { ...target };
386
+
387
+ for (const key of Object.keys(source)) {
388
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
389
+ result[key] = deepMerge(target[key] || {}, source[key]);
390
+ } else {
391
+ result[key] = source[key];
392
+ }
393
+ }
394
+
395
+ return result;
396
+ }
397
+
398
+ /**
399
+ * Ensure .gitignore includes local safelist
400
+ * @param {string} projectRoot - Project root
401
+ */
402
+ function ensureGitIgnore(projectRoot) {
403
+ const gitignorePath = path.join(projectRoot, ".gitignore");
404
+ const localSafelistPattern = ".vibecheck/safelist.local.json";
405
+
406
+ try {
407
+ let content = "";
408
+ if (fs.existsSync(gitignorePath)) {
409
+ content = fs.readFileSync(gitignorePath, "utf8");
410
+ }
411
+
412
+ if (!content.includes(localSafelistPattern)) {
413
+ const addition = `\n# vibecheck local safelist (machine-specific)\n${localSafelistPattern}\n`;
414
+ fs.appendFileSync(gitignorePath, addition);
415
+ return true;
416
+ }
417
+ } catch {
418
+ // Ignore errors
419
+ }
420
+
421
+ return false;
422
+ }
423
+
424
+ // ═══════════════════════════════════════════════════════════════════════════════
425
+ // EXPORTS
426
+ // ═══════════════════════════════════════════════════════════════════════════════
427
+
428
+ module.exports = {
429
+ getSafelistPaths,
430
+ loadSafelistFile,
431
+ loadSafelist,
432
+ saveSafelistFile,
433
+ saveEntry,
434
+ removeEntry,
435
+ updateEntry,
436
+ migrateLegacyAllowlist,
437
+ ensureGitIgnore,
438
+ };
@@ -0,0 +1,251 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "vibecheck/ship-manifest/v1",
4
+ "title": "Ship Manifest",
5
+ "description": "Vibecheck Ship Manifest - The authoritative artifact for ship verdicts",
6
+ "type": "object",
7
+ "required": ["version", "schema", "verdict", "repo", "evidence", "checks", "meta", "signature"],
8
+ "properties": {
9
+ "version": {
10
+ "type": "string",
11
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
12
+ "description": "Manifest format version (semver)"
13
+ },
14
+ "schema": {
15
+ "type": "string",
16
+ "const": "vibecheck/ship-manifest/v1",
17
+ "description": "Schema identifier"
18
+ },
19
+ "verdict": {
20
+ "type": "object",
21
+ "required": ["status", "exitCode", "score", "ciStatus", "canShip"],
22
+ "properties": {
23
+ "status": {
24
+ "type": "string",
25
+ "enum": ["SHIP", "WARN", "BLOCK"],
26
+ "description": "Final verdict"
27
+ },
28
+ "exitCode": {
29
+ "type": "integer",
30
+ "minimum": 0,
31
+ "maximum": 10,
32
+ "description": "CLI exit code (0=SHIP, 1=WARN, 2=BLOCK)"
33
+ },
34
+ "score": {
35
+ "type": "integer",
36
+ "minimum": 0,
37
+ "maximum": 100,
38
+ "description": "Quality score out of 100"
39
+ },
40
+ "ciStatus": {
41
+ "type": "string",
42
+ "enum": ["success", "warning", "failure"],
43
+ "description": "CI status string"
44
+ },
45
+ "canShip": {
46
+ "type": "boolean",
47
+ "description": "Whether the code is ready to ship"
48
+ }
49
+ },
50
+ "additionalProperties": false
51
+ },
52
+ "repo": {
53
+ "type": "object",
54
+ "required": ["root", "name", "fingerprint", "commit", "branch", "isDirty"],
55
+ "properties": {
56
+ "root": {
57
+ "type": "string",
58
+ "description": "Absolute path to repository root"
59
+ },
60
+ "name": {
61
+ "type": "string",
62
+ "maxLength": 100,
63
+ "description": "Repository name"
64
+ },
65
+ "fingerprint": {
66
+ "type": "string",
67
+ "pattern": "^[a-f0-9]{32}$",
68
+ "description": "Deterministic repo state fingerprint"
69
+ },
70
+ "commit": {
71
+ "type": "string",
72
+ "description": "Git commit hash (12 chars) or 'unknown'"
73
+ },
74
+ "branch": {
75
+ "type": "string",
76
+ "description": "Git branch name or 'unknown'"
77
+ },
78
+ "treeHash": {
79
+ "type": "string",
80
+ "description": "Git tree hash (12 chars) or 'unknown'"
81
+ },
82
+ "isDirty": {
83
+ "type": "boolean",
84
+ "description": "Whether working tree has uncommitted changes"
85
+ },
86
+ "gitAvailable": {
87
+ "type": "boolean",
88
+ "description": "Whether git was available for fingerprinting"
89
+ }
90
+ },
91
+ "additionalProperties": false
92
+ },
93
+ "evidence": {
94
+ "type": "object",
95
+ "required": ["summary", "whyTree", "coverage"],
96
+ "properties": {
97
+ "summary": {
98
+ "type": "object",
99
+ "required": ["total", "blockers", "warnings", "info"],
100
+ "properties": {
101
+ "total": { "type": "integer", "minimum": 0 },
102
+ "blockers": { "type": "integer", "minimum": 0 },
103
+ "warnings": { "type": "integer", "minimum": 0 },
104
+ "info": { "type": "integer", "minimum": 0 }
105
+ },
106
+ "additionalProperties": false
107
+ },
108
+ "whyTree": {
109
+ "type": "object",
110
+ "required": ["summary", "topIssues", "fixMissions"],
111
+ "properties": {
112
+ "summary": { "type": "string" },
113
+ "topIssues": {
114
+ "type": "array",
115
+ "items": {
116
+ "type": "object",
117
+ "required": ["id", "category", "title"],
118
+ "properties": {
119
+ "id": { "type": "string", "maxLength": 64 },
120
+ "category": { "type": "string", "maxLength": 50 },
121
+ "title": { "type": "string", "maxLength": 500 },
122
+ "why": { "type": ["string", "null"], "maxLength": 1000 },
123
+ "evidence": {
124
+ "type": ["object", "null"],
125
+ "properties": {
126
+ "file": { "type": ["string", "null"] },
127
+ "lines": { "type": ["string", "null"] },
128
+ "snippet": { "type": ["string", "null"] },
129
+ "kind": { "type": "string" }
130
+ }
131
+ },
132
+ "fixHint": { "type": ["string", "null"] }
133
+ }
134
+ },
135
+ "maxItems": 10
136
+ },
137
+ "fixMissions": {
138
+ "type": "array",
139
+ "items": {
140
+ "type": "object",
141
+ "properties": {
142
+ "target": { "type": "string" },
143
+ "action": { "type": "string" },
144
+ "priority": { "type": "string", "enum": ["critical", "recommended"] }
145
+ }
146
+ },
147
+ "maxItems": 10
148
+ }
149
+ }
150
+ },
151
+ "coverage": {
152
+ "type": "object",
153
+ "properties": {
154
+ "routeCoverage": { "type": "number", "minimum": 0, "maximum": 100 },
155
+ "envCoverage": { "type": "number", "minimum": 0, "maximum": 100 },
156
+ "authCoverage": { "type": ["number", "null"] },
157
+ "runtimeCoverage": { "type": ["number", "null"] }
158
+ }
159
+ }
160
+ }
161
+ },
162
+ "checks": {
163
+ "type": "object",
164
+ "required": ["audit", "reality", "shield"],
165
+ "properties": {
166
+ "audit": {
167
+ "type": "object",
168
+ "required": ["ran", "findingsCount"],
169
+ "properties": {
170
+ "ran": { "type": "boolean" },
171
+ "findingsCount": { "type": "integer", "minimum": 0 },
172
+ "truthpackHash": { "type": ["string", "null"] }
173
+ }
174
+ },
175
+ "reality": {
176
+ "type": "object",
177
+ "required": ["ran", "actionsCount", "requestsCount"],
178
+ "properties": {
179
+ "ran": { "type": "boolean" },
180
+ "actionsCount": { "type": "integer", "minimum": 0 },
181
+ "requestsCount": { "type": "integer", "minimum": 0 }
182
+ }
183
+ },
184
+ "shield": {
185
+ "type": "object",
186
+ "required": ["ran", "mode", "enforcing", "locked"],
187
+ "properties": {
188
+ "ran": { "type": "boolean" },
189
+ "mode": { "type": "string" },
190
+ "enforcing": { "type": "boolean" },
191
+ "locked": { "type": "boolean" }
192
+ }
193
+ }
194
+ }
195
+ },
196
+ "meta": {
197
+ "type": "object",
198
+ "required": ["generatedAt", "durationMs", "tool", "toolVersion"],
199
+ "properties": {
200
+ "generatedAt": {
201
+ "type": "string",
202
+ "format": "date-time",
203
+ "description": "ISO 8601 timestamp"
204
+ },
205
+ "durationMs": {
206
+ "type": "integer",
207
+ "minimum": 0,
208
+ "description": "Evaluation duration in milliseconds"
209
+ },
210
+ "tool": {
211
+ "type": "string",
212
+ "const": "vibecheck"
213
+ },
214
+ "toolVersion": {
215
+ "type": "string",
216
+ "description": "Vibecheck version"
217
+ },
218
+ "platform": {
219
+ "type": "string",
220
+ "description": "Platform identifier (os-arch)"
221
+ },
222
+ "nodeVersion": {
223
+ "type": "string",
224
+ "description": "Node.js version"
225
+ }
226
+ }
227
+ },
228
+ "signature": {
229
+ "type": "object",
230
+ "required": ["algorithm", "digest", "verifiable"],
231
+ "properties": {
232
+ "algorithm": {
233
+ "type": "string",
234
+ "enum": ["sha256"],
235
+ "description": "Signature algorithm"
236
+ },
237
+ "digest": {
238
+ "type": "string",
239
+ "pattern": "^[a-f0-9]{32}$",
240
+ "description": "Signature digest"
241
+ },
242
+ "verifiable": {
243
+ "type": "boolean",
244
+ "description": "Whether signature can be verified"
245
+ }
246
+ },
247
+ "additionalProperties": false
248
+ }
249
+ },
250
+ "additionalProperties": false
251
+ }