@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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time Machine Module
|
|
3
|
+
*
|
|
4
|
+
* Forensic Replay for AI Actions
|
|
5
|
+
* Enables deep analysis and replay of what AI agents did.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Time Machine
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const { ReplayEngine, createReplayEngine } = require("./replay-engine.js");
|
|
13
|
+
const { TimelineBuilder, createTimelineBuilder, EVENT_TYPES } = require("./timeline-builder.js");
|
|
14
|
+
const { StateReconstructor, createStateReconstructor } = require("./state-reconstructor.js");
|
|
15
|
+
const { IncidentCorrelator, createIncidentCorrelator, RELATIONSHIP_TYPES } = require("./incident-correlator.js");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Time Machine singleton for unified access
|
|
19
|
+
*/
|
|
20
|
+
class TimeMachine {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.replayEngine = null;
|
|
23
|
+
this.timelineBuilder = null;
|
|
24
|
+
this.stateReconstructor = null;
|
|
25
|
+
this.incidentCorrelator = null;
|
|
26
|
+
this.projectRoot = null;
|
|
27
|
+
this.initialized = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the Time Machine for a project
|
|
32
|
+
* @param {string} projectRoot - Project root directory
|
|
33
|
+
* @returns {TimeMachine} This instance
|
|
34
|
+
*/
|
|
35
|
+
init(projectRoot) {
|
|
36
|
+
if (this.initialized && this.projectRoot === projectRoot) {
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.projectRoot = projectRoot;
|
|
41
|
+
this.replayEngine = createReplayEngine({ projectRoot });
|
|
42
|
+
this.timelineBuilder = createTimelineBuilder({ projectRoot });
|
|
43
|
+
this.stateReconstructor = createStateReconstructor({ projectRoot });
|
|
44
|
+
this.incidentCorrelator = createIncidentCorrelator({ projectRoot });
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Start a replay session
|
|
52
|
+
* @param {Object} options - Replay options
|
|
53
|
+
* @returns {Object} Replay session
|
|
54
|
+
*/
|
|
55
|
+
async startReplay(options = {}) {
|
|
56
|
+
this.ensureInitialized();
|
|
57
|
+
return this.replayEngine.startReplay(options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get current snapshot in a replay session
|
|
62
|
+
* @param {string} sessionId - Session ID
|
|
63
|
+
* @returns {Object|null} Current snapshot
|
|
64
|
+
*/
|
|
65
|
+
getCurrentSnapshot(sessionId) {
|
|
66
|
+
this.ensureInitialized();
|
|
67
|
+
return this.replayEngine.getCurrentSnapshot(sessionId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Move to next snapshot
|
|
72
|
+
* @param {string} sessionId - Session ID
|
|
73
|
+
* @returns {Object|null} Next snapshot
|
|
74
|
+
*/
|
|
75
|
+
nextSnapshot(sessionId) {
|
|
76
|
+
this.ensureInitialized();
|
|
77
|
+
return this.replayEngine.nextSnapshot(sessionId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Move to previous snapshot
|
|
82
|
+
* @param {string} sessionId - Session ID
|
|
83
|
+
* @returns {Object|null} Previous snapshot
|
|
84
|
+
*/
|
|
85
|
+
previousSnapshot(sessionId) {
|
|
86
|
+
this.ensureInitialized();
|
|
87
|
+
return this.replayEngine.previousSnapshot(sessionId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get replay session status
|
|
92
|
+
* @param {string} sessionId - Session ID
|
|
93
|
+
* @returns {Object|null} Session status
|
|
94
|
+
*/
|
|
95
|
+
getSessionStatus(sessionId) {
|
|
96
|
+
this.ensureInitialized();
|
|
97
|
+
return this.replayEngine.getSessionStatus(sessionId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* End a replay session
|
|
102
|
+
* @param {string} sessionId - Session ID
|
|
103
|
+
*/
|
|
104
|
+
endReplay(sessionId) {
|
|
105
|
+
this.ensureInitialized();
|
|
106
|
+
this.replayEngine.endReplay(sessionId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build a forensic timeline
|
|
111
|
+
* @param {Object} options - Timeline options
|
|
112
|
+
* @returns {Object} Forensic timeline
|
|
113
|
+
*/
|
|
114
|
+
async buildTimeline(options = {}) {
|
|
115
|
+
this.ensureInitialized();
|
|
116
|
+
return this.timelineBuilder.buildTimeline(options);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate a timeline report
|
|
121
|
+
* @param {Object} timeline - Timeline to report on
|
|
122
|
+
* @returns {Object} Report
|
|
123
|
+
*/
|
|
124
|
+
generateReport(timeline) {
|
|
125
|
+
this.ensureInitialized();
|
|
126
|
+
return this.timelineBuilder.generateReport(timeline);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Compare agent belief vs reality for a snapshot
|
|
131
|
+
* @param {Object} snapshot - Snapshot to analyze
|
|
132
|
+
* @returns {Object} Comparison result
|
|
133
|
+
*/
|
|
134
|
+
compareBeliefVsReality(snapshot) {
|
|
135
|
+
this.ensureInitialized();
|
|
136
|
+
return this.replayEngine.compareBeliefVsReality(snapshot);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Quick replay for an incident
|
|
141
|
+
* @param {string} incidentId - Incident ID
|
|
142
|
+
* @param {Object} options - Additional options
|
|
143
|
+
* @returns {Object} Replay result with timeline
|
|
144
|
+
*/
|
|
145
|
+
async replayIncident(incidentId, options = {}) {
|
|
146
|
+
this.ensureInitialized();
|
|
147
|
+
|
|
148
|
+
// Build timeline for the incident
|
|
149
|
+
const timeline = await this.buildTimeline({
|
|
150
|
+
...options,
|
|
151
|
+
includeIncidents: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Find the incident
|
|
155
|
+
const incidentEvent = timeline.events.find(
|
|
156
|
+
e => e.id === incidentId || e.details?.incidentId === incidentId
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (!incidentEvent) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: "Incident not found",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Start replay from before the incident
|
|
167
|
+
const incidentTime = new Date(incidentEvent.timestamp);
|
|
168
|
+
const lookbackMs = options.lookbackMs || 24 * 60 * 60 * 1000; // 24 hours default
|
|
169
|
+
|
|
170
|
+
const session = await this.startReplay({
|
|
171
|
+
startTime: new Date(incidentTime.getTime() - lookbackMs),
|
|
172
|
+
endTime: incidentTime,
|
|
173
|
+
...options,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
session,
|
|
179
|
+
timeline,
|
|
180
|
+
incident: incidentEvent,
|
|
181
|
+
rootCause: timeline.rootCause,
|
|
182
|
+
causalChain: timeline.causalChain,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reconstruct state at a specific timestamp
|
|
188
|
+
* @param {Date} timestamp - Target timestamp
|
|
189
|
+
* @param {Object} options - Options
|
|
190
|
+
* @returns {Object} Reconstructed state
|
|
191
|
+
*/
|
|
192
|
+
async reconstructState(timestamp, options = {}) {
|
|
193
|
+
this.ensureInitialized();
|
|
194
|
+
return this.stateReconstructor.reconstructState(timestamp, options);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Record a new incident
|
|
199
|
+
* @param {Object} incidentData - Incident data
|
|
200
|
+
* @returns {Object} Created incident
|
|
201
|
+
*/
|
|
202
|
+
recordIncident(incidentData) {
|
|
203
|
+
this.ensureInitialized();
|
|
204
|
+
return this.incidentCorrelator.recordIncident(incidentData);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Correlate an incident with firewall events
|
|
209
|
+
* @param {string} incidentId - Incident ID
|
|
210
|
+
* @param {Object} options - Options
|
|
211
|
+
* @returns {Object} Incident report
|
|
212
|
+
*/
|
|
213
|
+
async correlateIncident(incidentId, options = {}) {
|
|
214
|
+
this.ensureInitialized();
|
|
215
|
+
return this.incidentCorrelator.correlateIncident(incidentId, options);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get all recorded incidents
|
|
220
|
+
* @returns {Object[]} All incidents
|
|
221
|
+
*/
|
|
222
|
+
getAllIncidents() {
|
|
223
|
+
this.ensureInitialized();
|
|
224
|
+
return this.incidentCorrelator.getAllIncidents();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create a snapshot of current state
|
|
229
|
+
* @param {string} snapshotId - Optional snapshot ID
|
|
230
|
+
* @returns {string} Snapshot ID
|
|
231
|
+
*/
|
|
232
|
+
async createSnapshot(snapshotId = null) {
|
|
233
|
+
this.ensureInitialized();
|
|
234
|
+
return this.stateReconstructor.createSnapshot(snapshotId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Ensure Time Machine is initialized
|
|
239
|
+
*/
|
|
240
|
+
ensureInitialized() {
|
|
241
|
+
if (!this.initialized) {
|
|
242
|
+
throw new Error("Time Machine not initialized. Call timeMachine.init(projectRoot) first.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Singleton instance
|
|
248
|
+
const timeMachine = new TimeMachine();
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
timeMachine,
|
|
252
|
+
TimeMachine,
|
|
253
|
+
// Replay Engine
|
|
254
|
+
ReplayEngine,
|
|
255
|
+
createReplayEngine,
|
|
256
|
+
// Timeline Builder
|
|
257
|
+
TimelineBuilder,
|
|
258
|
+
createTimelineBuilder,
|
|
259
|
+
EVENT_TYPES,
|
|
260
|
+
// State Reconstructor
|
|
261
|
+
StateReconstructor,
|
|
262
|
+
createStateReconstructor,
|
|
263
|
+
// Incident Correlator
|
|
264
|
+
IncidentCorrelator,
|
|
265
|
+
createIncidentCorrelator,
|
|
266
|
+
RELATIONSHIP_TYPES,
|
|
267
|
+
};
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time Machine Replay Engine
|
|
3
|
+
*
|
|
4
|
+
* Reconstructs codebase state at any point in time.
|
|
5
|
+
* Enables forensic analysis of AI agent actions.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Time Machine
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const { timeMachineLogger: log, getErrorMessage } = require("../logger.js");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ReplaySnapshot
|
|
18
|
+
* @property {string} snapshotId - Unique snapshot ID
|
|
19
|
+
* @property {Date} timestamp - Point in time
|
|
20
|
+
* @property {Object} realityState - What the repo actually was
|
|
21
|
+
* @property {Object} agentBelief - What the agent assumed
|
|
22
|
+
* @property {Object} proposal - The change proposal
|
|
23
|
+
* @property {Object} verdict - The verdict issued
|
|
24
|
+
* @property {Object} [overrideContext] - Override info if applicable
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} ReplaySession
|
|
29
|
+
* @property {string} sessionId - Replay session ID
|
|
30
|
+
* @property {Date} startTime - When replay started
|
|
31
|
+
* @property {Date} targetTime - Point being replayed
|
|
32
|
+
* @property {ReplaySnapshot[]} snapshots - Snapshots in this session
|
|
33
|
+
* @property {number} currentIndex - Current position in replay
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Replay Engine class
|
|
38
|
+
*/
|
|
39
|
+
class ReplayEngine {
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
42
|
+
this.packetsDir = path.join(this.projectRoot, ".vibecheck", "packets");
|
|
43
|
+
this.auditDir = path.join(this.projectRoot, ".vibecheck", "audit");
|
|
44
|
+
this.snapshotsDir = path.join(this.projectRoot, ".vibecheck", "snapshots");
|
|
45
|
+
this.sessions = new Map();
|
|
46
|
+
this.packetCache = new Map();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Start a replay session for a specific time range
|
|
51
|
+
* @param {Object} options - Replay options
|
|
52
|
+
* @returns {ReplaySession} Replay session
|
|
53
|
+
*/
|
|
54
|
+
async startReplay(options = {}) {
|
|
55
|
+
const {
|
|
56
|
+
startTime,
|
|
57
|
+
endTime = new Date(),
|
|
58
|
+
agentId = null,
|
|
59
|
+
file = null,
|
|
60
|
+
incidentId = null,
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
63
|
+
const sessionId = `replay_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
64
|
+
|
|
65
|
+
// Load relevant packets
|
|
66
|
+
const packets = await this.loadPackets({
|
|
67
|
+
startTime,
|
|
68
|
+
endTime,
|
|
69
|
+
agentId,
|
|
70
|
+
file,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Build snapshots from packets
|
|
74
|
+
const snapshots = await this.buildSnapshots(packets);
|
|
75
|
+
|
|
76
|
+
const session = {
|
|
77
|
+
sessionId,
|
|
78
|
+
startTime: new Date(),
|
|
79
|
+
targetTime: startTime,
|
|
80
|
+
options,
|
|
81
|
+
snapshots,
|
|
82
|
+
currentIndex: 0,
|
|
83
|
+
totalSnapshots: snapshots.length,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
this.sessions.set(sessionId, session);
|
|
87
|
+
|
|
88
|
+
return session;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Load change packets for a time range
|
|
93
|
+
* @param {Object} filters - Filter options
|
|
94
|
+
* @returns {Object[]} Change packets
|
|
95
|
+
*/
|
|
96
|
+
async loadPackets(filters = {}) {
|
|
97
|
+
const packets = [];
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(this.packetsDir)) {
|
|
100
|
+
return packets;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const files = fs.readdirSync(this.packetsDir)
|
|
104
|
+
.filter(f => f.endsWith(".json"))
|
|
105
|
+
.sort();
|
|
106
|
+
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(path.join(this.packetsDir, file), "utf-8");
|
|
110
|
+
const packet = JSON.parse(content);
|
|
111
|
+
|
|
112
|
+
// Apply time filter
|
|
113
|
+
const packetTime = new Date(packet.timestamp || packet.createdAt);
|
|
114
|
+
|
|
115
|
+
if (filters.startTime && packetTime < new Date(filters.startTime)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (filters.endTime && packetTime > new Date(filters.endTime)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Apply agent filter
|
|
124
|
+
if (filters.agentId && packet.agentId !== filters.agentId) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply file filter
|
|
129
|
+
if (filters.file) {
|
|
130
|
+
const affectedFiles = this.extractAffectedFiles(packet);
|
|
131
|
+
if (!affectedFiles.some(f => f.includes(filters.file))) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
packets.push(packet);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
log.warn(`Failed to load packet ${file}: ${getErrorMessage(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Sort by timestamp
|
|
143
|
+
packets.sort((a, b) => {
|
|
144
|
+
const timeA = new Date(a.timestamp || a.createdAt).getTime();
|
|
145
|
+
const timeB = new Date(b.timestamp || b.createdAt).getTime();
|
|
146
|
+
return timeA - timeB;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return packets;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extract affected files from a packet
|
|
154
|
+
* @param {Object} packet - Change packet
|
|
155
|
+
* @returns {string[]} Affected file paths
|
|
156
|
+
*/
|
|
157
|
+
extractAffectedFiles(packet) {
|
|
158
|
+
const files = [];
|
|
159
|
+
|
|
160
|
+
if (packet.file) files.push(packet.file);
|
|
161
|
+
if (packet.filePath) files.push(packet.filePath);
|
|
162
|
+
|
|
163
|
+
for (const op of packet.operations || []) {
|
|
164
|
+
if (op.path) files.push(op.path);
|
|
165
|
+
if (op.file) files.push(op.file);
|
|
166
|
+
if (op.filePath) files.push(op.filePath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return files;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build replay snapshots from packets
|
|
174
|
+
* @param {Object[]} packets - Change packets
|
|
175
|
+
* @returns {ReplaySnapshot[]} Replay snapshots
|
|
176
|
+
*/
|
|
177
|
+
async buildSnapshots(packets) {
|
|
178
|
+
const snapshots = [];
|
|
179
|
+
|
|
180
|
+
for (const packet of packets) {
|
|
181
|
+
const snapshot = {
|
|
182
|
+
snapshotId: packet.id || `snap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
183
|
+
timestamp: new Date(packet.timestamp || packet.createdAt),
|
|
184
|
+
realityState: await this.extractRealityState(packet),
|
|
185
|
+
agentBelief: this.extractAgentBelief(packet),
|
|
186
|
+
proposal: this.extractProposal(packet),
|
|
187
|
+
verdict: this.extractVerdict(packet),
|
|
188
|
+
overrideContext: this.extractOverrideContext(packet),
|
|
189
|
+
metadata: {
|
|
190
|
+
agentId: packet.agentId,
|
|
191
|
+
sessionId: packet.sessionId,
|
|
192
|
+
file: packet.file || this.extractAffectedFiles(packet)[0],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
snapshots.push(snapshot);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return snapshots;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extract reality state from a packet
|
|
204
|
+
* @param {Object} packet - Change packet
|
|
205
|
+
* @returns {Object} Reality state
|
|
206
|
+
*/
|
|
207
|
+
async extractRealityState(packet) {
|
|
208
|
+
// Try to load from proof artifact
|
|
209
|
+
if (packet.proof?.realitySnapshot) {
|
|
210
|
+
return packet.proof.realitySnapshot;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Build from packet data
|
|
214
|
+
return {
|
|
215
|
+
files: this.extractAffectedFiles(packet).map(f => ({
|
|
216
|
+
path: f,
|
|
217
|
+
exists: true, // Would need historical data to verify
|
|
218
|
+
})),
|
|
219
|
+
timestamp: packet.timestamp || packet.createdAt,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extract what the agent believed/assumed
|
|
225
|
+
* @param {Object} packet - Change packet
|
|
226
|
+
* @returns {Object} Agent belief state
|
|
227
|
+
*/
|
|
228
|
+
extractAgentBelief(packet) {
|
|
229
|
+
const assumptions = packet.assumptions || packet.proposal?.assumptions || [];
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
assumptions: assumptions.map(a => ({
|
|
233
|
+
type: a.type,
|
|
234
|
+
target: a.target || a.path,
|
|
235
|
+
expectedValue: a.expectedValue || a.value,
|
|
236
|
+
wasValid: a.valid !== undefined ? a.valid : null,
|
|
237
|
+
})),
|
|
238
|
+
confidence: packet.confidence || packet.proposal?.confidence,
|
|
239
|
+
intent: packet.intent || packet.proposal?.intent,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract the proposal from a packet
|
|
245
|
+
* @param {Object} packet - Change packet
|
|
246
|
+
* @returns {Object} Proposal
|
|
247
|
+
*/
|
|
248
|
+
extractProposal(packet) {
|
|
249
|
+
return {
|
|
250
|
+
proposalId: packet.proposalId || packet.id,
|
|
251
|
+
intent: packet.intent || packet.proposal?.intent,
|
|
252
|
+
operations: packet.operations || packet.proposal?.operations || [],
|
|
253
|
+
summary: packet.summary || packet.proposal?.summary,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Extract the verdict from a packet
|
|
259
|
+
* @param {Object} packet - Change packet
|
|
260
|
+
* @returns {Object} Verdict
|
|
261
|
+
*/
|
|
262
|
+
extractVerdict(packet) {
|
|
263
|
+
return {
|
|
264
|
+
decision: packet.verdict?.decision || packet.proof?.decision || packet.decision,
|
|
265
|
+
confidence: packet.verdict?.confidence || packet.proof?.confidence,
|
|
266
|
+
rulesTriggered: packet.verdict?.rulesTriggered || packet.proof?.rulesTriggered || [],
|
|
267
|
+
riskScore: packet.riskScore || packet.proof?.riskScore,
|
|
268
|
+
riskLevel: packet.riskLevel || packet.proof?.riskLevel,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extract override context if any
|
|
274
|
+
* @param {Object} packet - Change packet
|
|
275
|
+
* @returns {Object|null} Override context
|
|
276
|
+
*/
|
|
277
|
+
extractOverrideContext(packet) {
|
|
278
|
+
if (!packet.proof?.overrideUsed && !packet.override?.used) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
used: true,
|
|
284
|
+
by: packet.proof?.overrideBy || packet.override?.by,
|
|
285
|
+
reason: packet.proof?.overrideReason || packet.override?.reason,
|
|
286
|
+
timestamp: packet.proof?.overrideTimestamp || packet.override?.timestamp,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get the current snapshot in a replay session
|
|
292
|
+
* @param {string} sessionId - Session ID
|
|
293
|
+
* @returns {ReplaySnapshot|null} Current snapshot
|
|
294
|
+
*/
|
|
295
|
+
getCurrentSnapshot(sessionId) {
|
|
296
|
+
const session = this.sessions.get(sessionId);
|
|
297
|
+
if (!session) return null;
|
|
298
|
+
|
|
299
|
+
return session.snapshots[session.currentIndex] || null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Move to the next snapshot
|
|
304
|
+
* @param {string} sessionId - Session ID
|
|
305
|
+
* @returns {ReplaySnapshot|null} Next snapshot or null if at end
|
|
306
|
+
*/
|
|
307
|
+
nextSnapshot(sessionId) {
|
|
308
|
+
const session = this.sessions.get(sessionId);
|
|
309
|
+
if (!session) return null;
|
|
310
|
+
|
|
311
|
+
if (session.currentIndex < session.snapshots.length - 1) {
|
|
312
|
+
session.currentIndex++;
|
|
313
|
+
return session.snapshots[session.currentIndex];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Move to the previous snapshot
|
|
321
|
+
* @param {string} sessionId - Session ID
|
|
322
|
+
* @returns {ReplaySnapshot|null} Previous snapshot or null if at start
|
|
323
|
+
*/
|
|
324
|
+
previousSnapshot(sessionId) {
|
|
325
|
+
const session = this.sessions.get(sessionId);
|
|
326
|
+
if (!session) return null;
|
|
327
|
+
|
|
328
|
+
if (session.currentIndex > 0) {
|
|
329
|
+
session.currentIndex--;
|
|
330
|
+
return session.snapshots[session.currentIndex];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Jump to a specific snapshot index
|
|
338
|
+
* @param {string} sessionId - Session ID
|
|
339
|
+
* @param {number} index - Index to jump to
|
|
340
|
+
* @returns {ReplaySnapshot|null} Snapshot at index
|
|
341
|
+
*/
|
|
342
|
+
jumpToSnapshot(sessionId, index) {
|
|
343
|
+
const session = this.sessions.get(sessionId);
|
|
344
|
+
if (!session) return null;
|
|
345
|
+
|
|
346
|
+
if (index >= 0 && index < session.snapshots.length) {
|
|
347
|
+
session.currentIndex = index;
|
|
348
|
+
return session.snapshots[index];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get session status
|
|
356
|
+
* @param {string} sessionId - Session ID
|
|
357
|
+
* @returns {Object|null} Session status
|
|
358
|
+
*/
|
|
359
|
+
getSessionStatus(sessionId) {
|
|
360
|
+
const session = this.sessions.get(sessionId);
|
|
361
|
+
if (!session) return null;
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
sessionId: session.sessionId,
|
|
365
|
+
totalSnapshots: session.totalSnapshots,
|
|
366
|
+
currentIndex: session.currentIndex,
|
|
367
|
+
currentSnapshot: session.snapshots[session.currentIndex],
|
|
368
|
+
isAtStart: session.currentIndex === 0,
|
|
369
|
+
isAtEnd: session.currentIndex === session.snapshots.length - 1,
|
|
370
|
+
progress: session.totalSnapshots > 0
|
|
371
|
+
? Math.round((session.currentIndex / (session.totalSnapshots - 1)) * 100)
|
|
372
|
+
: 100,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* End a replay session
|
|
378
|
+
* @param {string} sessionId - Session ID
|
|
379
|
+
*/
|
|
380
|
+
endReplay(sessionId) {
|
|
381
|
+
this.sessions.delete(sessionId);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get all active replay sessions
|
|
386
|
+
* @returns {Object[]} Active sessions
|
|
387
|
+
*/
|
|
388
|
+
getActiveSessions() {
|
|
389
|
+
return Array.from(this.sessions.values()).map(s => ({
|
|
390
|
+
sessionId: s.sessionId,
|
|
391
|
+
startTime: s.startTime,
|
|
392
|
+
targetTime: s.targetTime,
|
|
393
|
+
totalSnapshots: s.totalSnapshots,
|
|
394
|
+
currentIndex: s.currentIndex,
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Compare what agent believed vs what was true
|
|
400
|
+
* @param {ReplaySnapshot} snapshot - Snapshot to analyze
|
|
401
|
+
* @returns {Object} Comparison result
|
|
402
|
+
*/
|
|
403
|
+
compareBeliefVsReality(snapshot) {
|
|
404
|
+
const discrepancies = [];
|
|
405
|
+
|
|
406
|
+
for (const assumption of snapshot.agentBelief.assumptions || []) {
|
|
407
|
+
if (assumption.wasValid === false) {
|
|
408
|
+
discrepancies.push({
|
|
409
|
+
type: assumption.type,
|
|
410
|
+
target: assumption.target,
|
|
411
|
+
expected: assumption.expectedValue,
|
|
412
|
+
actual: "Did not match reality",
|
|
413
|
+
severity: "error",
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
hadDiscrepancies: discrepancies.length > 0,
|
|
420
|
+
discrepancies,
|
|
421
|
+
confidence: snapshot.agentBelief.confidence,
|
|
422
|
+
verdictIssued: snapshot.verdict.decision,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a replay engine instance
|
|
429
|
+
* @param {Object} options - Options
|
|
430
|
+
* @returns {ReplayEngine} Replay engine
|
|
431
|
+
*/
|
|
432
|
+
function createReplayEngine(options = {}) {
|
|
433
|
+
return new ReplayEngine(options);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
module.exports = { ReplayEngine, createReplayEngine };
|