@vibecheckai/cli 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -63
- package/bin/_deprecations.js +447 -19
- package/bin/_router.js +1 -1
- package/bin/registry.js +347 -280
- package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/index.js +62 -3
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/entitlements-v2.js +2 -2
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +421 -32
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output.js +1 -1
- package/bin/runners/lib/unified-cli-output.js +710 -383
- package/bin/runners/lib/upsell.js +3 -3
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/runAllowlist.js +33 -4
- package/bin/runners/runApprove.js +240 -1122
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +325 -29
- package/bin/runners/runCheckpoint.js +442 -494
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runDoctor.js +269 -19
- package/bin/runners/runFix.js +411 -32
- package/bin/runners/runForge.js +411 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1741 -837
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.js +41 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +21 -9
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +395 -16
- package/bin/vibecheck.js +34 -6
- package/mcp-server/README.md +117 -158
- package/mcp-server/handlers/tool-handler.ts +3 -3
- package/mcp-server/index.js +16 -0
- package/mcp-server/intent-firewall-interceptor.js +529 -0
- package/mcp-server/manifest.json +473 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry/tool-registry.js +315 -523
- package/mcp-server/registry/tools.json +442 -428
- package/mcp-server/tier-auth.js +68 -11
- package/mcp-server/tools-v3.js +70 -16
- package/package.json +1 -1
- package/bin/runners/runProof.zip +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship Gate - Enforcement integration for ship/packs/seal/CI
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* AGENT FIREWALL™ - SHIP GATE
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* This module provides the integration layer between the Agent Firewall
|
|
9
|
+
* enforcement system and shipping commands (ship, packs, seal, CI).
|
|
10
|
+
*
|
|
11
|
+
* SHIP CANNOT PROCEED unless:
|
|
12
|
+
* 1. A valid PASS verdict exists
|
|
13
|
+
* 2. The verdict is recent (not stale)
|
|
14
|
+
* 3. The verdict hash is verified
|
|
15
|
+
* 4. Reality proofs are satisfied (if required)
|
|
16
|
+
*
|
|
17
|
+
* @module integration/ship-gate
|
|
18
|
+
* @version 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
"use strict";
|
|
22
|
+
|
|
23
|
+
const path = require("path");
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const crypto = require("crypto");
|
|
26
|
+
|
|
27
|
+
const { createGateway, MODE, VERDICT } = require("../enforcement/gateway");
|
|
28
|
+
const { IntentStore } = require("../intent/store");
|
|
29
|
+
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
31
|
+
// CONSTANTS
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
33
|
+
|
|
34
|
+
const SHIP_GATE_STATUS = {
|
|
35
|
+
CLEAR: "CLEAR", // Can ship
|
|
36
|
+
BLOCKED: "BLOCKED", // Cannot ship - violations
|
|
37
|
+
NO_VERDICT: "NO_VERDICT", // Cannot ship - no enforcement run
|
|
38
|
+
STALE_VERDICT: "STALE_VERDICT", // Cannot ship - verdict too old
|
|
39
|
+
TAMPERED: "TAMPERED", // Cannot ship - verdict integrity failed
|
|
40
|
+
NO_INTENT: "NO_INTENT", // Cannot ship - no intent declared
|
|
41
|
+
PROOFS_PENDING: "PROOFS_PENDING", // Cannot ship - reality proofs needed
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DEFAULT_VERDICT_MAX_AGE_MS = 30 * 60 * 1000; // 30 minutes
|
|
45
|
+
const STRICT_VERDICT_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes for CI
|
|
46
|
+
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
48
|
+
// SHIP GATE
|
|
49
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
50
|
+
|
|
51
|
+
class ShipGate {
|
|
52
|
+
constructor(projectRoot, options = {}) {
|
|
53
|
+
this.projectRoot = projectRoot;
|
|
54
|
+
this.options = options;
|
|
55
|
+
this.gateway = createGateway(projectRoot, options);
|
|
56
|
+
this.intentStore = new IntentStore(projectRoot);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if ship is allowed
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} options
|
|
63
|
+
* @param {boolean} options.strict - Require recent verdict and proofs
|
|
64
|
+
* @param {boolean} options.requireIntent - Require declared intent
|
|
65
|
+
* @param {boolean} options.requireProofs - Require reality proofs
|
|
66
|
+
* @param {number} options.maxVerdictAge - Max age of verdict in ms
|
|
67
|
+
* @returns {Object} Ship gate result
|
|
68
|
+
*/
|
|
69
|
+
async checkShipStatus(options = {}) {
|
|
70
|
+
const strict = options.strict || process.env.CI || false;
|
|
71
|
+
const requireIntent = options.requireIntent !== false;
|
|
72
|
+
const requireProofs = options.requireProofs || strict;
|
|
73
|
+
const maxAge = options.maxVerdictAge || (strict ? STRICT_VERDICT_MAX_AGE_MS : DEFAULT_VERDICT_MAX_AGE_MS);
|
|
74
|
+
|
|
75
|
+
const result = {
|
|
76
|
+
status: SHIP_GATE_STATUS.CLEAR,
|
|
77
|
+
canShip: false,
|
|
78
|
+
message: "",
|
|
79
|
+
details: {},
|
|
80
|
+
verdict: null,
|
|
81
|
+
intent: null,
|
|
82
|
+
proofs: [],
|
|
83
|
+
recommendations: [],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Step 1: Check for declared intent
|
|
87
|
+
if (requireIntent) {
|
|
88
|
+
const hasIntent = this.intentStore.hasIntent();
|
|
89
|
+
const intent = this.intentStore.getCurrent({ allowMissing: true });
|
|
90
|
+
|
|
91
|
+
if (!hasIntent || intent?.summary?.includes("NO INTENT")) {
|
|
92
|
+
result.status = SHIP_GATE_STATUS.NO_INTENT;
|
|
93
|
+
result.message = "No intent declared - cannot ship without declared intent";
|
|
94
|
+
result.recommendations.push("Run: vibecheck intent set -s 'your intent'");
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
result.intent = intent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Step 2: Check for valid verdict
|
|
102
|
+
const latestVerdict = this._getLatestVerdict();
|
|
103
|
+
|
|
104
|
+
if (!latestVerdict) {
|
|
105
|
+
result.status = SHIP_GATE_STATUS.NO_VERDICT;
|
|
106
|
+
result.message = "No enforcement verdict found - run vibecheck shield first";
|
|
107
|
+
result.recommendations.push("Run: vibecheck shield");
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
result.verdict = latestVerdict;
|
|
112
|
+
|
|
113
|
+
// Step 3: Verify verdict integrity
|
|
114
|
+
const integrityCheck = this._verifyVerdictIntegrity(latestVerdict);
|
|
115
|
+
|
|
116
|
+
if (!integrityCheck.valid) {
|
|
117
|
+
result.status = SHIP_GATE_STATUS.TAMPERED;
|
|
118
|
+
result.message = `Verdict integrity check failed: ${integrityCheck.reason}`;
|
|
119
|
+
result.details.integrityCheck = integrityCheck;
|
|
120
|
+
result.recommendations.push("Re-run: vibecheck shield");
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Step 4: Check verdict age
|
|
125
|
+
const verdictAge = Date.now() - new Date(latestVerdict.timestamp).getTime();
|
|
126
|
+
|
|
127
|
+
if (verdictAge > maxAge) {
|
|
128
|
+
result.status = SHIP_GATE_STATUS.STALE_VERDICT;
|
|
129
|
+
result.message = `Verdict is ${Math.round(verdictAge / 60000)} minutes old (max: ${Math.round(maxAge / 60000)})`;
|
|
130
|
+
result.details.verdictAge = verdictAge;
|
|
131
|
+
result.details.maxAge = maxAge;
|
|
132
|
+
result.recommendations.push("Re-run: vibecheck shield");
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Step 5: Check verdict decision
|
|
137
|
+
if (latestVerdict.decision === VERDICT.BLOCK) {
|
|
138
|
+
result.status = SHIP_GATE_STATUS.BLOCKED;
|
|
139
|
+
result.message = latestVerdict.summary || "Enforcement violations found";
|
|
140
|
+
result.details.violations = latestVerdict.violations;
|
|
141
|
+
result.details.fixGuidance = latestVerdict.fix_guidance;
|
|
142
|
+
|
|
143
|
+
// Add fix recommendations
|
|
144
|
+
for (const fix of latestVerdict.fix_guidance || []) {
|
|
145
|
+
if (fix.command) {
|
|
146
|
+
result.recommendations.push(fix.command);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 6: Check reality proofs (if required)
|
|
154
|
+
if (requireProofs && latestVerdict.proofs) {
|
|
155
|
+
const pendingProofs = latestVerdict.proofs.filter(p => p.status === "pending");
|
|
156
|
+
const failedProofs = latestVerdict.proofs.filter(p => p.status === "failed");
|
|
157
|
+
|
|
158
|
+
if (failedProofs.length > 0) {
|
|
159
|
+
result.status = SHIP_GATE_STATUS.BLOCKED;
|
|
160
|
+
result.message = `${failedProofs.length} reality proof(s) failed`;
|
|
161
|
+
result.details.failedProofs = failedProofs;
|
|
162
|
+
result.recommendations.push("Run: vibecheck prove");
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (pendingProofs.length > 0) {
|
|
167
|
+
result.status = SHIP_GATE_STATUS.PROOFS_PENDING;
|
|
168
|
+
result.message = `${pendingProofs.length} reality proof(s) pending`;
|
|
169
|
+
result.details.pendingProofs = pendingProofs;
|
|
170
|
+
result.recommendations.push("Run: vibecheck prove --all");
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
result.proofs = latestVerdict.proofs;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// All checks passed
|
|
178
|
+
result.status = SHIP_GATE_STATUS.CLEAR;
|
|
179
|
+
result.canShip = true;
|
|
180
|
+
result.message = "All enforcement checks passed - clear to ship";
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get ship manifest for embedding in releases
|
|
187
|
+
*
|
|
188
|
+
* Creates a signed manifest that proves the code passed enforcement
|
|
189
|
+
* at ship time. Can be verified later.
|
|
190
|
+
*/
|
|
191
|
+
async getShipManifest() {
|
|
192
|
+
const status = await this.checkShipStatus({ strict: true });
|
|
193
|
+
|
|
194
|
+
if (!status.canShip) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: status.message,
|
|
198
|
+
status: status.status,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const manifest = {
|
|
203
|
+
schema_version: "3.0.0",
|
|
204
|
+
shipped_at: new Date().toISOString(),
|
|
205
|
+
project_root: this.projectRoot,
|
|
206
|
+
intent: {
|
|
207
|
+
hash: status.intent?.hash,
|
|
208
|
+
summary: status.intent?.summary,
|
|
209
|
+
},
|
|
210
|
+
verdict: {
|
|
211
|
+
id: status.verdict.id,
|
|
212
|
+
decision: status.verdict.decision,
|
|
213
|
+
hash: status.verdict.verdict_hash,
|
|
214
|
+
timestamp: status.verdict.timestamp,
|
|
215
|
+
violations_count: status.verdict.violations?.length || 0,
|
|
216
|
+
proofs_count: status.verdict.proofs?.length || 0,
|
|
217
|
+
},
|
|
218
|
+
proofs_verified: status.proofs.filter(p => p.status === "verified").length,
|
|
219
|
+
chain: status.verdict.chain,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Sign the manifest
|
|
223
|
+
manifest.signature = this._signManifest(manifest);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
manifest,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Verify a ship manifest
|
|
233
|
+
*/
|
|
234
|
+
verifyShipManifest(manifest) {
|
|
235
|
+
if (!manifest || !manifest.signature) {
|
|
236
|
+
return { valid: false, reason: "MISSING_SIGNATURE" };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Verify signature
|
|
240
|
+
const secret = process.env.VIBECHECK_SIGN_KEY || "vibecheck-local-signing-key";
|
|
241
|
+
const content = JSON.stringify({
|
|
242
|
+
shipped_at: manifest.shipped_at,
|
|
243
|
+
intent: manifest.intent,
|
|
244
|
+
verdict: manifest.verdict,
|
|
245
|
+
proofs_verified: manifest.proofs_verified,
|
|
246
|
+
chain: manifest.chain,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const expected = crypto.createHmac("sha256", secret)
|
|
250
|
+
.update(content)
|
|
251
|
+
.digest("hex");
|
|
252
|
+
|
|
253
|
+
if (expected !== manifest.signature.value) {
|
|
254
|
+
return { valid: false, reason: "SIGNATURE_MISMATCH" };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { valid: true, reason: "VERIFIED" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format ship gate status for CLI output
|
|
262
|
+
*/
|
|
263
|
+
formatStatus(status, options = {}) {
|
|
264
|
+
const lines = [];
|
|
265
|
+
const ansi = options.ansi || {};
|
|
266
|
+
const r = ansi.reset || "";
|
|
267
|
+
const b = ansi.bold || "";
|
|
268
|
+
const g = ansi.green || "";
|
|
269
|
+
const y = ansi.yellow || "";
|
|
270
|
+
const red = ansi.red || "";
|
|
271
|
+
const d = ansi.dim || "";
|
|
272
|
+
|
|
273
|
+
// Header
|
|
274
|
+
lines.push("═══════════════════════════════════════════════════════════════════════════════");
|
|
275
|
+
lines.push(`${b}AGENT FIREWALL - SHIP GATE${r}`);
|
|
276
|
+
lines.push("═══════════════════════════════════════════════════════════════════════════════");
|
|
277
|
+
lines.push("");
|
|
278
|
+
|
|
279
|
+
// Status
|
|
280
|
+
const statusColor = status.canShip ? g : red;
|
|
281
|
+
const statusIcon = status.canShip ? "✓" : "✗";
|
|
282
|
+
|
|
283
|
+
lines.push(`${b}Status:${r} ${statusColor}${statusIcon} ${status.status}${r}`);
|
|
284
|
+
lines.push(`${b}Message:${r} ${status.message}`);
|
|
285
|
+
lines.push("");
|
|
286
|
+
|
|
287
|
+
// Details
|
|
288
|
+
if (status.verdict) {
|
|
289
|
+
lines.push(`${d}Verdict ID:${r} ${status.verdict.id}`);
|
|
290
|
+
lines.push(`${d}Decision:${r} ${status.verdict.decision}`);
|
|
291
|
+
lines.push(`${d}Verdict Hash:${r} ${status.verdict.verdict_hash?.substring(0, 16)}...`);
|
|
292
|
+
lines.push("");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (status.intent) {
|
|
296
|
+
lines.push(`${d}Intent:${r} "${status.intent.summary}"`);
|
|
297
|
+
lines.push(`${d}Intent Hash:${r} ${status.intent.hash?.substring(0, 16)}...`);
|
|
298
|
+
lines.push("");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Violations
|
|
302
|
+
if (status.details?.violations?.length > 0) {
|
|
303
|
+
lines.push(`${b}Violations (${status.details.violations.length}):${r}`);
|
|
304
|
+
for (const v of status.details.violations.slice(0, 5)) {
|
|
305
|
+
lines.push(` ${red}•${r} [${v.code}] ${v.message}`);
|
|
306
|
+
if (v.fix_hint) {
|
|
307
|
+
lines.push(` ${g}→${r} ${v.fix_hint}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (status.details.violations.length > 5) {
|
|
311
|
+
lines.push(` ${d}... and ${status.details.violations.length - 5} more${r}`);
|
|
312
|
+
}
|
|
313
|
+
lines.push("");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Recommendations
|
|
317
|
+
if (status.recommendations?.length > 0) {
|
|
318
|
+
lines.push(`${b}To proceed:${r}`);
|
|
319
|
+
for (const rec of status.recommendations) {
|
|
320
|
+
lines.push(` ${y}→${r} ${rec}`);
|
|
321
|
+
}
|
|
322
|
+
lines.push("");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
lines.push("═══════════════════════════════════════════════════════════════════════════════");
|
|
326
|
+
|
|
327
|
+
return lines.join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
331
|
+
// PRIVATE METHODS
|
|
332
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
333
|
+
|
|
334
|
+
_getLatestVerdict() {
|
|
335
|
+
const latestPath = path.join(this.projectRoot, ".vibecheck", "verdicts", "latest.json");
|
|
336
|
+
|
|
337
|
+
if (fs.existsSync(latestPath)) {
|
|
338
|
+
try {
|
|
339
|
+
return JSON.parse(fs.readFileSync(latestPath, "utf-8"));
|
|
340
|
+
} catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
_verifyVerdictIntegrity(verdict) {
|
|
349
|
+
if (!verdict || !verdict.verdict_hash) {
|
|
350
|
+
return { valid: false, reason: "MISSING_HASH" };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const content = JSON.stringify({
|
|
354
|
+
decision: verdict.decision,
|
|
355
|
+
mode: verdict.mode,
|
|
356
|
+
violations: verdict.violations.map(v => ({ code: v.code, resource: v.resource })),
|
|
357
|
+
proofs: verdict.proofs.map(p => ({ id: p.id, status: p.status })),
|
|
358
|
+
intent_hash: verdict.intent_hash,
|
|
359
|
+
timestamp: verdict.timestamp,
|
|
360
|
+
chain: verdict.chain,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const computed = crypto.createHash("sha256").update(content).digest("hex");
|
|
364
|
+
|
|
365
|
+
if (computed !== verdict.verdict_hash) {
|
|
366
|
+
return { valid: false, reason: "HASH_MISMATCH", computed, stored: verdict.verdict_hash };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return { valid: true, reason: "VERIFIED" };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
_signManifest(manifest) {
|
|
373
|
+
const secret = process.env.VIBECHECK_SIGN_KEY || "vibecheck-local-signing-key";
|
|
374
|
+
const content = JSON.stringify({
|
|
375
|
+
shipped_at: manifest.shipped_at,
|
|
376
|
+
intent: manifest.intent,
|
|
377
|
+
verdict: manifest.verdict,
|
|
378
|
+
proofs_verified: manifest.proofs_verified,
|
|
379
|
+
chain: manifest.chain,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
algorithm: "sha256-hmac",
|
|
384
|
+
value: crypto.createHmac("sha256", secret).update(content).digest("hex"),
|
|
385
|
+
signed_at: new Date().toISOString(),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
391
|
+
// FACTORY FUNCTIONS
|
|
392
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Create a ship gate instance
|
|
396
|
+
*/
|
|
397
|
+
function createShipGate(projectRoot, options = {}) {
|
|
398
|
+
return new ShipGate(projectRoot, options);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Quick check if ship is allowed
|
|
403
|
+
*/
|
|
404
|
+
async function canShip(projectRoot, options = {}) {
|
|
405
|
+
const gate = createShipGate(projectRoot, options);
|
|
406
|
+
const status = await gate.checkShipStatus(options);
|
|
407
|
+
return status.canShip;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get ship status for CI
|
|
412
|
+
*/
|
|
413
|
+
async function getCIStatus(projectRoot) {
|
|
414
|
+
const gate = createShipGate(projectRoot, { mode: MODE.CI });
|
|
415
|
+
const status = await gate.checkShipStatus({ strict: true });
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
canShip: status.canShip,
|
|
419
|
+
exitCode: status.canShip ? 0 : 2,
|
|
420
|
+
status: status.status,
|
|
421
|
+
message: status.message,
|
|
422
|
+
verdictId: status.verdict?.id,
|
|
423
|
+
violationsCount: status.details?.violations?.length || 0,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
428
|
+
// EXPORTS
|
|
429
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
430
|
+
|
|
431
|
+
module.exports = {
|
|
432
|
+
ShipGate,
|
|
433
|
+
createShipGate,
|
|
434
|
+
canShip,
|
|
435
|
+
getCIStatus,
|
|
436
|
+
SHIP_GATE_STATUS,
|
|
437
|
+
};
|