mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-31 06:03:29 +08:00
* Add install.ps1 PowerShell wrapper and tests Add a Windows-native PowerShell wrapper (install.ps1) that resolves symlinks and delegates to the Node-based installer runtime. Update README with PowerShell usage examples and cross-platform npx entrypoint guidance. Point the ecc-install bin to the Node installer (scripts/install-apply.js) in package.json (and refresh package-lock), include install.ps1 in package files, and add tests: a new install-ps1.test.js and a tweak to install-sh.test.js to skip on Windows. These changes provide native Windows installer support while keeping npm-compatible cross-platform invocation. * Improve tests for Windows HOME/USERPROFILE Make tests more cross-platform by ensuring HOME and USERPROFILE are kept in sync and by normalizing test file paths for display. - tests/lib/session-adapters.test.js: set USERPROFILE when temporarily setting HOME and restore previous USERPROFILE on teardown. - tests/run-all.js: use a normalized displayPath (forward-slash separated) for logging and error messages so output is consistent across platforms. - tests/scripts/ecc.test.js & tests/scripts/session-inspect.test.js: build envOverrides from options.env and add HOME <-> USERPROFILE fallbacks so spawned child processes receive both variables when only one is provided. These changes prevent test failures and inconsistent logs on Windows where USERPROFILE is used instead of HOME. * Fix Windows paths and test flakiness Improve cross-platform behavior and test stability. - Remove unused createLegacyInstallPlan import from install-lifecycle.js. - Change resolveInstallConfigPath to use path.normalize(path.join(cwd, configPath)) to produce normalized relative paths. - Tests: add toBashPath and normalizedRelativePath helpers to normalize Windows paths for bash and comparisons. - Make cleanupTestDir retry rmSync on transient Windows errors (EPERM/EBUSY/ENOTEMPTY) with short backoff using sleepMs. - Ensure spawned test processes receive USERPROFILE and convert repo/detect paths to bash format when invoking bash. These changes reduce Windows-specific failures and flakiness in the test suite and tidy up a small unused import.
562 lines
19 KiB
JavaScript
562 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const {
|
|
getFallbackSessionRecordingPath,
|
|
persistCanonicalSnapshot
|
|
} = require('../../scripts/lib/session-adapters/canonical-session');
|
|
const { createClaudeHistoryAdapter } = require('../../scripts/lib/session-adapters/claude-history');
|
|
const { createDmuxTmuxAdapter } = require('../../scripts/lib/session-adapters/dmux-tmux');
|
|
const {
|
|
createAdapterRegistry,
|
|
inspectSessionTarget
|
|
} = require('../../scripts/lib/session-adapters/registry');
|
|
|
|
console.log('=== Testing session-adapters ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
passed += 1;
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}: ${error.message}`);
|
|
failed += 1;
|
|
}
|
|
}
|
|
|
|
function withHome(homeDir, fn) {
|
|
const previousHome = process.env.HOME;
|
|
const previousUserProfile = process.env.USERPROFILE;
|
|
process.env.HOME = homeDir;
|
|
process.env.USERPROFILE = homeDir;
|
|
|
|
try {
|
|
fn();
|
|
} finally {
|
|
if (typeof previousHome === 'string') {
|
|
process.env.HOME = previousHome;
|
|
} else {
|
|
delete process.env.HOME;
|
|
}
|
|
|
|
if (typeof previousUserProfile === 'string') {
|
|
process.env.USERPROFILE = previousUserProfile;
|
|
} else {
|
|
delete process.env.USERPROFILE;
|
|
}
|
|
}
|
|
}
|
|
|
|
test('dmux adapter normalizes orchestration snapshots into canonical form', () => {
|
|
const recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-recordings-'));
|
|
|
|
try {
|
|
const adapter = createDmuxTmuxAdapter({
|
|
collectSessionSnapshotImpl: () => ({
|
|
sessionName: 'workflow-visual-proof',
|
|
coordinationDir: '/tmp/.claude/orchestration/workflow-visual-proof',
|
|
repoRoot: '/tmp/repo',
|
|
targetType: 'plan',
|
|
sessionActive: true,
|
|
paneCount: 1,
|
|
workerCount: 1,
|
|
workerStates: { running: 1 },
|
|
panes: [{
|
|
paneId: '%95',
|
|
windowIndex: 1,
|
|
paneIndex: 0,
|
|
title: 'seed-check',
|
|
currentCommand: 'codex',
|
|
currentPath: '/tmp/worktree',
|
|
active: false,
|
|
dead: false,
|
|
pid: 1234
|
|
}],
|
|
workers: [{
|
|
workerSlug: 'seed-check',
|
|
workerDir: '/tmp/.claude/orchestration/workflow-visual-proof/seed-check',
|
|
status: {
|
|
state: 'running',
|
|
updated: '2026-03-13T00:00:00Z',
|
|
branch: 'feature/seed-check',
|
|
worktree: '/tmp/worktree',
|
|
taskFile: '/tmp/task.md',
|
|
handoffFile: '/tmp/handoff.md'
|
|
},
|
|
task: {
|
|
objective: 'Inspect seeded files.',
|
|
seedPaths: ['scripts/orchestrate-worktrees.js']
|
|
},
|
|
handoff: {
|
|
summary: ['Pending'],
|
|
validation: [],
|
|
remainingRisks: ['No screenshot yet']
|
|
},
|
|
files: {
|
|
status: '/tmp/status.md',
|
|
task: '/tmp/task.md',
|
|
handoff: '/tmp/handoff.md'
|
|
},
|
|
pane: {
|
|
paneId: '%95',
|
|
title: 'seed-check'
|
|
}
|
|
}]
|
|
}),
|
|
recordingDir
|
|
});
|
|
|
|
const snapshot = adapter.open('workflow-visual-proof').getSnapshot();
|
|
const recordingPath = getFallbackSessionRecordingPath(snapshot, { recordingDir });
|
|
const persisted = JSON.parse(fs.readFileSync(recordingPath, 'utf8'));
|
|
|
|
assert.strictEqual(snapshot.schemaVersion, 'ecc.session.v1');
|
|
assert.strictEqual(snapshot.adapterId, 'dmux-tmux');
|
|
assert.strictEqual(snapshot.session.id, 'workflow-visual-proof');
|
|
assert.strictEqual(snapshot.session.kind, 'orchestrated');
|
|
assert.strictEqual(snapshot.session.state, 'active');
|
|
assert.strictEqual(snapshot.session.sourceTarget.type, 'session');
|
|
assert.strictEqual(snapshot.aggregates.workerCount, 1);
|
|
assert.strictEqual(snapshot.workers[0].runtime.kind, 'tmux-pane');
|
|
assert.strictEqual(snapshot.workers[0].outputs.remainingRisks[0], 'No screenshot yet');
|
|
assert.strictEqual(persisted.latest.session.state, 'active');
|
|
assert.strictEqual(persisted.latest.adapterId, 'dmux-tmux');
|
|
assert.strictEqual(persisted.history.length, 1);
|
|
} finally {
|
|
fs.rmSync(recordingDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('dmux adapter marks finished sessions as completed and records history', () => {
|
|
const recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-recordings-'));
|
|
|
|
try {
|
|
const adapter = createDmuxTmuxAdapter({
|
|
collectSessionSnapshotImpl: () => ({
|
|
sessionName: 'workflow-visual-proof',
|
|
coordinationDir: '/tmp/.claude/orchestration/workflow-visual-proof',
|
|
repoRoot: '/tmp/repo',
|
|
targetType: 'session',
|
|
sessionActive: false,
|
|
paneCount: 0,
|
|
workerCount: 2,
|
|
workerStates: { completed: 2 },
|
|
panes: [],
|
|
workers: [{
|
|
workerSlug: 'seed-check',
|
|
workerDir: '/tmp/.claude/orchestration/workflow-visual-proof/seed-check',
|
|
status: {
|
|
state: 'completed',
|
|
updated: '2026-03-13T00:00:00Z',
|
|
branch: 'feature/seed-check',
|
|
worktree: '/tmp/worktree-a',
|
|
taskFile: '/tmp/task-a.md',
|
|
handoffFile: '/tmp/handoff-a.md'
|
|
},
|
|
task: {
|
|
objective: 'Inspect seeded files.',
|
|
seedPaths: ['scripts/orchestrate-worktrees.js']
|
|
},
|
|
handoff: {
|
|
summary: ['Finished'],
|
|
validation: ['Reviewed outputs'],
|
|
remainingRisks: []
|
|
},
|
|
files: {
|
|
status: '/tmp/status-a.md',
|
|
task: '/tmp/task-a.md',
|
|
handoff: '/tmp/handoff-a.md'
|
|
},
|
|
pane: null
|
|
}, {
|
|
workerSlug: 'proof',
|
|
workerDir: '/tmp/.claude/orchestration/workflow-visual-proof/proof',
|
|
status: {
|
|
state: 'completed',
|
|
updated: '2026-03-13T00:10:00Z',
|
|
branch: 'feature/proof',
|
|
worktree: '/tmp/worktree-b',
|
|
taskFile: '/tmp/task-b.md',
|
|
handoffFile: '/tmp/handoff-b.md'
|
|
},
|
|
task: {
|
|
objective: 'Capture proof.',
|
|
seedPaths: ['README.md']
|
|
},
|
|
handoff: {
|
|
summary: ['Delivered proof'],
|
|
validation: ['Checked screenshots'],
|
|
remainingRisks: []
|
|
},
|
|
files: {
|
|
status: '/tmp/status-b.md',
|
|
task: '/tmp/task-b.md',
|
|
handoff: '/tmp/handoff-b.md'
|
|
},
|
|
pane: null
|
|
}]
|
|
}),
|
|
recordingDir
|
|
});
|
|
|
|
const snapshot = adapter.open('workflow-visual-proof').getSnapshot();
|
|
const recordingPath = getFallbackSessionRecordingPath(snapshot, { recordingDir });
|
|
const persisted = JSON.parse(fs.readFileSync(recordingPath, 'utf8'));
|
|
|
|
assert.strictEqual(snapshot.session.state, 'completed');
|
|
assert.strictEqual(snapshot.aggregates.states.completed, 2);
|
|
assert.strictEqual(persisted.latest.session.state, 'completed');
|
|
assert.strictEqual(persisted.history.length, 1);
|
|
} finally {
|
|
fs.rmSync(recordingDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('fallback recording does not append duplicate history entries for unchanged snapshots', () => {
|
|
const recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-recordings-'));
|
|
|
|
try {
|
|
const adapter = createDmuxTmuxAdapter({
|
|
collectSessionSnapshotImpl: () => ({
|
|
sessionName: 'workflow-visual-proof',
|
|
coordinationDir: '/tmp/.claude/orchestration/workflow-visual-proof',
|
|
repoRoot: '/tmp/repo',
|
|
targetType: 'session',
|
|
sessionActive: true,
|
|
paneCount: 1,
|
|
workerCount: 1,
|
|
workerStates: { running: 1 },
|
|
panes: [],
|
|
workers: [{
|
|
workerSlug: 'seed-check',
|
|
workerDir: '/tmp/.claude/orchestration/workflow-visual-proof/seed-check',
|
|
status: {
|
|
state: 'running',
|
|
updated: '2026-03-13T00:00:00Z',
|
|
branch: 'feature/seed-check',
|
|
worktree: '/tmp/worktree',
|
|
taskFile: '/tmp/task.md',
|
|
handoffFile: '/tmp/handoff.md'
|
|
},
|
|
task: {
|
|
objective: 'Inspect seeded files.',
|
|
seedPaths: ['scripts/orchestrate-worktrees.js']
|
|
},
|
|
handoff: {
|
|
summary: ['Pending'],
|
|
validation: [],
|
|
remainingRisks: []
|
|
},
|
|
files: {
|
|
status: '/tmp/status.md',
|
|
task: '/tmp/task.md',
|
|
handoff: '/tmp/handoff.md'
|
|
},
|
|
pane: null
|
|
}]
|
|
}),
|
|
recordingDir
|
|
});
|
|
|
|
const handle = adapter.open('workflow-visual-proof');
|
|
const firstSnapshot = handle.getSnapshot();
|
|
const secondSnapshot = handle.getSnapshot();
|
|
const recordingPath = getFallbackSessionRecordingPath(firstSnapshot, { recordingDir });
|
|
const persisted = JSON.parse(fs.readFileSync(recordingPath, 'utf8'));
|
|
|
|
assert.deepStrictEqual(secondSnapshot, firstSnapshot);
|
|
assert.strictEqual(persisted.history.length, 1);
|
|
assert.deepStrictEqual(persisted.latest, secondSnapshot);
|
|
} finally {
|
|
fs.rmSync(recordingDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('claude-history adapter loads the latest recorded session', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-adapter-home-'));
|
|
const recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-recordings-'));
|
|
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
|
|
const sessionPath = path.join(sessionsDir, '2026-03-13-a1b2c3d4-session.tmp');
|
|
fs.writeFileSync(sessionPath, [
|
|
'# Session Review',
|
|
'',
|
|
'**Date:** 2026-03-13',
|
|
'**Started:** 09:00',
|
|
'**Last Updated:** 11:30',
|
|
'**Project:** everything-claude-code',
|
|
'**Branch:** feat/session-adapter',
|
|
'**Worktree:** /tmp/ecc-worktree',
|
|
'',
|
|
'### Completed',
|
|
'- [x] Build snapshot prototype',
|
|
'',
|
|
'### In Progress',
|
|
'- [ ] Add CLI wrapper',
|
|
'',
|
|
'### Notes for Next Session',
|
|
'Need a second adapter.',
|
|
'',
|
|
'### Context to Load',
|
|
'```',
|
|
'scripts/lib/orchestration-session.js',
|
|
'```'
|
|
].join('\n'));
|
|
|
|
try {
|
|
withHome(homeDir, () => {
|
|
const adapter = createClaudeHistoryAdapter({ recordingDir });
|
|
const snapshot = adapter.open('claude:latest').getSnapshot();
|
|
const recordingPath = getFallbackSessionRecordingPath(snapshot, { recordingDir });
|
|
const persisted = JSON.parse(fs.readFileSync(recordingPath, 'utf8'));
|
|
|
|
assert.strictEqual(snapshot.schemaVersion, 'ecc.session.v1');
|
|
assert.strictEqual(snapshot.adapterId, 'claude-history');
|
|
assert.strictEqual(snapshot.session.kind, 'history');
|
|
assert.strictEqual(snapshot.session.state, 'recorded');
|
|
assert.strictEqual(snapshot.workers.length, 1);
|
|
assert.strictEqual(snapshot.workers[0].branch, 'feat/session-adapter');
|
|
assert.strictEqual(snapshot.workers[0].worktree, '/tmp/ecc-worktree');
|
|
assert.strictEqual(snapshot.workers[0].runtime.kind, 'claude-session');
|
|
assert.deepStrictEqual(snapshot.workers[0].intent.seedPaths, ['scripts/lib/orchestration-session.js']);
|
|
assert.strictEqual(snapshot.workers[0].artifacts.sessionFile, sessionPath);
|
|
assert.ok(snapshot.workers[0].outputs.summary.includes('Build snapshot prototype'));
|
|
assert.strictEqual(persisted.latest.adapterId, 'claude-history');
|
|
assert.strictEqual(persisted.history.length, 1);
|
|
});
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(recordingDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('adapter registry routes plan files to dmux and explicit claude targets to history', () => {
|
|
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-registry-repo-'));
|
|
const planPath = path.join(repoRoot, 'workflow.json');
|
|
fs.writeFileSync(planPath, JSON.stringify({
|
|
sessionName: 'workflow-visual-proof',
|
|
repoRoot,
|
|
coordinationRoot: path.join(repoRoot, '.claude', 'orchestration')
|
|
}));
|
|
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-registry-home-'));
|
|
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(sessionsDir, '2026-03-13-z9y8x7w6-session.tmp'),
|
|
'# History Session\n\n**Branch:** feat/history\n'
|
|
);
|
|
|
|
try {
|
|
withHome(homeDir, () => {
|
|
const registry = createAdapterRegistry({
|
|
adapters: [
|
|
createDmuxTmuxAdapter({
|
|
collectSessionSnapshotImpl: () => ({
|
|
sessionName: 'workflow-visual-proof',
|
|
coordinationDir: path.join(repoRoot, '.claude', 'orchestration', 'workflow-visual-proof'),
|
|
repoRoot,
|
|
targetType: 'plan',
|
|
sessionActive: false,
|
|
paneCount: 0,
|
|
workerCount: 0,
|
|
workerStates: {},
|
|
panes: [],
|
|
workers: []
|
|
})
|
|
}),
|
|
createClaudeHistoryAdapter()
|
|
]
|
|
});
|
|
|
|
const dmuxSnapshot = registry.open(planPath, { cwd: repoRoot }).getSnapshot();
|
|
const claudeSnapshot = registry.open('claude:latest', { cwd: repoRoot }).getSnapshot();
|
|
|
|
assert.strictEqual(dmuxSnapshot.adapterId, 'dmux-tmux');
|
|
assert.strictEqual(claudeSnapshot.adapterId, 'claude-history');
|
|
});
|
|
} finally {
|
|
fs.rmSync(repoRoot, { recursive: true, force: true });
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('adapter registry resolves structured target types into the correct adapter', () => {
|
|
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-typed-repo-'));
|
|
const planPath = path.join(repoRoot, 'workflow.json');
|
|
fs.writeFileSync(planPath, JSON.stringify({
|
|
sessionName: 'workflow-typed-proof',
|
|
repoRoot,
|
|
coordinationRoot: path.join(repoRoot, '.claude', 'orchestration')
|
|
}));
|
|
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-typed-home-'));
|
|
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(sessionsDir, '2026-03-13-z9y8x7w6-session.tmp'),
|
|
'# Typed History Session\n\n**Branch:** feat/typed-targets\n'
|
|
);
|
|
|
|
try {
|
|
withHome(homeDir, () => {
|
|
const registry = createAdapterRegistry({
|
|
adapters: [
|
|
createDmuxTmuxAdapter({
|
|
collectSessionSnapshotImpl: () => ({
|
|
sessionName: 'workflow-typed-proof',
|
|
coordinationDir: path.join(repoRoot, '.claude', 'orchestration', 'workflow-typed-proof'),
|
|
repoRoot,
|
|
targetType: 'plan',
|
|
sessionActive: true,
|
|
paneCount: 0,
|
|
workerCount: 0,
|
|
workerStates: {},
|
|
panes: [],
|
|
workers: []
|
|
})
|
|
}),
|
|
createClaudeHistoryAdapter()
|
|
]
|
|
});
|
|
|
|
const dmuxSnapshot = registry.open({ type: 'plan', value: planPath }, { cwd: repoRoot }).getSnapshot();
|
|
const claudeSnapshot = registry.open({ type: 'claude-history', value: 'latest' }, { cwd: repoRoot }).getSnapshot();
|
|
|
|
assert.strictEqual(dmuxSnapshot.adapterId, 'dmux-tmux');
|
|
assert.strictEqual(dmuxSnapshot.session.sourceTarget.type, 'plan');
|
|
assert.strictEqual(claudeSnapshot.adapterId, 'claude-history');
|
|
assert.strictEqual(claudeSnapshot.session.sourceTarget.type, 'claude-history');
|
|
assert.strictEqual(claudeSnapshot.workers[0].branch, 'feat/typed-targets');
|
|
});
|
|
} finally {
|
|
fs.rmSync(repoRoot, { recursive: true, force: true });
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('default registry forwards a nested state-store writer to adapters', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-registry-home-'));
|
|
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(sessionsDir, '2026-03-13-z9y8x7w6-session.tmp'),
|
|
'# History Session\n\n**Branch:** feat/history\n'
|
|
);
|
|
|
|
const stateStore = {
|
|
sessions: {
|
|
persisted: [],
|
|
persistCanonicalSessionSnapshot(snapshot, metadata) {
|
|
this.persisted.push({ snapshot, metadata });
|
|
}
|
|
}
|
|
};
|
|
|
|
try {
|
|
withHome(homeDir, () => {
|
|
const snapshot = inspectSessionTarget('claude:latest', {
|
|
cwd: process.cwd(),
|
|
stateStore
|
|
});
|
|
|
|
assert.strictEqual(snapshot.adapterId, 'claude-history');
|
|
assert.strictEqual(stateStore.sessions.persisted.length, 1);
|
|
assert.strictEqual(stateStore.sessions.persisted[0].snapshot.adapterId, 'claude-history');
|
|
assert.strictEqual(stateStore.sessions.persisted[0].metadata.sessionId, snapshot.session.id);
|
|
});
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('adapter registry lists adapter metadata and target types', () => {
|
|
const registry = createAdapterRegistry();
|
|
const adapters = registry.listAdapters();
|
|
const ids = adapters.map(adapter => adapter.id);
|
|
|
|
assert.ok(ids.includes('claude-history'));
|
|
assert.ok(ids.includes('dmux-tmux'));
|
|
assert.ok(
|
|
adapters.some(adapter => adapter.id === 'claude-history' && adapter.targetTypes.includes('claude-history')),
|
|
'claude-history should advertise its canonical target type'
|
|
);
|
|
assert.ok(
|
|
adapters.some(adapter => adapter.id === 'dmux-tmux' && adapter.targetTypes.includes('plan')),
|
|
'dmux-tmux should advertise plan targets'
|
|
);
|
|
});
|
|
|
|
test('persistence only falls back when the state-store module is missing', () => {
|
|
const snapshot = {
|
|
schemaVersion: 'ecc.session.v1',
|
|
adapterId: 'claude-history',
|
|
session: {
|
|
id: 'a1b2c3d4',
|
|
kind: 'history',
|
|
state: 'recorded',
|
|
repoRoot: null,
|
|
sourceTarget: {
|
|
type: 'claude-history',
|
|
value: 'latest'
|
|
}
|
|
},
|
|
workers: [{
|
|
id: 'a1b2c3d4',
|
|
label: 'Session Review',
|
|
state: 'recorded',
|
|
branch: null,
|
|
worktree: null,
|
|
runtime: {
|
|
kind: 'claude-session',
|
|
command: 'claude',
|
|
pid: null,
|
|
active: false,
|
|
dead: true
|
|
},
|
|
intent: {
|
|
objective: 'Session Review',
|
|
seedPaths: []
|
|
},
|
|
outputs: {
|
|
summary: [],
|
|
validation: [],
|
|
remainingRisks: []
|
|
},
|
|
artifacts: {
|
|
sessionFile: '/tmp/session.tmp',
|
|
context: null
|
|
}
|
|
}],
|
|
aggregates: {
|
|
workerCount: 1,
|
|
states: {
|
|
recorded: 1
|
|
}
|
|
}
|
|
};
|
|
|
|
const loadError = new Error('state-store bootstrap failed');
|
|
loadError.code = 'ERR_STATE_STORE_BOOT';
|
|
|
|
assert.throws(() => {
|
|
persistCanonicalSnapshot(snapshot, {
|
|
loadStateStoreImpl() {
|
|
throw loadError;
|
|
}
|
|
});
|
|
}, /state-store bootstrap failed/);
|
|
});
|
|
|
|
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`);
|
|
if (failed > 0) process.exit(1);
|