@vibecheckai/cli 3.2.6 → 3.3.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 (84) hide show
  1. package/bin/registry.js +192 -5
  2. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  3. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  4. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  5. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  6. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  7. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  8. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  11. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  12. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  14. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  15. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  16. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  17. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  18. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  19. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  20. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  21. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  22. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  23. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  24. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  25. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  26. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  27. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  28. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  29. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  30. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  31. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  32. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  35. package/bin/runners/lib/analyzers.js +81 -18
  36. package/bin/runners/lib/authority-badge.js +425 -0
  37. package/bin/runners/lib/cli-output.js +7 -1
  38. package/bin/runners/lib/error-handler.js +16 -9
  39. package/bin/runners/lib/exit-codes.js +275 -0
  40. package/bin/runners/lib/global-flags.js +37 -0
  41. package/bin/runners/lib/help-formatter.js +413 -0
  42. package/bin/runners/lib/logger.js +38 -0
  43. package/bin/runners/lib/unified-cli-output.js +604 -0
  44. package/bin/runners/lib/upsell.js +148 -0
  45. package/bin/runners/runApprove.js +1200 -0
  46. package/bin/runners/runAuth.js +324 -95
  47. package/bin/runners/runCheckpoint.js +39 -21
  48. package/bin/runners/runClassify.js +859 -0
  49. package/bin/runners/runContext.js +136 -24
  50. package/bin/runners/runDoctor.js +108 -68
  51. package/bin/runners/runFix.js +6 -5
  52. package/bin/runners/runGuard.js +212 -118
  53. package/bin/runners/runInit.js +3 -2
  54. package/bin/runners/runMcp.js +130 -52
  55. package/bin/runners/runPolish.js +43 -20
  56. package/bin/runners/runProve.js +1 -2
  57. package/bin/runners/runReport.js +3 -2
  58. package/bin/runners/runScan.js +63 -44
  59. package/bin/runners/runShip.js +3 -4
  60. package/bin/runners/runValidate.js +19 -2
  61. package/bin/runners/runWatch.js +104 -53
  62. package/bin/vibecheck.js +106 -19
  63. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  64. package/mcp-server/agent-firewall-interceptor.js +367 -31
  65. package/mcp-server/authority-tools.js +569 -0
  66. package/mcp-server/conductor/conflict-resolver.js +588 -0
  67. package/mcp-server/conductor/execution-planner.js +544 -0
  68. package/mcp-server/conductor/index.js +377 -0
  69. package/mcp-server/conductor/lock-manager.js +615 -0
  70. package/mcp-server/conductor/request-queue.js +550 -0
  71. package/mcp-server/conductor/session-manager.js +500 -0
  72. package/mcp-server/conductor/tools.js +510 -0
  73. package/mcp-server/index.js +1149 -243
  74. package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
  75. package/mcp-server/lib/logger.cjs +30 -0
  76. package/mcp-server/logger.js +173 -0
  77. package/mcp-server/package.json +2 -2
  78. package/mcp-server/premium-tools.js +2 -2
  79. package/mcp-server/tier-auth.js +245 -35
  80. package/mcp-server/truth-firewall-tools.js +145 -15
  81. package/mcp-server/vibecheck-tools.js +2 -2
  82. package/package.json +2 -3
  83. package/mcp-server/index.old.js +0 -4137
  84. package/mcp-server/package-lock.json +0 -165
@@ -0,0 +1,490 @@
1
+ /**
2
+ * Time Machine State Reconstructor
3
+ *
4
+ * Rebuilds file and reality state at any timestamp.
5
+ * Shows what the AI thought vs what was actually true.
6
+ *
7
+ * Codename: Time Machine
8
+ */
9
+
10
+ "use strict";
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+ const crypto = require("crypto");
15
+
16
+ /**
17
+ * @typedef {Object} FileState
18
+ * @property {string} path - File path
19
+ * @property {boolean} existed - Did file exist at timestamp
20
+ * @property {string} [content] - File content if available
21
+ * @property {string} [hash] - Content hash
22
+ * @property {Object} [metadata] - Additional metadata
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} ReconstructedState
27
+ * @property {Date} timestamp - Point in time
28
+ * @property {FileState[]} files - File states
29
+ * @property {Object} routes - Route registry
30
+ * @property {Object} envVars - Environment variables
31
+ * @property {Object} services - Service definitions
32
+ * @property {string} snapshotId - Snapshot ID used
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} BeliefVsRealityComparison
37
+ * @property {Object} agentBelief - What agent assumed
38
+ * @property {Object} actualReality - What was true
39
+ * @property {Object[]} discrepancies - Differences found
40
+ * @property {number} accuracyScore - How accurate was the agent
41
+ */
42
+
43
+ /**
44
+ * State Reconstructor class
45
+ */
46
+ class StateReconstructor {
47
+ constructor(options = {}) {
48
+ this.projectRoot = options.projectRoot || process.cwd();
49
+ this.snapshotsDir = path.join(this.projectRoot, ".vibecheck", "snapshots");
50
+ this.packetsDir = path.join(this.projectRoot, ".vibecheck", "packets");
51
+ this.guardrailDir = path.join(this.projectRoot, ".guardrail");
52
+ this.snapshotCache = new Map();
53
+ }
54
+
55
+ /**
56
+ * Reconstruct state at a specific timestamp
57
+ * @param {Date} timestamp - Target timestamp
58
+ * @param {Object} options - Options
59
+ * @returns {ReconstructedState} Reconstructed state
60
+ */
61
+ async reconstructState(timestamp, options = {}) {
62
+ const {
63
+ files = null, // Specific files to reconstruct
64
+ includeContent = false,
65
+ useSnapshots = true,
66
+ } = options;
67
+
68
+ // Find the nearest snapshot before the timestamp
69
+ const nearestSnapshot = await this.findNearestSnapshot(timestamp);
70
+
71
+ // Load the snapshot or build from packets
72
+ let baseState;
73
+
74
+ if (nearestSnapshot && useSnapshots) {
75
+ baseState = await this.loadSnapshot(nearestSnapshot);
76
+ } else {
77
+ baseState = await this.buildStateFromPackets(timestamp);
78
+ }
79
+
80
+ // Apply any changes between snapshot and target time
81
+ if (nearestSnapshot) {
82
+ baseState = await this.applyChanges(baseState, nearestSnapshot.timestamp, timestamp);
83
+ }
84
+
85
+ // Filter to specific files if requested
86
+ if (files && files.length > 0) {
87
+ baseState.files = baseState.files.filter(f =>
88
+ files.some(reqFile => f.path.includes(reqFile) || reqFile.includes(f.path))
89
+ );
90
+ }
91
+
92
+ // Optionally load content
93
+ if (includeContent) {
94
+ await this.loadFileContents(baseState, timestamp);
95
+ }
96
+
97
+ return {
98
+ timestamp,
99
+ snapshotId: nearestSnapshot?.id || `reconstructed_${timestamp.getTime()}`,
100
+ ...baseState,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Find the nearest snapshot before a timestamp
106
+ * @param {Date} timestamp - Target timestamp
107
+ * @returns {Object|null} Nearest snapshot or null
108
+ */
109
+ async findNearestSnapshot(timestamp) {
110
+ if (!fs.existsSync(this.snapshotsDir)) {
111
+ return null;
112
+ }
113
+
114
+ const targetTime = timestamp.getTime();
115
+ let nearestSnapshot = null;
116
+ let nearestDiff = Infinity;
117
+
118
+ const files = fs.readdirSync(this.snapshotsDir)
119
+ .filter(f => f.endsWith(".json"))
120
+ .sort();
121
+
122
+ for (const file of files) {
123
+ try {
124
+ const snapshotPath = path.join(this.snapshotsDir, file);
125
+ const content = fs.readFileSync(snapshotPath, "utf-8");
126
+ const snapshot = JSON.parse(content);
127
+
128
+ const snapshotTime = new Date(snapshot.timestamp).getTime();
129
+
130
+ // Only consider snapshots before or at the target time
131
+ if (snapshotTime <= targetTime) {
132
+ const diff = targetTime - snapshotTime;
133
+ if (diff < nearestDiff) {
134
+ nearestDiff = diff;
135
+ nearestSnapshot = {
136
+ id: file.replace(".json", ""),
137
+ path: snapshotPath,
138
+ timestamp: new Date(snapshot.timestamp),
139
+ data: snapshot,
140
+ };
141
+ }
142
+ }
143
+ } catch {
144
+ // Skip invalid snapshots
145
+ }
146
+ }
147
+
148
+ return nearestSnapshot;
149
+ }
150
+
151
+ /**
152
+ * Load a snapshot
153
+ * @param {Object} snapshotInfo - Snapshot info
154
+ * @returns {Object} Loaded state
155
+ */
156
+ async loadSnapshot(snapshotInfo) {
157
+ if (this.snapshotCache.has(snapshotInfo.id)) {
158
+ return JSON.parse(JSON.stringify(this.snapshotCache.get(snapshotInfo.id)));
159
+ }
160
+
161
+ const state = {
162
+ files: snapshotInfo.data.files || [],
163
+ routes: snapshotInfo.data.routes || {},
164
+ envVars: snapshotInfo.data.envVars || {},
165
+ services: snapshotInfo.data.services || {},
166
+ };
167
+
168
+ this.snapshotCache.set(snapshotInfo.id, state);
169
+
170
+ return JSON.parse(JSON.stringify(state));
171
+ }
172
+
173
+ /**
174
+ * Build state from change packets
175
+ * @param {Date} timestamp - Target timestamp
176
+ * @returns {Object} Built state
177
+ */
178
+ async buildStateFromPackets(timestamp) {
179
+ const state = {
180
+ files: [],
181
+ routes: {},
182
+ envVars: {},
183
+ services: {},
184
+ };
185
+
186
+ // Try to load from guardrail context
187
+ const contextPath = path.join(this.guardrailDir, "context-snapshot.json");
188
+
189
+ if (fs.existsSync(contextPath)) {
190
+ try {
191
+ const context = JSON.parse(fs.readFileSync(contextPath, "utf-8"));
192
+
193
+ // Extract file information
194
+ if (context.files) {
195
+ state.files = Object.entries(context.files).map(([filePath, info]) => ({
196
+ path: filePath,
197
+ existed: true,
198
+ hash: info.hash,
199
+ metadata: info,
200
+ }));
201
+ }
202
+
203
+ // Extract routes
204
+ if (context.routes) {
205
+ state.routes = context.routes;
206
+ }
207
+
208
+ // Extract env vars
209
+ if (context.envVars) {
210
+ state.envVars = context.envVars;
211
+ }
212
+ } catch {
213
+ // Ignore errors
214
+ }
215
+ }
216
+
217
+ // Apply changes from packets up to timestamp
218
+ if (fs.existsSync(this.packetsDir)) {
219
+ const packets = fs.readdirSync(this.packetsDir)
220
+ .filter(f => f.endsWith(".json"))
221
+ .sort();
222
+
223
+ for (const packetFile of packets) {
224
+ try {
225
+ const packet = JSON.parse(
226
+ fs.readFileSync(path.join(this.packetsDir, packetFile), "utf-8")
227
+ );
228
+
229
+ const packetTime = new Date(packet.timestamp || packet.createdAt);
230
+ if (packetTime > timestamp) break;
231
+
232
+ // Apply packet changes to state
233
+ this.applyPacketToState(state, packet);
234
+ } catch {
235
+ // Skip invalid packets
236
+ }
237
+ }
238
+ }
239
+
240
+ return state;
241
+ }
242
+
243
+ /**
244
+ * Apply a packet's changes to state
245
+ * @param {Object} state - State to modify
246
+ * @param {Object} packet - Packet to apply
247
+ */
248
+ applyPacketToState(state, packet) {
249
+ for (const op of packet.operations || []) {
250
+ const filePath = op.path || op.file;
251
+ if (!filePath) continue;
252
+
253
+ const existingIndex = state.files.findIndex(f => f.path === filePath);
254
+
255
+ switch (op.type) {
256
+ case "create":
257
+ if (existingIndex === -1) {
258
+ state.files.push({
259
+ path: filePath,
260
+ existed: true,
261
+ hash: op.content ? this.hashContent(op.content) : null,
262
+ });
263
+ } else {
264
+ state.files[existingIndex].existed = true;
265
+ if (op.content) {
266
+ state.files[existingIndex].hash = this.hashContent(op.content);
267
+ }
268
+ }
269
+ break;
270
+
271
+ case "delete":
272
+ if (existingIndex !== -1) {
273
+ state.files[existingIndex].existed = false;
274
+ }
275
+ break;
276
+
277
+ case "modify":
278
+ if (existingIndex !== -1 && op.content) {
279
+ state.files[existingIndex].hash = this.hashContent(op.content);
280
+ }
281
+ break;
282
+ }
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Apply changes between two timestamps
288
+ * @param {Object} state - Base state
289
+ * @param {Date} fromTime - Start time
290
+ * @param {Date} toTime - End time
291
+ * @returns {Object} Updated state
292
+ */
293
+ async applyChanges(state, fromTime, toTime) {
294
+ if (!fs.existsSync(this.packetsDir)) {
295
+ return state;
296
+ }
297
+
298
+ const packets = fs.readdirSync(this.packetsDir)
299
+ .filter(f => f.endsWith(".json"))
300
+ .sort();
301
+
302
+ for (const packetFile of packets) {
303
+ try {
304
+ const packet = JSON.parse(
305
+ fs.readFileSync(path.join(this.packetsDir, packetFile), "utf-8")
306
+ );
307
+
308
+ const packetTime = new Date(packet.timestamp || packet.createdAt);
309
+
310
+ if (packetTime <= fromTime) continue;
311
+ if (packetTime > toTime) break;
312
+
313
+ this.applyPacketToState(state, packet);
314
+ } catch {
315
+ // Skip invalid packets
316
+ }
317
+ }
318
+
319
+ return state;
320
+ }
321
+
322
+ /**
323
+ * Load file contents for a state
324
+ * @param {Object} state - State to load content for
325
+ * @param {Date} timestamp - Target timestamp
326
+ */
327
+ async loadFileContents(state, timestamp) {
328
+ // For now, we can only load current content
329
+ // Historical content would require git integration
330
+ for (const file of state.files) {
331
+ if (file.existed) {
332
+ const fullPath = path.resolve(this.projectRoot, file.path);
333
+
334
+ if (fs.existsSync(fullPath)) {
335
+ try {
336
+ file.content = fs.readFileSync(fullPath, "utf-8");
337
+ file.currentHash = this.hashContent(file.content);
338
+ } catch {
339
+ // Skip unreadable files
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Compare agent belief vs actual reality
348
+ * @param {Object} agentBelief - What agent assumed
349
+ * @param {ReconstructedState} actualState - Reconstructed reality
350
+ * @returns {BeliefVsRealityComparison} Comparison result
351
+ */
352
+ compareBeliefVsReality(agentBelief, actualState) {
353
+ const discrepancies = [];
354
+ let correctAssumptions = 0;
355
+ let totalAssumptions = 0;
356
+
357
+ for (const assumption of agentBelief.assumptions || []) {
358
+ totalAssumptions++;
359
+
360
+ const check = this.checkAssumption(assumption, actualState);
361
+
362
+ if (!check.valid) {
363
+ discrepancies.push({
364
+ assumption: assumption.type,
365
+ target: assumption.target,
366
+ expected: assumption.expectedValue,
367
+ actual: check.actualValue,
368
+ message: check.message,
369
+ });
370
+ } else {
371
+ correctAssumptions++;
372
+ }
373
+ }
374
+
375
+ const accuracyScore = totalAssumptions > 0
376
+ ? Math.round((correctAssumptions / totalAssumptions) * 100)
377
+ : 100;
378
+
379
+ return {
380
+ agentBelief,
381
+ actualReality: {
382
+ fileCount: actualState.files.filter(f => f.existed).length,
383
+ routeCount: Object.keys(actualState.routes).length,
384
+ envVarCount: Object.keys(actualState.envVars).length,
385
+ },
386
+ discrepancies,
387
+ accuracyScore,
388
+ totalAssumptions,
389
+ correctAssumptions,
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Check if an assumption matches reality
395
+ * @param {Object} assumption - Assumption to check
396
+ * @param {ReconstructedState} state - Reality state
397
+ * @returns {Object} Check result
398
+ */
399
+ checkAssumption(assumption, state) {
400
+ switch (assumption.type) {
401
+ case "file_exists": {
402
+ const file = state.files.find(f => f.path === assumption.target);
403
+ const exists = file?.existed ?? false;
404
+ const expected = assumption.expectedValue !== false;
405
+ return {
406
+ valid: exists === expected,
407
+ actualValue: exists,
408
+ message: exists === expected ? "Match" : `File ${expected ? "does not exist" : "exists unexpectedly"}`,
409
+ };
410
+ }
411
+
412
+ case "route_exists": {
413
+ const routeExists = !!state.routes[assumption.target];
414
+ const expected = assumption.expectedValue !== false;
415
+ return {
416
+ valid: routeExists === expected,
417
+ actualValue: routeExists,
418
+ message: routeExists === expected ? "Match" : `Route ${expected ? "does not exist" : "exists unexpectedly"}`,
419
+ };
420
+ }
421
+
422
+ case "env_exists": {
423
+ const envExists = !!state.envVars[assumption.target];
424
+ const expected = assumption.expectedValue !== false;
425
+ return {
426
+ valid: envExists === expected,
427
+ actualValue: envExists,
428
+ message: envExists === expected ? "Match" : `Env var ${expected ? "does not exist" : "exists unexpectedly"}`,
429
+ };
430
+ }
431
+
432
+ default:
433
+ return {
434
+ valid: true,
435
+ actualValue: "unknown",
436
+ message: "Cannot verify assumption type",
437
+ };
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Create a snapshot of current state
443
+ * @param {string} snapshotId - Optional snapshot ID
444
+ * @returns {string} Snapshot ID
445
+ */
446
+ async createSnapshot(snapshotId = null) {
447
+ const id = snapshotId || `snapshot_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
448
+
449
+ // Build current state
450
+ const state = await this.buildStateFromPackets(new Date());
451
+
452
+ const snapshot = {
453
+ id,
454
+ timestamp: new Date().toISOString(),
455
+ ...state,
456
+ };
457
+
458
+ // Save snapshot
459
+ if (!fs.existsSync(this.snapshotsDir)) {
460
+ fs.mkdirSync(this.snapshotsDir, { recursive: true });
461
+ }
462
+
463
+ fs.writeFileSync(
464
+ path.join(this.snapshotsDir, `${id}.json`),
465
+ JSON.stringify(snapshot, null, 2)
466
+ );
467
+
468
+ return id;
469
+ }
470
+
471
+ /**
472
+ * Hash content for comparison
473
+ * @param {string} content - Content to hash
474
+ * @returns {string} Hash
475
+ */
476
+ hashContent(content) {
477
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Create a state reconstructor instance
483
+ * @param {Object} options - Options
484
+ * @returns {StateReconstructor} State reconstructor
485
+ */
486
+ function createStateReconstructor(options = {}) {
487
+ return new StateReconstructor(options);
488
+ }
489
+
490
+ module.exports = { StateReconstructor, createStateReconstructor };