@neurcode-ai/cli 0.9.61 → 0.9.62

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 (71) hide show
  1. package/LICENSE +201 -0
  2. package/dist/commands/control-plane.d.ts +3 -0
  3. package/dist/commands/control-plane.d.ts.map +1 -0
  4. package/dist/commands/control-plane.js +163 -0
  5. package/dist/commands/control-plane.js.map +1 -0
  6. package/dist/commands/export.d.ts +7 -0
  7. package/dist/commands/export.d.ts.map +1 -0
  8. package/dist/commands/export.js +72 -0
  9. package/dist/commands/export.js.map +1 -0
  10. package/dist/commands/fix.d.ts +1 -0
  11. package/dist/commands/fix.d.ts.map +1 -1
  12. package/dist/commands/fix.js +3 -0
  13. package/dist/commands/fix.js.map +1 -1
  14. package/dist/commands/generate.d.ts +1 -0
  15. package/dist/commands/generate.d.ts.map +1 -1
  16. package/dist/commands/generate.js +63 -48
  17. package/dist/commands/generate.js.map +1 -1
  18. package/dist/commands/patch-apply.d.ts.map +1 -1
  19. package/dist/commands/patch-apply.js +1 -0
  20. package/dist/commands/patch-apply.js.map +1 -1
  21. package/dist/commands/replay.d.ts +3 -0
  22. package/dist/commands/replay.d.ts.map +1 -0
  23. package/dist/commands/replay.js +267 -0
  24. package/dist/commands/replay.js.map +1 -0
  25. package/dist/commands/verify.d.ts +7 -1
  26. package/dist/commands/verify.d.ts.map +1 -1
  27. package/dist/commands/verify.js +281 -149
  28. package/dist/commands/verify.js.map +1 -1
  29. package/dist/commands/workspace.d.ts +3 -0
  30. package/dist/commands/workspace.d.ts.map +1 -0
  31. package/dist/commands/workspace.js +407 -0
  32. package/dist/commands/workspace.js.map +1 -0
  33. package/dist/daemon/server.d.ts +0 -17
  34. package/dist/daemon/server.d.ts.map +1 -1
  35. package/dist/daemon/server.js +1038 -53
  36. package/dist/daemon/server.js.map +1 -1
  37. package/dist/index.js +296 -12
  38. package/dist/index.js.map +1 -1
  39. package/dist/utils/cli-json.d.ts +1 -0
  40. package/dist/utils/cli-json.d.ts.map +1 -1
  41. package/dist/utils/cli-json.js +1 -0
  42. package/dist/utils/cli-json.js.map +1 -1
  43. package/dist/utils/control-plane.d.ts +171 -0
  44. package/dist/utils/control-plane.d.ts.map +1 -0
  45. package/dist/utils/control-plane.js +684 -0
  46. package/dist/utils/control-plane.js.map +1 -0
  47. package/dist/utils/execution-bus.d.ts +205 -0
  48. package/dist/utils/execution-bus.d.ts.map +1 -0
  49. package/dist/utils/execution-bus.js +1346 -0
  50. package/dist/utils/execution-bus.js.map +1 -0
  51. package/dist/utils/gitignore.d.ts +2 -2
  52. package/dist/utils/gitignore.d.ts.map +1 -1
  53. package/dist/utils/gitignore.js +27 -14
  54. package/dist/utils/gitignore.js.map +1 -1
  55. package/dist/utils/replay-runtime.d.ts +295 -0
  56. package/dist/utils/replay-runtime.d.ts.map +1 -0
  57. package/dist/utils/replay-runtime.js +1080 -0
  58. package/dist/utils/replay-runtime.js.map +1 -0
  59. package/dist/utils/runtime-events.d.ts +44 -0
  60. package/dist/utils/runtime-events.d.ts.map +1 -0
  61. package/dist/utils/runtime-events.js +213 -0
  62. package/dist/utils/runtime-events.js.map +1 -0
  63. package/dist/utils/verification-evidence.d.ts +22 -0
  64. package/dist/utils/verification-evidence.d.ts.map +1 -0
  65. package/dist/utils/verification-evidence.js +233 -0
  66. package/dist/utils/verification-evidence.js.map +1 -0
  67. package/dist/utils/workspace-runtime.d.ts +267 -0
  68. package/dist/utils/workspace-runtime.d.ts.map +1 -0
  69. package/dist/utils/workspace-runtime.js +1415 -0
  70. package/dist/utils/workspace-runtime.js.map +1 -0
  71. package/package.json +7 -8
@@ -0,0 +1,1080 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.replayGovernanceState = replayGovernanceState;
4
+ exports.replayExecution = replayExecution;
5
+ exports.replayWorkspace = replayWorkspace;
6
+ exports.replayTimeline = replayTimeline;
7
+ exports.getWorkspaceSnapshotHistory = getWorkspaceSnapshotHistory;
8
+ exports.writeWorkspaceReplaySnapshot = writeWorkspaceReplaySnapshot;
9
+ const crypto_1 = require("crypto");
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const execution_bus_1 = require("./execution-bus");
13
+ const REPLAY_STATE_SCHEMA = 'neurcode.replay.state.v1';
14
+ const REPLAY_EXECUTION_SCHEMA = 'neurcode.replay.execution.v1';
15
+ const REPLAY_WORKSPACE_SCHEMA = 'neurcode.replay.workspace.v1';
16
+ const REPLAY_TIMELINE_SCHEMA = 'neurcode.replay.timeline.v1';
17
+ const REPLAY_CACHE_SCHEMA = 'neurcode.replay.cache.v1';
18
+ const WORKSPACE_SNAPSHOT_SCHEMA = 'neurcode.workspace.snapshot.v1';
19
+ const CONTROL_PLANE_SNAPSHOT_SCHEMA = 'neurcode.control-plane.snapshot.v1';
20
+ const VERIFY_EVIDENCE_SCHEMA = 'neurcode.verify.evidence.v1';
21
+ const RUNTIME_EVENT_SCHEMA = 'neurcode.runtime-event.v1';
22
+ const DEFAULT_TIMELINE_LIMIT = 200;
23
+ const DEFAULT_EVENT_LIMIT = 400;
24
+ const MAX_TIMELINE_LIMIT = 5000;
25
+ function nowIso() {
26
+ return new Date().toISOString();
27
+ }
28
+ function asObject(value) {
29
+ if (!value || typeof value !== 'object' || Array.isArray(value))
30
+ return null;
31
+ return value;
32
+ }
33
+ function asString(value) {
34
+ if (typeof value !== 'string')
35
+ return null;
36
+ const trimmed = value.trim();
37
+ return trimmed.length > 0 ? trimmed : null;
38
+ }
39
+ function asNumber(value) {
40
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
41
+ }
42
+ function asBoolean(value) {
43
+ if (typeof value === 'boolean')
44
+ return value;
45
+ return null;
46
+ }
47
+ function toMs(value) {
48
+ if (!value)
49
+ return null;
50
+ const parsed = Date.parse(value);
51
+ return Number.isFinite(parsed) ? parsed : null;
52
+ }
53
+ function clamp(value, min, max) {
54
+ return Math.max(min, Math.min(max, value));
55
+ }
56
+ function stableNormalize(value) {
57
+ if (value === null || typeof value !== 'object')
58
+ return value;
59
+ if (Array.isArray(value))
60
+ return value.map((entry) => stableNormalize(entry));
61
+ const record = value;
62
+ const normalized = {};
63
+ for (const key of Object.keys(record).sort()) {
64
+ const current = record[key];
65
+ if (typeof current === 'undefined')
66
+ continue;
67
+ normalized[key] = stableNormalize(current);
68
+ }
69
+ return normalized;
70
+ }
71
+ function stableHash(value) {
72
+ return (0, crypto_1.createHash)('sha256').update(JSON.stringify(stableNormalize(value)), 'utf-8').digest('hex');
73
+ }
74
+ function withinRoot(root, target) {
75
+ const normalizedRoot = root.endsWith(path_1.sep) ? root : `${root}${path_1.sep}`;
76
+ return target === root || target.startsWith(normalizedRoot);
77
+ }
78
+ function toRelativeSafe(root, filePath) {
79
+ const resolvedRoot = (0, path_1.resolve)(root);
80
+ const resolvedTarget = (0, path_1.resolve)(filePath);
81
+ if (!withinRoot(resolvedRoot, resolvedTarget))
82
+ return (0, path_1.basename)(resolvedTarget);
83
+ const rel = (0, path_1.relative)(resolvedRoot, resolvedTarget).replace(/\\/g, '/');
84
+ return rel.length > 0 ? rel : '.';
85
+ }
86
+ function toReplayPaths(cwd) {
87
+ const projectRoot = (0, path_1.resolve)(cwd);
88
+ const replayDir = (0, path_1.resolve)(projectRoot, '.neurcode/replay');
89
+ const cacheDir = (0, path_1.join)(replayDir, 'cache');
90
+ (0, fs_1.mkdirSync)(cacheDir, { recursive: true });
91
+ return {
92
+ projectRoot,
93
+ replayDir,
94
+ cacheFile: (0, path_1.join)(cacheDir, 'index.json'),
95
+ executionRecordsDir: (0, path_1.resolve)(projectRoot, '.neurcode/executions/records'),
96
+ evidenceDir: (0, path_1.resolve)(projectRoot, '.neurcode/evidence'),
97
+ runtimeEventsFile: (0, path_1.resolve)(projectRoot, '.neurcode/runtime-events/events.jsonl'),
98
+ controlPlaneSnapshotsDir: (0, path_1.resolve)(projectRoot, '.neurcode/control-plane/snapshots'),
99
+ workspaceSnapshotsDir: (0, path_1.resolve)(projectRoot, '.neurcode/workspaces/snapshots'),
100
+ };
101
+ }
102
+ function readJsonFile(filePath) {
103
+ try {
104
+ const raw = (0, fs_1.readFileSync)(filePath, 'utf-8');
105
+ const parsed = JSON.parse(raw);
106
+ return asObject(parsed);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ function fingerprint(filePath) {
113
+ try {
114
+ const stat = (0, fs_1.statSync)(filePath);
115
+ return {
116
+ size: stat.size,
117
+ mtimeMs: Math.floor(stat.mtimeMs),
118
+ };
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ function hasFingerprintChanged(key, next, cache) {
125
+ if (!next)
126
+ return true;
127
+ const prev = cache.files[key];
128
+ if (!prev)
129
+ return true;
130
+ return prev.size !== next.size || prev.mtimeMs !== next.mtimeMs;
131
+ }
132
+ function parseExecutionDigest(root, filePath) {
133
+ const parsed = readJsonFile(filePath);
134
+ if (!parsed || parsed.schemaVersion !== 'neurcode.execution.v1')
135
+ return null;
136
+ const result = asObject(parsed.result);
137
+ const verification = asObject(parsed.verification);
138
+ const diff = asObject(verification?.diff);
139
+ const afterCounts = asObject(diff?.after);
140
+ const beforeCounts = asObject(diff?.before);
141
+ const narrative = asObject(parsed.narrative);
142
+ const evidence = asObject(parsed.evidence);
143
+ const references = Array.isArray(evidence?.references)
144
+ ? evidence.references.filter((entry) => typeof entry === 'string')
145
+ : [];
146
+ const blocking = asNumber(afterCounts?.blocking) ?? asNumber(beforeCounts?.blocking) ?? 0;
147
+ const advisory = asNumber(afterCounts?.advisory) ?? asNumber(beforeCounts?.advisory) ?? 0;
148
+ const trendRaw = asString(diff?.trend);
149
+ const trend = trendRaw === 'improved'
150
+ || trendRaw === 'regressed'
151
+ || trendRaw === 'unchanged'
152
+ || trendRaw === 'baseline'
153
+ ? trendRaw
154
+ : 'baseline';
155
+ const riskRaw = asString(narrative?.riskLevel);
156
+ const riskLevel = riskRaw === 'high' || riskRaw === 'medium' ? riskRaw : 'low';
157
+ return {
158
+ file: toRelativeSafe(root, filePath),
159
+ id: asString(parsed.id) || (0, path_1.basename)(filePath),
160
+ type: asString(parsed.type) || 'unknown',
161
+ source: asString(parsed.source) || 'unknown',
162
+ actor: asString(parsed.actor) || 'unknown',
163
+ target: asString(parsed.target),
164
+ status: asString(parsed.status) || 'unknown',
165
+ createdAt: asString(parsed.createdAt) || nowIso(),
166
+ startedAt: asString(parsed.startedAt),
167
+ completedAt: asString(parsed.completedAt),
168
+ durationMs: asNumber(parsed.durationMs),
169
+ success: asBoolean(result?.success) === true,
170
+ exitCode: asNumber(result?.exitCode),
171
+ message: asString(result?.message),
172
+ trend,
173
+ blocking: clamp(Math.floor(blocking), 0, 100000),
174
+ advisory: clamp(Math.floor(advisory), 0, 100000),
175
+ evidenceRefs: references.map((entry) => toRelativeSafe(root, entry)),
176
+ narrative: narrative
177
+ ? {
178
+ summary: asString(narrative.summary) || '',
179
+ why: asString(narrative.why) || '',
180
+ riskLevel,
181
+ recommendedAction: asString(narrative.recommendedAction) || '',
182
+ expectedImprovement: asString(narrative.expectedImprovement) || '',
183
+ }
184
+ : null,
185
+ };
186
+ }
187
+ function parseEvidenceDigest(root, filePath) {
188
+ const parsed = readJsonFile(filePath);
189
+ if (!parsed || parsed.schemaVersion !== VERIFY_EVIDENCE_SCHEMA)
190
+ return null;
191
+ const canonical = asObject(parsed.canonicalVerifyOutput) || {};
192
+ const git = asObject(parsed.git) || {};
193
+ const regressions = Array.isArray(parsed.regressions) ? parsed.regressions : [];
194
+ const flowIssues = Array.isArray(parsed.flowIssues) ? parsed.flowIssues : [];
195
+ const coverage = asNumber(canonical.driftScore) ?? asNumber(canonical.score);
196
+ return {
197
+ file: toRelativeSafe(root, filePath),
198
+ timestamp: asString(parsed.timestamp) || nowIso(),
199
+ verdict: asString(parsed.verdict) || 'UNKNOWN',
200
+ pass: asBoolean(parsed.pass) === true,
201
+ ciMode: asBoolean(parsed.ciMode) === true,
202
+ deterministicMode: asBoolean(parsed.deterministicMode) === true,
203
+ deterministicVerificationHash: asString(parsed.deterministicVerificationHash) || '',
204
+ blockingCount: clamp(Math.floor(asNumber(parsed.blockingCount) ?? 0), 0, 100000),
205
+ advisoryCount: clamp(Math.floor(asNumber(parsed.advisoryCount) ?? 0), 0, 100000),
206
+ regressionCount: regressions.length,
207
+ flowIssueCount: flowIssues.length,
208
+ coverageScore: coverage === null ? null : clamp(Math.round(coverage * 100) / 100, 0, 100),
209
+ branch: asString(git.branch),
210
+ commitSha: asString(git.commitSha),
211
+ };
212
+ }
213
+ function parseControlPlaneSnapshotDigest(root, filePath) {
214
+ const parsed = readJsonFile(filePath);
215
+ if (!parsed || parsed.schemaVersion !== CONTROL_PLANE_SNAPSHOT_SCHEMA)
216
+ return null;
217
+ const impact = asObject(parsed.impact) || {};
218
+ const changedSections = Array.isArray(impact.changedSections)
219
+ ? impact.changedSections.filter((entry) => typeof entry === 'string').sort((left, right) => left.localeCompare(right))
220
+ : [];
221
+ const riskRaw = asString(impact.riskLevel);
222
+ const state = asObject(parsed.state);
223
+ const stateRecord = state
224
+ ? {
225
+ runtime: asObject(state.runtime) || {},
226
+ remediation: asObject(state.remediation) || {},
227
+ evidence: asObject(state.evidence) || {},
228
+ eventRuntime: asObject(state.eventRuntime) || {},
229
+ ciGovernance: asObject(state.ciGovernance) || {},
230
+ policyGovernance: asObject(state.policyGovernance) || {},
231
+ }
232
+ : null;
233
+ return {
234
+ file: toRelativeSafe(root, filePath),
235
+ snapshotId: asString(parsed.snapshotId) || (0, path_1.basename)(filePath),
236
+ createdAt: asString(parsed.createdAt) || nowIso(),
237
+ source: asString(parsed.source) || 'unknown',
238
+ actor: asString(parsed.actor) || 'unknown',
239
+ changedSections,
240
+ riskLevel: riskRaw === 'high' || riskRaw === 'medium' ? riskRaw : 'low',
241
+ state: stateRecord,
242
+ };
243
+ }
244
+ function parseWorkspaceSnapshotDigest(root, filePath) {
245
+ const parsed = readJsonFile(filePath);
246
+ if (!parsed || parsed.schemaVersion !== WORKSPACE_SNAPSHOT_SCHEMA)
247
+ return null;
248
+ const workspace = asObject(parsed.workspace) || {};
249
+ const posture = asObject(parsed.posture);
250
+ return {
251
+ file: toRelativeSafe(root, filePath),
252
+ snapshotId: asString(parsed.snapshotId) || (0, path_1.basename)(filePath),
253
+ workspaceId: asString(parsed.workspaceId) || 'unknown',
254
+ workspaceName: asString(parsed.workspaceName) || 'Workspace',
255
+ createdAt: asString(parsed.createdAt) || nowIso(),
256
+ source: asString(parsed.source) || 'unknown',
257
+ actor: asString(parsed.actor) || 'unknown',
258
+ action: asString(parsed.action) || 'snapshot',
259
+ executionId: asString(parsed.executionId),
260
+ activeWorkspaceId: asString(parsed.activeWorkspaceId),
261
+ workspace,
262
+ posture,
263
+ };
264
+ }
265
+ function parseRuntimeEvents(filePath) {
266
+ if (!(0, fs_1.existsSync)(filePath))
267
+ return [];
268
+ try {
269
+ const lines = (0, fs_1.readFileSync)(filePath, 'utf-8')
270
+ .split('\n')
271
+ .map((line) => line.trim())
272
+ .filter(Boolean);
273
+ const events = [];
274
+ for (const line of lines) {
275
+ try {
276
+ const parsed = JSON.parse(line);
277
+ const record = asObject(parsed);
278
+ if (!record || record.schemaVersion !== RUNTIME_EVENT_SCHEMA)
279
+ continue;
280
+ const severityRaw = asString(record.severity);
281
+ const severity = severityRaw === 'high' || severityRaw === 'medium' ? severityRaw : 'low';
282
+ events.push({
283
+ id: asString(record.id) || (0, crypto_1.createHash)('sha256').update(line, 'utf-8').digest('hex').slice(0, 16),
284
+ cursor: asString(record.cursor) || '',
285
+ type: asString(record.type) || 'unknown',
286
+ timestamp: asString(record.timestamp) || nowIso(),
287
+ executionId: asString(record.executionId) || 'unknown',
288
+ source: asString(record.source) || 'unknown',
289
+ actor: asString(record.actor) || 'unknown',
290
+ severity,
291
+ payload: asObject(record.payload) || {},
292
+ });
293
+ }
294
+ catch {
295
+ // Ignore malformed lines.
296
+ }
297
+ }
298
+ return events.sort((left, right) => {
299
+ const leftMs = toMs(left.timestamp) ?? 0;
300
+ const rightMs = toMs(right.timestamp) ?? 0;
301
+ if (leftMs !== rightMs)
302
+ return leftMs - rightMs;
303
+ return left.id.localeCompare(right.id);
304
+ });
305
+ }
306
+ catch {
307
+ return [];
308
+ }
309
+ }
310
+ function loadCache(paths) {
311
+ if (!(0, fs_1.existsSync)(paths.cacheFile)) {
312
+ return {
313
+ schemaVersion: REPLAY_CACHE_SCHEMA,
314
+ generatedAt: nowIso(),
315
+ files: {},
316
+ executions: {},
317
+ evidences: {},
318
+ controlPlaneSnapshots: {},
319
+ workspaceSnapshots: {},
320
+ runtimeEvents: [],
321
+ runtimeEventsFile: null,
322
+ };
323
+ }
324
+ try {
325
+ const parsed = JSON.parse((0, fs_1.readFileSync)(paths.cacheFile, 'utf-8'));
326
+ const record = asObject(parsed);
327
+ if (!record || record.schemaVersion !== REPLAY_CACHE_SCHEMA) {
328
+ throw new Error('invalid replay cache schema');
329
+ }
330
+ return {
331
+ schemaVersion: REPLAY_CACHE_SCHEMA,
332
+ generatedAt: asString(record.generatedAt) || nowIso(),
333
+ files: asObject(record.files) || {},
334
+ executions: asObject(record.executions) || {},
335
+ evidences: asObject(record.evidences) || {},
336
+ controlPlaneSnapshots: asObject(record.controlPlaneSnapshots) || {},
337
+ workspaceSnapshots: asObject(record.workspaceSnapshots) || {},
338
+ runtimeEvents: Array.isArray(record.runtimeEvents)
339
+ ? record.runtimeEvents.filter((entry) => {
340
+ const asRecord = asObject(entry);
341
+ return Boolean(asRecord && typeof asRecord.id === 'string' && typeof asRecord.timestamp === 'string');
342
+ })
343
+ : [],
344
+ runtimeEventsFile: asString(record.runtimeEventsFile),
345
+ };
346
+ }
347
+ catch {
348
+ return {
349
+ schemaVersion: REPLAY_CACHE_SCHEMA,
350
+ generatedAt: nowIso(),
351
+ files: {},
352
+ executions: {},
353
+ evidences: {},
354
+ controlPlaneSnapshots: {},
355
+ workspaceSnapshots: {},
356
+ runtimeEvents: [],
357
+ runtimeEventsFile: null,
358
+ };
359
+ }
360
+ }
361
+ function saveCache(paths, cache) {
362
+ cache.generatedAt = nowIso();
363
+ (0, fs_1.writeFileSync)(paths.cacheFile, `${JSON.stringify(cache, null, 2)}\n`, 'utf-8');
364
+ }
365
+ function listFiles(dirPath, filter) {
366
+ if (!(0, fs_1.existsSync)(dirPath))
367
+ return [];
368
+ return (0, fs_1.readdirSync)(dirPath)
369
+ .filter(filter)
370
+ .map((name) => (0, path_1.join)(dirPath, name))
371
+ .sort((left, right) => left.localeCompare(right));
372
+ }
373
+ function updateDigestMap(root, cache, files, existing, parseDigest) {
374
+ const keep = new Set();
375
+ for (const filePath of files) {
376
+ const key = toRelativeSafe(root, filePath);
377
+ const nextFingerprint = fingerprint(filePath);
378
+ if (!hasFingerprintChanged(key, nextFingerprint, cache) && existing[key]) {
379
+ keep.add(key);
380
+ continue;
381
+ }
382
+ const parsed = parseDigest(root, filePath);
383
+ if (!parsed)
384
+ continue;
385
+ existing[key] = parsed;
386
+ keep.add(key);
387
+ if (nextFingerprint)
388
+ cache.files[key] = nextFingerprint;
389
+ }
390
+ for (const key of Object.keys(existing)) {
391
+ if (!keep.has(key)) {
392
+ delete existing[key];
393
+ delete cache.files[key];
394
+ }
395
+ }
396
+ return existing;
397
+ }
398
+ function buildReplayIndex(cwd = process.cwd()) {
399
+ const paths = toReplayPaths(cwd);
400
+ const cache = loadCache(paths);
401
+ const executionFiles = listFiles(paths.executionRecordsDir, (name) => name.startsWith('execution-') && name.endsWith('.json'));
402
+ const evidenceFiles = listFiles(paths.evidenceDir, (name) => name.startsWith('verification-') && name.endsWith('.json'));
403
+ const controlPlaneSnapshotFiles = listFiles(paths.controlPlaneSnapshotsDir, (name) => name.startsWith('snapshot-') && name.endsWith('.json'));
404
+ const workspaceSnapshotFiles = listFiles(paths.workspaceSnapshotsDir, (name) => name.startsWith('workspace-snapshot-') && name.endsWith('.json'));
405
+ cache.executions = updateDigestMap(paths.projectRoot, cache, executionFiles, cache.executions, parseExecutionDigest);
406
+ cache.evidences = updateDigestMap(paths.projectRoot, cache, evidenceFiles, cache.evidences, parseEvidenceDigest);
407
+ cache.controlPlaneSnapshots = updateDigestMap(paths.projectRoot, cache, controlPlaneSnapshotFiles, cache.controlPlaneSnapshots, parseControlPlaneSnapshotDigest);
408
+ cache.workspaceSnapshots = updateDigestMap(paths.projectRoot, cache, workspaceSnapshotFiles, cache.workspaceSnapshots, parseWorkspaceSnapshotDigest);
409
+ const runtimeFingerprint = fingerprint(paths.runtimeEventsFile);
410
+ const runtimeKey = toRelativeSafe(paths.projectRoot, paths.runtimeEventsFile);
411
+ const runtimeChanged = hasFingerprintChanged(runtimeKey, runtimeFingerprint, cache) || cache.runtimeEventsFile !== runtimeKey;
412
+ if (runtimeChanged) {
413
+ cache.runtimeEvents = parseRuntimeEvents(paths.runtimeEventsFile);
414
+ cache.runtimeEventsFile = runtimeKey;
415
+ if (runtimeFingerprint)
416
+ cache.files[runtimeKey] = runtimeFingerprint;
417
+ }
418
+ saveCache(paths, cache);
419
+ const executions = Object.values(cache.executions).sort((left, right) => {
420
+ const leftMs = toMs(left.createdAt) ?? 0;
421
+ const rightMs = toMs(right.createdAt) ?? 0;
422
+ if (leftMs !== rightMs)
423
+ return leftMs - rightMs;
424
+ return left.id.localeCompare(right.id);
425
+ });
426
+ const evidences = Object.values(cache.evidences).sort((left, right) => {
427
+ const leftMs = toMs(left.timestamp) ?? 0;
428
+ const rightMs = toMs(right.timestamp) ?? 0;
429
+ if (leftMs !== rightMs)
430
+ return leftMs - rightMs;
431
+ return left.file.localeCompare(right.file);
432
+ });
433
+ const controlPlaneSnapshots = Object.values(cache.controlPlaneSnapshots).sort((left, right) => {
434
+ const leftMs = toMs(left.createdAt) ?? 0;
435
+ const rightMs = toMs(right.createdAt) ?? 0;
436
+ if (leftMs !== rightMs)
437
+ return leftMs - rightMs;
438
+ return left.snapshotId.localeCompare(right.snapshotId);
439
+ });
440
+ const workspaceSnapshots = Object.values(cache.workspaceSnapshots).sort((left, right) => {
441
+ const leftMs = toMs(left.createdAt) ?? 0;
442
+ const rightMs = toMs(right.createdAt) ?? 0;
443
+ if (leftMs !== rightMs)
444
+ return leftMs - rightMs;
445
+ return left.snapshotId.localeCompare(right.snapshotId);
446
+ });
447
+ return {
448
+ executions,
449
+ evidences,
450
+ controlPlaneSnapshots,
451
+ workspaceSnapshots,
452
+ runtimeEvents: cache.runtimeEvents,
453
+ };
454
+ }
455
+ function parseRequiredTimestamp(input) {
456
+ const parsed = Date.parse(input);
457
+ if (!Number.isFinite(parsed)) {
458
+ throw new Error(`Invalid timestamp: ${input}`);
459
+ }
460
+ return new Date(parsed).toISOString();
461
+ }
462
+ function selectLatestAt(items, toTimestamp, atMs) {
463
+ let selected = null;
464
+ let selectedMs = Number.NEGATIVE_INFINITY;
465
+ for (const item of items) {
466
+ const itemMs = toMs(toTimestamp(item));
467
+ if (itemMs === null || itemMs > atMs)
468
+ continue;
469
+ if (itemMs >= selectedMs) {
470
+ selected = item;
471
+ selectedMs = itemMs;
472
+ }
473
+ }
474
+ return selected;
475
+ }
476
+ function riskFromCounts(blocking, advisory) {
477
+ if (blocking > 0)
478
+ return 'high';
479
+ if (advisory > 0)
480
+ return 'medium';
481
+ return 'low';
482
+ }
483
+ function buildHotspots(executions) {
484
+ const buckets = new Map();
485
+ for (const execution of executions) {
486
+ for (const ref of execution.evidenceRefs) {
487
+ const current = buckets.get(ref) || { occurrences: 0, score: 0 };
488
+ current.occurrences += 1;
489
+ current.score += execution.blocking * 2 + execution.advisory * 0.5 + (execution.trend === 'regressed' ? 2 : 0);
490
+ buckets.set(ref, current);
491
+ }
492
+ }
493
+ return [...buckets.entries()]
494
+ .map(([key, value]) => ({
495
+ key,
496
+ occurrences: value.occurrences,
497
+ score: Math.round((value.score / Math.max(1, value.occurrences)) * 100) / 100,
498
+ }))
499
+ .sort((left, right) => right.score - left.score || right.occurrences - left.occurrences)
500
+ .slice(0, 20);
501
+ }
502
+ function computePosture(executions, evidences) {
503
+ const runCount = executions.length > 0 ? executions.length : evidences.length;
504
+ let passCount = 0;
505
+ let blocked = 0;
506
+ let regressions = 0;
507
+ let latestVerdict = null;
508
+ let latestCoverageScore = null;
509
+ let latestMs = Number.NEGATIVE_INFINITY;
510
+ for (const execution of executions) {
511
+ if (execution.success)
512
+ passCount += 1;
513
+ if (execution.blocking > 0 || execution.status === 'failed')
514
+ blocked += 1;
515
+ if (execution.trend === 'regressed')
516
+ regressions += 1;
517
+ }
518
+ for (const evidence of evidences) {
519
+ const eventMs = toMs(evidence.timestamp) ?? Number.NEGATIVE_INFINITY;
520
+ if (eventMs >= latestMs) {
521
+ latestMs = eventMs;
522
+ latestVerdict = evidence.verdict;
523
+ latestCoverageScore = evidence.coverageScore;
524
+ }
525
+ }
526
+ const passRate = runCount > 0 ? Math.round((passCount / runCount) * 10000) / 100 : 0;
527
+ const blockRate = runCount > 0 ? Math.round((blocked / runCount) * 10000) / 100 : 0;
528
+ const regressionRate = runCount > 0 ? Math.round((regressions / runCount) * 10000) / 100 : 0;
529
+ return {
530
+ runCount,
531
+ passRate,
532
+ blockRate,
533
+ regressionRate,
534
+ latestVerdict,
535
+ latestCoverageScore,
536
+ };
537
+ }
538
+ function normalizeTimelineLimit(limit) {
539
+ if (typeof limit === 'number' && Number.isFinite(limit) && limit >= 1) {
540
+ return Math.min(MAX_TIMELINE_LIMIT, Math.floor(limit));
541
+ }
542
+ return DEFAULT_TIMELINE_LIMIT;
543
+ }
544
+ function buildDeterminismWarnings(index, controlPlane, workspace) {
545
+ const warnings = [];
546
+ if (controlPlane === null) {
547
+ warnings.push('No immutable control-plane snapshot existed at this timestamp.');
548
+ }
549
+ if (workspace === null) {
550
+ warnings.push('No immutable workspace snapshot existed at this timestamp.');
551
+ }
552
+ if (index.executions.length === 0) {
553
+ warnings.push('No execution records available in replay index.');
554
+ }
555
+ if (index.evidences.length === 0) {
556
+ warnings.push('No evidence artifacts available in replay index.');
557
+ }
558
+ return warnings;
559
+ }
560
+ function replayGovernanceState(request, cwd = process.cwd()) {
561
+ const paths = toReplayPaths(cwd);
562
+ const asOf = parseRequiredTimestamp(request.at);
563
+ const asOfMs = Date.parse(asOf);
564
+ const index = buildReplayIndex(paths.projectRoot);
565
+ const limitEvents = typeof request.eventLimit === 'number' && Number.isFinite(request.eventLimit)
566
+ ? Math.max(1, Math.min(2000, Math.floor(request.eventLimit)))
567
+ : DEFAULT_EVENT_LIMIT;
568
+ const executions = index.executions.filter((entry) => {
569
+ const ts = toMs(entry.createdAt);
570
+ if (ts === null || ts > asOfMs)
571
+ return false;
572
+ if (!request.workspaceId)
573
+ return true;
574
+ return entry.target === null || entry.target.includes(request.workspaceId);
575
+ });
576
+ const evidences = index.evidences.filter((entry) => {
577
+ const ts = toMs(entry.timestamp);
578
+ return ts !== null && ts <= asOfMs;
579
+ });
580
+ const controlPlane = selectLatestAt(index.controlPlaneSnapshots, (item) => item.createdAt, asOfMs);
581
+ const workspace = request.workspaceId
582
+ ? selectLatestAt(index.workspaceSnapshots.filter((item) => item.workspaceId === request.workspaceId), (item) => item.createdAt, asOfMs)
583
+ : selectLatestAt(index.workspaceSnapshots, (item) => item.createdAt, asOfMs);
584
+ const regressions = executions
585
+ .filter((entry) => entry.trend === 'regressed')
586
+ .sort((left, right) => (toMs(right.createdAt) ?? 0) - (toMs(left.createdAt) ?? 0))
587
+ .slice(0, 50)
588
+ .map((entry) => ({
589
+ executionId: entry.id,
590
+ createdAt: entry.createdAt,
591
+ source: entry.source,
592
+ actor: entry.actor,
593
+ blockingDelta: entry.trend === 'regressed' ? entry.blocking : null,
594
+ advisoryDelta: entry.trend === 'regressed' ? entry.advisory : null,
595
+ trend: entry.trend,
596
+ }));
597
+ const blockedExecutions = executions
598
+ .filter((entry) => entry.blocking > 0 || entry.status === 'failed')
599
+ .sort((left, right) => (toMs(right.createdAt) ?? 0) - (toMs(left.createdAt) ?? 0))
600
+ .slice(0, 50)
601
+ .map((entry) => ({
602
+ executionId: entry.id,
603
+ createdAt: entry.createdAt,
604
+ type: entry.type,
605
+ source: entry.source,
606
+ actor: entry.actor,
607
+ blocking: entry.blocking,
608
+ advisory: entry.advisory,
609
+ message: entry.message,
610
+ }));
611
+ const timelineItems = [];
612
+ for (const execution of executions) {
613
+ timelineItems.push({
614
+ timestamp: execution.createdAt,
615
+ kind: 'execution',
616
+ id: execution.id,
617
+ summary: `${execution.type} ${execution.success ? 'succeeded' : 'failed'} (${execution.trend})`,
618
+ severity: riskFromCounts(execution.blocking, execution.advisory),
619
+ source: execution.source,
620
+ });
621
+ }
622
+ for (const evidence of evidences) {
623
+ timelineItems.push({
624
+ timestamp: evidence.timestamp,
625
+ kind: 'evidence',
626
+ id: evidence.file,
627
+ summary: `verify ${evidence.verdict} (blocking ${evidence.blockingCount}, advisory ${evidence.advisoryCount})`,
628
+ severity: riskFromCounts(evidence.blockingCount, evidence.advisoryCount),
629
+ source: evidence.ciMode ? 'ci' : 'cli',
630
+ });
631
+ }
632
+ if (controlPlane) {
633
+ timelineItems.push({
634
+ timestamp: controlPlane.createdAt,
635
+ kind: 'control-plane',
636
+ id: controlPlane.snapshotId,
637
+ summary: `control-plane snapshot (${controlPlane.changedSections.join(', ') || 'no section changes'})`,
638
+ severity: controlPlane.riskLevel,
639
+ source: controlPlane.source,
640
+ });
641
+ }
642
+ if (workspace) {
643
+ timelineItems.push({
644
+ timestamp: workspace.createdAt,
645
+ kind: 'workspace',
646
+ id: workspace.snapshotId,
647
+ summary: `workspace ${workspace.workspaceName} ${workspace.action}`,
648
+ severity: 'low',
649
+ source: workspace.source,
650
+ });
651
+ }
652
+ const events = index.runtimeEvents
653
+ .filter((event) => {
654
+ const ts = toMs(event.timestamp);
655
+ if (ts === null || ts > asOfMs)
656
+ return false;
657
+ if (!request.workspaceId)
658
+ return true;
659
+ const workspaceIdFromPayload = asString(event.payload.workspaceId);
660
+ return workspaceIdFromPayload ? workspaceIdFromPayload === request.workspaceId : true;
661
+ })
662
+ .slice(-limitEvents);
663
+ if (request.includeEvents) {
664
+ for (const event of events) {
665
+ timelineItems.push({
666
+ timestamp: event.timestamp,
667
+ kind: 'event',
668
+ id: event.id,
669
+ summary: event.type,
670
+ severity: event.severity,
671
+ source: event.source,
672
+ });
673
+ }
674
+ }
675
+ timelineItems.sort((left, right) => {
676
+ const leftMs = toMs(left.timestamp) ?? 0;
677
+ const rightMs = toMs(right.timestamp) ?? 0;
678
+ if (leftMs !== rightMs)
679
+ return rightMs - leftMs;
680
+ return left.id.localeCompare(right.id);
681
+ });
682
+ const posture = computePosture(executions, evidences);
683
+ const hotspots = buildHotspots(executions);
684
+ const warnings = buildDeterminismWarnings(index, controlPlane, workspace);
685
+ const determinismPayload = {
686
+ asOf,
687
+ controlPlaneSnapshotId: controlPlane?.snapshotId || null,
688
+ workspaceSnapshotId: workspace?.snapshotId || null,
689
+ executions,
690
+ evidences,
691
+ events: request.includeEvents ? events : [],
692
+ regressions,
693
+ blockedExecutions,
694
+ posture,
695
+ hotspots,
696
+ };
697
+ return {
698
+ schemaVersion: REPLAY_STATE_SCHEMA,
699
+ generatedAt: nowIso(),
700
+ asOf,
701
+ rootDir: paths.projectRoot,
702
+ determinism: {
703
+ immutableOnly: true,
704
+ artifactHash: stableHash(determinismPayload),
705
+ warnings,
706
+ inputs: {
707
+ executionRecords: executions.length,
708
+ evidenceArtifacts: evidences.length,
709
+ runtimeEvents: events.length,
710
+ controlPlaneSnapshots: index.controlPlaneSnapshots.length,
711
+ workspaceSnapshots: index.workspaceSnapshots.length,
712
+ },
713
+ },
714
+ controlPlane: {
715
+ snapshotId: controlPlane?.snapshotId || null,
716
+ createdAt: controlPlane?.createdAt || null,
717
+ source: controlPlane?.source || null,
718
+ actor: controlPlane?.actor || null,
719
+ changedSections: controlPlane?.changedSections || [],
720
+ state: controlPlane?.state || null,
721
+ },
722
+ workspace: {
723
+ workspaceId: workspace?.workspaceId || null,
724
+ workspaceName: workspace?.workspaceName || null,
725
+ snapshotId: workspace?.snapshotId || null,
726
+ action: workspace?.action || null,
727
+ activeWorkspaceId: workspace?.activeWorkspaceId || null,
728
+ posture: workspace?.posture || null,
729
+ definition: workspace?.workspace || null,
730
+ },
731
+ posture,
732
+ regressions,
733
+ hotspots,
734
+ blockedExecutions,
735
+ timeline: timelineItems.slice(0, MAX_TIMELINE_LIMIT),
736
+ events,
737
+ };
738
+ }
739
+ function executionTimeline(record) {
740
+ const events = Array.isArray(record.events) ? record.events : [];
741
+ return events
742
+ .map((event) => {
743
+ const row = asObject(event);
744
+ return {
745
+ stage: asString(row?.stage) || 'unknown',
746
+ timestamp: asString(row?.timestamp) || nowIso(),
747
+ message: asString(row?.message) || 'event',
748
+ details: asObject(row?.details),
749
+ };
750
+ })
751
+ .sort((left, right) => (toMs(left.timestamp) ?? 0) - (toMs(right.timestamp) ?? 0));
752
+ }
753
+ function digestFromExecutionRecord(root, record) {
754
+ const after = record.verification.diff.after || record.verification.diff.before || { blocking: 0, advisory: 0 };
755
+ const narrative = record.narrative
756
+ ? {
757
+ summary: record.narrative.summary,
758
+ why: record.narrative.why,
759
+ riskLevel: record.narrative.riskLevel,
760
+ recommendedAction: record.narrative.recommendedAction,
761
+ expectedImprovement: record.narrative.expectedImprovement,
762
+ }
763
+ : null;
764
+ return {
765
+ file: `execution-${record.id}`,
766
+ id: record.id,
767
+ type: record.type,
768
+ source: record.source,
769
+ actor: record.actor,
770
+ target: record.target,
771
+ status: record.status,
772
+ createdAt: record.createdAt,
773
+ startedAt: record.startedAt,
774
+ completedAt: record.completedAt,
775
+ durationMs: record.durationMs,
776
+ success: record.result?.success === true,
777
+ exitCode: typeof record.result?.exitCode === 'number' ? record.result.exitCode : null,
778
+ message: record.result?.message || null,
779
+ trend: record.verification.diff.trend,
780
+ blocking: Math.max(0, Math.floor(after?.blocking || 0)),
781
+ advisory: Math.max(0, Math.floor(after?.advisory || 0)),
782
+ evidenceRefs: (record.evidence.references || []).map((entry) => toRelativeSafe(root, entry)),
783
+ narrative,
784
+ };
785
+ }
786
+ function replayExecution(request, cwd = process.cwd()) {
787
+ const paths = toReplayPaths(cwd);
788
+ const record = (0, execution_bus_1.getExecutionById)(request.executionId, paths.projectRoot);
789
+ if (!record) {
790
+ throw new Error(`Execution not found: ${request.executionId}`);
791
+ }
792
+ const digest = digestFromExecutionRecord(paths.projectRoot, record);
793
+ const createdMs = toMs(digest.createdAt) ?? Date.now();
794
+ const stateAtExecution = replayGovernanceState({
795
+ at: new Date(createdMs).toISOString(),
796
+ includeEvents: true,
797
+ eventLimit: 200,
798
+ }, paths.projectRoot);
799
+ const relatedEvents = stateAtExecution.events.filter((event) => event.executionId === digest.id);
800
+ const relatedEvidence = buildReplayIndex(paths.projectRoot).evidences
801
+ .filter((evidence) => digest.evidenceRefs.includes(evidence.file))
802
+ .sort((left, right) => (toMs(left.timestamp) ?? 0) - (toMs(right.timestamp) ?? 0));
803
+ const warnings = [...stateAtExecution.determinism.warnings];
804
+ const artifactHash = stableHash({
805
+ execution: digest,
806
+ timeline: executionTimeline(record),
807
+ relatedEvents,
808
+ relatedEvidence,
809
+ posture: stateAtExecution.posture,
810
+ });
811
+ return {
812
+ schemaVersion: REPLAY_EXECUTION_SCHEMA,
813
+ generatedAt: nowIso(),
814
+ executionId: digest.id,
815
+ rootDir: paths.projectRoot,
816
+ determinism: {
817
+ immutableOnly: true,
818
+ artifactHash,
819
+ warnings,
820
+ },
821
+ execution: digest,
822
+ timeline: executionTimeline(record),
823
+ relatedEvents,
824
+ relatedEvidence,
825
+ predictedVsActual: {
826
+ predictedRisk: digest.narrative?.riskLevel || null,
827
+ expectedImprovement: digest.narrative?.expectedImprovement || null,
828
+ actualSuccess: digest.success,
829
+ actualTrend: digest.trend,
830
+ blocking: digest.blocking,
831
+ advisory: digest.advisory,
832
+ },
833
+ resultingPosture: {
834
+ runCount: stateAtExecution.posture.runCount,
835
+ passRate: stateAtExecution.posture.passRate,
836
+ blockRate: stateAtExecution.posture.blockRate,
837
+ regressionRate: stateAtExecution.posture.regressionRate,
838
+ latestVerdict: stateAtExecution.posture.latestVerdict,
839
+ },
840
+ };
841
+ }
842
+ function replayWorkspace(request, cwd = process.cwd()) {
843
+ const at = request.at ? parseRequiredTimestamp(request.at) : nowIso();
844
+ const state = replayGovernanceState({
845
+ at,
846
+ workspaceId: request.workspaceId,
847
+ includeEvents: true,
848
+ eventLimit: 300,
849
+ }, cwd);
850
+ const executionsForWorkspace = state.timeline.filter((entry) => entry.kind === 'execution');
851
+ const failed = state.blockedExecutions.length;
852
+ const total = executionsForWorkspace.length;
853
+ const succeeded = Math.max(0, total - failed);
854
+ const blockRate = total > 0 ? Math.round((failed / total) * 10000) / 100 : 0;
855
+ const passRate = total > 0 ? Math.round((succeeded / total) * 10000) / 100 : 0;
856
+ const warnings = [...state.determinism.warnings];
857
+ return {
858
+ schemaVersion: REPLAY_WORKSPACE_SCHEMA,
859
+ generatedAt: nowIso(),
860
+ asOf: at,
861
+ rootDir: state.rootDir,
862
+ workspaceId: state.workspace.workspaceId,
863
+ workspaceName: state.workspace.workspaceName,
864
+ activeWorkspaceId: state.workspace.activeWorkspaceId,
865
+ snapshotId: state.workspace.snapshotId,
866
+ action: state.workspace.action,
867
+ posture: state.workspace.posture,
868
+ definition: state.workspace.definition,
869
+ executionSummary: {
870
+ total,
871
+ succeeded,
872
+ failed,
873
+ passRate,
874
+ blockRate,
875
+ },
876
+ hotspotSummary: state.hotspots.slice(0, 20),
877
+ recentEvents: state.events.slice(-50),
878
+ determinism: {
879
+ immutableOnly: true,
880
+ artifactHash: stableHash({
881
+ asOf: at,
882
+ workspaceId: state.workspace.workspaceId,
883
+ executionSummary: {
884
+ total,
885
+ succeeded,
886
+ failed,
887
+ passRate,
888
+ blockRate,
889
+ },
890
+ hotspots: state.hotspots,
891
+ events: state.events,
892
+ }),
893
+ warnings,
894
+ },
895
+ };
896
+ }
897
+ function replayTimeline(request = {}, cwd = process.cwd()) {
898
+ const paths = toReplayPaths(cwd);
899
+ const index = buildReplayIndex(paths.projectRoot);
900
+ const from = request.from ? parseRequiredTimestamp(request.from) : null;
901
+ const to = request.to ? parseRequiredTimestamp(request.to) : null;
902
+ const fromMs = from ? Date.parse(from) : null;
903
+ const toMsValue = to ? Date.parse(to) : null;
904
+ const limit = normalizeTimelineLimit(request.limit);
905
+ const items = [];
906
+ for (const execution of index.executions) {
907
+ const ts = toMs(execution.createdAt);
908
+ if (ts === null)
909
+ continue;
910
+ if (fromMs !== null && ts < fromMs)
911
+ continue;
912
+ if (toMsValue !== null && ts > toMsValue)
913
+ continue;
914
+ items.push({
915
+ timestamp: execution.createdAt,
916
+ kind: 'execution',
917
+ id: execution.id,
918
+ source: execution.source,
919
+ severity: riskFromCounts(execution.blocking, execution.advisory),
920
+ summary: `${execution.type} ${execution.success ? 'success' : 'failure'} (${execution.trend})`,
921
+ executionId: execution.id,
922
+ workspaceId: request.workspaceId || null,
923
+ });
924
+ }
925
+ for (const evidence of index.evidences) {
926
+ const ts = toMs(evidence.timestamp);
927
+ if (ts === null)
928
+ continue;
929
+ if (fromMs !== null && ts < fromMs)
930
+ continue;
931
+ if (toMsValue !== null && ts > toMsValue)
932
+ continue;
933
+ items.push({
934
+ timestamp: evidence.timestamp,
935
+ kind: 'evidence',
936
+ id: evidence.file,
937
+ source: evidence.ciMode ? 'ci' : 'cli',
938
+ severity: riskFromCounts(evidence.blockingCount, evidence.advisoryCount),
939
+ summary: `verification ${evidence.verdict}`,
940
+ executionId: null,
941
+ workspaceId: request.workspaceId || null,
942
+ });
943
+ }
944
+ for (const event of index.runtimeEvents) {
945
+ const ts = toMs(event.timestamp);
946
+ if (ts === null)
947
+ continue;
948
+ if (fromMs !== null && ts < fromMs)
949
+ continue;
950
+ if (toMsValue !== null && ts > toMsValue)
951
+ continue;
952
+ if (request.workspaceId) {
953
+ const workspaceId = asString(event.payload.workspaceId);
954
+ if (workspaceId && workspaceId !== request.workspaceId)
955
+ continue;
956
+ }
957
+ items.push({
958
+ timestamp: event.timestamp,
959
+ kind: 'event',
960
+ id: event.id,
961
+ source: event.source,
962
+ severity: event.severity,
963
+ summary: event.type,
964
+ executionId: event.executionId,
965
+ workspaceId: asString(event.payload.workspaceId),
966
+ });
967
+ }
968
+ for (const snapshot of index.controlPlaneSnapshots) {
969
+ const ts = toMs(snapshot.createdAt);
970
+ if (ts === null)
971
+ continue;
972
+ if (fromMs !== null && ts < fromMs)
973
+ continue;
974
+ if (toMsValue !== null && ts > toMsValue)
975
+ continue;
976
+ items.push({
977
+ timestamp: snapshot.createdAt,
978
+ kind: 'control-plane',
979
+ id: snapshot.snapshotId,
980
+ source: snapshot.source,
981
+ severity: snapshot.riskLevel,
982
+ summary: `control-plane update (${snapshot.changedSections.join(', ') || 'no section changes'})`,
983
+ executionId: null,
984
+ workspaceId: null,
985
+ });
986
+ }
987
+ for (const snapshot of index.workspaceSnapshots) {
988
+ const ts = toMs(snapshot.createdAt);
989
+ if (ts === null)
990
+ continue;
991
+ if (fromMs !== null && ts < fromMs)
992
+ continue;
993
+ if (toMsValue !== null && ts > toMsValue)
994
+ continue;
995
+ if (request.workspaceId && snapshot.workspaceId !== request.workspaceId)
996
+ continue;
997
+ items.push({
998
+ timestamp: snapshot.createdAt,
999
+ kind: 'workspace',
1000
+ id: snapshot.snapshotId,
1001
+ source: snapshot.source,
1002
+ severity: 'low',
1003
+ summary: `${snapshot.workspaceName} ${snapshot.action}`,
1004
+ executionId: snapshot.executionId,
1005
+ workspaceId: snapshot.workspaceId,
1006
+ });
1007
+ }
1008
+ items.sort((left, right) => {
1009
+ const leftMs = toMs(left.timestamp) ?? 0;
1010
+ const rightMs = toMs(right.timestamp) ?? 0;
1011
+ if (leftMs !== rightMs)
1012
+ return rightMs - leftMs;
1013
+ return left.id.localeCompare(right.id);
1014
+ });
1015
+ const trimmed = items.slice(0, limit);
1016
+ const warnings = buildDeterminismWarnings(index, index.controlPlaneSnapshots[index.controlPlaneSnapshots.length - 1] || null, index.workspaceSnapshots[index.workspaceSnapshots.length - 1] || null);
1017
+ return {
1018
+ schemaVersion: REPLAY_TIMELINE_SCHEMA,
1019
+ generatedAt: nowIso(),
1020
+ rootDir: paths.projectRoot,
1021
+ from,
1022
+ to,
1023
+ workspaceId: request.workspaceId || null,
1024
+ count: trimmed.length,
1025
+ items: trimmed,
1026
+ aggregate: {
1027
+ executions: trimmed.filter((entry) => entry.kind === 'execution').length,
1028
+ evidence: trimmed.filter((entry) => entry.kind === 'evidence').length,
1029
+ runtimeEvents: trimmed.filter((entry) => entry.kind === 'event').length,
1030
+ controlPlane: trimmed.filter((entry) => entry.kind === 'control-plane').length,
1031
+ workspace: trimmed.filter((entry) => entry.kind === 'workspace').length,
1032
+ },
1033
+ determinism: {
1034
+ immutableOnly: true,
1035
+ artifactHash: stableHash({
1036
+ from,
1037
+ to,
1038
+ workspaceId: request.workspaceId || null,
1039
+ items: trimmed,
1040
+ }),
1041
+ warnings,
1042
+ },
1043
+ };
1044
+ }
1045
+ function getWorkspaceSnapshotHistory(cwd = process.cwd(), limit = 50) {
1046
+ const paths = toReplayPaths(cwd);
1047
+ const index = buildReplayIndex(paths.projectRoot);
1048
+ return [...index.workspaceSnapshots]
1049
+ .sort((left, right) => (toMs(right.createdAt) ?? 0) - (toMs(left.createdAt) ?? 0))
1050
+ .slice(0, Math.max(1, Math.min(500, Math.floor(limit))));
1051
+ }
1052
+ function writeWorkspaceReplaySnapshot(input) {
1053
+ const paths = toReplayPaths(input.cwd || process.cwd());
1054
+ const snapshotsDir = paths.workspaceSnapshotsDir;
1055
+ (0, fs_1.mkdirSync)(snapshotsDir, { recursive: true });
1056
+ const createdAt = nowIso();
1057
+ const seed = {
1058
+ workspaceId: input.workspaceId,
1059
+ workspaceName: input.workspaceName,
1060
+ action: input.action,
1061
+ source: input.source,
1062
+ actor: input.actor,
1063
+ createdAt,
1064
+ executionId: input.executionId || null,
1065
+ activeWorkspaceId: input.activeWorkspaceId || null,
1066
+ workspace: input.workspace,
1067
+ posture: input.posture || null,
1068
+ };
1069
+ const snapshotId = `wss-${stableHash(seed).slice(0, 12)}`;
1070
+ const fileName = `workspace-snapshot-${createdAt.replace(/[.:]/g, '-')}-${snapshotId}.json`;
1071
+ const snapshotPath = (0, path_1.join)(snapshotsDir, fileName);
1072
+ const payload = {
1073
+ schemaVersion: WORKSPACE_SNAPSHOT_SCHEMA,
1074
+ snapshotId,
1075
+ ...seed,
1076
+ };
1077
+ (0, fs_1.writeFileSync)(snapshotPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
1078
+ return { snapshotId, snapshotPath };
1079
+ }
1080
+ //# sourceMappingURL=replay-runtime.js.map