@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.
- package/bin/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- 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 };
|