@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,540 @@
1
+ /**
2
+ * Unified Artifact Envelope
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * Single envelope schema for ALL VibeCheck artifacts
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Used by: audit, ship, reality, shield, fix, packs
9
+ *
10
+ * @module artifact-envelope
11
+ * @version 1.0.0
12
+ */
13
+
14
+ "use strict";
15
+
16
+ const crypto = require("crypto");
17
+ const path = require("path");
18
+ const fs = require("fs");
19
+ const { execSync } = require("child_process");
20
+
21
+ // ═══════════════════════════════════════════════════════════════════════════════
22
+ // CONSTANTS
23
+ // ═══════════════════════════════════════════════════════════════════════════════
24
+
25
+ const ENVELOPE_VERSION = "1.0.0";
26
+ const SCHEMA_URL = "https://vibecheck.dev/schemas/artifact-envelope.v1.json";
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════════
29
+ // ARTIFACT TYPES
30
+ // ═══════════════════════════════════════════════════════════════════════════════
31
+
32
+ const ARTIFACT_TYPES = {
33
+ AUDIT: "audit",
34
+ SHIP: "ship",
35
+ REALITY: "reality",
36
+ SHIELD: "shield",
37
+ FIX: "fix",
38
+ PACK: "pack",
39
+ CHECKPOINT: "checkpoint",
40
+ INTENT: "intent",
41
+ VERDICT: "verdict",
42
+ };
43
+
44
+ // ═══════════════════════════════════════════════════════════════════════════════
45
+ // REPO INFO
46
+ // ═══════════════════════════════════════════════════════════════════════════════
47
+
48
+ /**
49
+ * Get repository information for artifact envelope
50
+ *
51
+ * @param {string} projectRoot - Project root directory
52
+ * @returns {Object} Repository info
53
+ */
54
+ function getRepoInfo(projectRoot) {
55
+ const info = {
56
+ root: projectRoot,
57
+ name: path.basename(projectRoot),
58
+ commit: null,
59
+ branch: null,
60
+ treeHash: null,
61
+ isDirty: true,
62
+ fingerprint: null,
63
+ };
64
+
65
+ try {
66
+ // Check if it's a git repo
67
+ execSync("git rev-parse --git-dir", {
68
+ cwd: projectRoot,
69
+ stdio: ["pipe", "pipe", "pipe"]
70
+ });
71
+
72
+ // Get commit hash
73
+ info.commit = execSync("git rev-parse HEAD", {
74
+ cwd: projectRoot,
75
+ encoding: "utf-8",
76
+ stdio: ["pipe", "pipe", "pipe"],
77
+ }).trim();
78
+
79
+ // Get branch name
80
+ try {
81
+ info.branch = execSync("git rev-parse --abbrev-ref HEAD", {
82
+ cwd: projectRoot,
83
+ encoding: "utf-8",
84
+ stdio: ["pipe", "pipe", "pipe"],
85
+ }).trim();
86
+ } catch {
87
+ info.branch = "detached";
88
+ }
89
+
90
+ // Get tree hash
91
+ info.treeHash = execSync("git rev-parse HEAD^{tree}", {
92
+ cwd: projectRoot,
93
+ encoding: "utf-8",
94
+ stdio: ["pipe", "pipe", "pipe"],
95
+ }).trim();
96
+
97
+ // Check if dirty
98
+ const status = execSync("git status --porcelain", {
99
+ cwd: projectRoot,
100
+ encoding: "utf-8",
101
+ stdio: ["pipe", "pipe", "pipe"],
102
+ }).trim();
103
+ info.isDirty = status.length > 0;
104
+
105
+ // Compute fingerprint
106
+ info.fingerprint = computeRepoFingerprint(projectRoot, info);
107
+
108
+ } catch {
109
+ // Not a git repo or git not available
110
+ info.fingerprint = computeNonGitFingerprint(projectRoot);
111
+ }
112
+
113
+ return info;
114
+ }
115
+
116
+ /**
117
+ * Compute deterministic repo fingerprint
118
+ *
119
+ * Same repo state → Same fingerprint. Always.
120
+ */
121
+ function computeRepoFingerprint(projectRoot, repoInfo) {
122
+ // Config files that affect fingerprint
123
+ const configFiles = [
124
+ ".vibecheckrc",
125
+ ".vibecheckrc.json",
126
+ "package.json",
127
+ "pnpm-lock.yaml",
128
+ "package-lock.json",
129
+ "yarn.lock",
130
+ ];
131
+
132
+ const configHashes = configFiles.map(f => {
133
+ const filePath = path.join(projectRoot, f);
134
+ if (fs.existsSync(filePath)) {
135
+ return crypto.createHash("sha256")
136
+ .update(fs.readFileSync(filePath))
137
+ .digest("hex")
138
+ .slice(0, 8);
139
+ }
140
+ return "missing";
141
+ }).join("|");
142
+
143
+ const input = `${repoInfo.commit}|${repoInfo.treeHash}|${configHashes}`;
144
+
145
+ return `sha256:${crypto.createHash("sha256").update(input).digest("hex")}`;
146
+ }
147
+
148
+ /**
149
+ * Compute fingerprint for non-git projects
150
+ */
151
+ function computeNonGitFingerprint(projectRoot) {
152
+ const input = `${projectRoot}|${Date.now()}`;
153
+ return `nongit:${crypto.createHash("sha256").update(input).digest("hex").slice(0, 32)}`;
154
+ }
155
+
156
+ // ═══════════════════════════════════════════════════════════════════════════════
157
+ // ARTIFACT CREATION
158
+ // ═══════════════════════════════════════════════════════════════════════════════
159
+
160
+ /**
161
+ * Create a new artifact envelope
162
+ *
163
+ * @param {string} type - Artifact type (audit, ship, reality, etc.)
164
+ * @param {Object} payload - Command-specific payload
165
+ * @param {string} projectRoot - Project root directory
166
+ * @param {Object} options - Additional options
167
+ * @returns {Object} Complete artifact envelope
168
+ */
169
+ function createArtifactEnvelope(type, payload, projectRoot, options = {}) {
170
+ const timestamp = new Date().toISOString();
171
+ const randomId = crypto.randomBytes(4).toString("hex");
172
+
173
+ const envelope = {
174
+ $schema: SCHEMA_URL,
175
+ version: ENVELOPE_VERSION,
176
+
177
+ metadata: {
178
+ id: `${type}-${Date.now()}-${randomId}`,
179
+ type,
180
+ command: options.command || `vibecheck ${type}`,
181
+ generatedAt: timestamp,
182
+ durationMs: options.durationMs || 0,
183
+ tool: {
184
+ name: "vibecheck",
185
+ version: getToolVersion(),
186
+ },
187
+ },
188
+
189
+ repo: getRepoInfo(projectRoot),
190
+
191
+ payload,
192
+
193
+ evidence: {
194
+ files: options.files || [],
195
+ proofs: options.proofs || [],
196
+ },
197
+
198
+ signature: null, // Will be computed
199
+ };
200
+
201
+ // Compute signature
202
+ envelope.signature = computeSignature(envelope);
203
+
204
+ return envelope;
205
+ }
206
+
207
+ /**
208
+ * Get tool version from package.json
209
+ */
210
+ function getToolVersion() {
211
+ try {
212
+ const pkgPath = path.join(__dirname, "..", "..", "..", "package.json");
213
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
214
+ return pkg.version || "0.0.0";
215
+ } catch {
216
+ return "0.0.0";
217
+ }
218
+ }
219
+
220
+ // ═══════════════════════════════════════════════════════════════════════════════
221
+ // SIGNATURE
222
+ // ═══════════════════════════════════════════════════════════════════════════════
223
+
224
+ /**
225
+ * Compute artifact signature
226
+ *
227
+ * @param {Object} envelope - Artifact envelope (without signature)
228
+ * @returns {Object} Signature object
229
+ */
230
+ function computeSignature(envelope) {
231
+ const fieldsToSign = ["metadata", "repo", "payload", "evidence"];
232
+
233
+ // Create canonical JSON (no whitespace, deterministic key order)
234
+ const dataToSign = fieldsToSign.reduce((acc, field) => {
235
+ acc[field] = envelope[field];
236
+ return acc;
237
+ }, {});
238
+
239
+ const canonical = JSON.stringify(dataToSign, Object.keys(dataToSign).sort(), 0);
240
+
241
+ return {
242
+ algorithm: "sha256",
243
+ digest: crypto.createHash("sha256").update(canonical).digest("hex"),
244
+ signedFields: fieldsToSign,
245
+ verifiable: true,
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Verify artifact signature
251
+ *
252
+ * @param {Object} envelope - Artifact envelope with signature
253
+ * @returns {Object} Verification result
254
+ */
255
+ function verifySignature(envelope) {
256
+ if (!envelope.signature) {
257
+ return { valid: false, reason: "No signature present" };
258
+ }
259
+
260
+ // Recompute expected signature
261
+ const envelopeWithoutSig = { ...envelope, signature: null };
262
+ const expected = computeSignature(envelopeWithoutSig);
263
+
264
+ if (envelope.signature.digest !== expected.digest) {
265
+ return {
266
+ valid: false,
267
+ reason: "Digest mismatch",
268
+ expected: expected.digest,
269
+ actual: envelope.signature.digest,
270
+ };
271
+ }
272
+
273
+ return { valid: true };
274
+ }
275
+
276
+ // ═══════════════════════════════════════════════════════════════════════════════
277
+ // ARTIFACT NAMING
278
+ // ═══════════════════════════════════════════════════════════════════════════════
279
+
280
+ /**
281
+ * Generate deterministic artifact filename
282
+ *
283
+ * Pattern: {type}-{YYYYMMDD}-{HHMMSS}-{hash8}.json
284
+ *
285
+ * @param {string} type - Artifact type
286
+ * @param {string} extension - File extension (default: json)
287
+ * @returns {string} Filename
288
+ */
289
+ function generateArtifactFilename(type, extension = "json") {
290
+ const now = new Date();
291
+ const datePart = now.toISOString().slice(0, 10).replace(/-/g, "");
292
+ const timePart = now.toISOString().slice(11, 19).replace(/:/g, "");
293
+ const hash = crypto.randomBytes(4).toString("hex");
294
+
295
+ return `${type}-${datePart}-${timePart}-${hash}.${extension}`;
296
+ }
297
+
298
+ /**
299
+ * Generate artifact directory path
300
+ *
301
+ * @param {string} projectRoot - Project root
302
+ * @param {string} type - Artifact type
303
+ * @returns {string} Directory path
304
+ */
305
+ function getArtifactDir(projectRoot, type) {
306
+ const typeToDir = {
307
+ [ARTIFACT_TYPES.AUDIT]: "audit",
308
+ [ARTIFACT_TYPES.SHIP]: "ship",
309
+ [ARTIFACT_TYPES.REALITY]: "reality",
310
+ [ARTIFACT_TYPES.SHIELD]: "shield",
311
+ [ARTIFACT_TYPES.FIX]: "fix",
312
+ [ARTIFACT_TYPES.PACK]: "packs",
313
+ [ARTIFACT_TYPES.CHECKPOINT]: "checkpoints",
314
+ [ARTIFACT_TYPES.INTENT]: "shield/intents",
315
+ [ARTIFACT_TYPES.VERDICT]: "shield/verdicts",
316
+ };
317
+
318
+ const subdir = typeToDir[type] || type;
319
+ return path.join(projectRoot, ".vibecheck", subdir);
320
+ }
321
+
322
+ // ═══════════════════════════════════════════════════════════════════════════════
323
+ // ARTIFACT I/O
324
+ // ═══════════════════════════════════════════════════════════════════════════════
325
+
326
+ /**
327
+ * Save artifact to disk
328
+ *
329
+ * @param {Object} envelope - Artifact envelope
330
+ * @param {string} projectRoot - Project root
331
+ * @param {Object} options - Save options
332
+ * @returns {Object} Save result with path
333
+ */
334
+ function saveArtifact(envelope, projectRoot, options = {}) {
335
+ const dir = options.dir || getArtifactDir(projectRoot, envelope.metadata.type);
336
+ const filename = options.filename || generateArtifactFilename(envelope.metadata.type);
337
+ const filePath = path.join(dir, filename);
338
+
339
+ // Ensure directory exists
340
+ fs.mkdirSync(dir, { recursive: true });
341
+
342
+ // Write artifact
343
+ fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
344
+
345
+ // Also write "latest" symlink/copy
346
+ const latestPath = path.join(dir, `last_${envelope.metadata.type}.json`);
347
+ fs.writeFileSync(latestPath, JSON.stringify(envelope, null, 2));
348
+
349
+ return {
350
+ success: true,
351
+ path: filePath,
352
+ latestPath,
353
+ id: envelope.metadata.id,
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Load artifact from disk
359
+ *
360
+ * @param {string} filePath - Full path to artifact
361
+ * @param {Object} options - Load options
362
+ * @returns {Object} Artifact envelope or null
363
+ */
364
+ function loadArtifact(filePath, options = {}) {
365
+ try {
366
+ const content = fs.readFileSync(filePath, "utf-8");
367
+ const envelope = JSON.parse(content);
368
+
369
+ // Optionally verify signature
370
+ if (options.verify) {
371
+ const verification = verifySignature(envelope);
372
+ if (!verification.valid) {
373
+ return {
374
+ envelope: null,
375
+ error: `Invalid signature: ${verification.reason}`,
376
+ };
377
+ }
378
+ }
379
+
380
+ return { envelope, error: null };
381
+ } catch (e) {
382
+ return { envelope: null, error: e.message };
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Load latest artifact of a type
388
+ *
389
+ * @param {string} projectRoot - Project root
390
+ * @param {string} type - Artifact type
391
+ * @param {Object} options - Load options
392
+ * @returns {Object} Artifact envelope or null
393
+ */
394
+ function loadLatestArtifact(projectRoot, type, options = {}) {
395
+ const dir = getArtifactDir(projectRoot, type);
396
+ const latestPath = path.join(dir, `last_${type}.json`);
397
+
398
+ if (!fs.existsSync(latestPath)) {
399
+ return { envelope: null, error: "No artifact found" };
400
+ }
401
+
402
+ return loadArtifact(latestPath, options);
403
+ }
404
+
405
+ // ═══════════════════════════════════════════════════════════════════════════════
406
+ // EVIDENCE HELPERS
407
+ // ═══════════════════════════════════════════════════════════════════════════════
408
+
409
+ /**
410
+ * Create file evidence entry
411
+ *
412
+ * @param {string} filePath - Relative file path
413
+ * @param {string} lines - Line range (e.g., "42-45")
414
+ * @param {string} projectRoot - Project root for hashing
415
+ * @returns {Object} File evidence entry
416
+ */
417
+ function createFileEvidence(filePath, lines, projectRoot) {
418
+ const fullPath = path.join(projectRoot, filePath);
419
+ let snippetHash = null;
420
+
421
+ if (fs.existsSync(fullPath) && lines) {
422
+ try {
423
+ const content = fs.readFileSync(fullPath, "utf-8");
424
+ const [start, end] = lines.split("-").map(n => parseInt(n, 10));
425
+ const snippet = content.split("\n").slice(start - 1, end).join("\n");
426
+ snippetHash = `sha256:${crypto.createHash("sha256").update(snippet).digest("hex").slice(0, 16)}`;
427
+ } catch {
428
+ // Ignore errors
429
+ }
430
+ }
431
+
432
+ return {
433
+ path: filePath,
434
+ lines,
435
+ snippetHash,
436
+ };
437
+ }
438
+
439
+ /**
440
+ * Create proof entry
441
+ *
442
+ * @param {string} type - Proof type (file_citation, runtime, assertion)
443
+ * @param {boolean} verified - Whether proof verified
444
+ * @param {Object} details - Additional details
445
+ * @returns {Object} Proof entry
446
+ */
447
+ function createProof(type, verified, details = {}) {
448
+ return {
449
+ id: `prf-${type.slice(0, 5)}-${crypto.randomBytes(4).toString("hex")}`,
450
+ type,
451
+ status: verified ? "verified" : "failed",
452
+ verifiedAt: new Date().toISOString(),
453
+ ...details,
454
+ };
455
+ }
456
+
457
+ // ═══════════════════════════════════════════════════════════════════════════════
458
+ // CROSS-REFERENCES
459
+ // ═══════════════════════════════════════════════════════════════════════════════
460
+
461
+ /**
462
+ * Create cross-reference to another artifact
463
+ *
464
+ * @param {string} type - Referenced artifact type
465
+ * @param {string} projectRoot - Project root
466
+ * @returns {Object} Cross-reference object
467
+ */
468
+ function createCrossRef(type, projectRoot) {
469
+ const dir = getArtifactDir(projectRoot, type);
470
+ const latestPath = path.join(dir, `last_${type}.json`);
471
+
472
+ // Use relative path
473
+ const relativePath = path.relative(
474
+ path.join(projectRoot, ".vibecheck"),
475
+ latestPath
476
+ );
477
+
478
+ return {
479
+ $ref: `./${relativePath}`,
480
+ type,
481
+ exists: fs.existsSync(latestPath),
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Build receipts object with cross-references
487
+ *
488
+ * @param {string} projectRoot - Project root
489
+ * @param {string[]} types - Types to include
490
+ * @returns {Object} Receipts object
491
+ */
492
+ function buildReceiptsObject(projectRoot, types = ["audit", "ship", "reality", "shield"]) {
493
+ const receipts = {};
494
+
495
+ for (const type of types) {
496
+ const ref = createCrossRef(type, projectRoot);
497
+ if (ref.exists) {
498
+ receipts[type] = ref;
499
+ }
500
+ }
501
+
502
+ return receipts;
503
+ }
504
+
505
+ // ═══════════════════════════════════════════════════════════════════════════════
506
+ // EXPORTS
507
+ // ═══════════════════════════════════════════════════════════════════════════════
508
+
509
+ module.exports = {
510
+ // Constants
511
+ ENVELOPE_VERSION,
512
+ SCHEMA_URL,
513
+ ARTIFACT_TYPES,
514
+
515
+ // Core functions
516
+ createArtifactEnvelope,
517
+ computeSignature,
518
+ verifySignature,
519
+
520
+ // Repo info
521
+ getRepoInfo,
522
+ computeRepoFingerprint,
523
+
524
+ // Naming
525
+ generateArtifactFilename,
526
+ getArtifactDir,
527
+
528
+ // I/O
529
+ saveArtifact,
530
+ loadArtifact,
531
+ loadLatestArtifact,
532
+
533
+ // Evidence
534
+ createFileEvidence,
535
+ createProof,
536
+
537
+ // Cross-references
538
+ createCrossRef,
539
+ buildReceiptsObject,
540
+ };