feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]
Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope. - git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved) - .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local - CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list - README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph - plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path - plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed) Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
This commit is contained in:
parent
8f1bf9b7b4
commit
7a90d348ad
149 changed files with 26 additions and 33 deletions
|
|
@ -1,125 +0,0 @@
|
|||
// tests/hooks/post-compact-flush.test.mjs
|
||||
// Step 13 (plan-v2) — PostCompact rehydrate hook test.
|
||||
//
|
||||
// Hook is read-only: discovers <cwd>/.claude/projects/*/.session-state.local.json,
|
||||
// validates it, emits additionalContext for the post-compact assistant turn.
|
||||
// Must always exit 0 — never blocks compaction.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { execFile } from 'node:child_process';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const HOOK = join(ROOT, 'hooks', 'scripts', 'post-compact-flush.mjs');
|
||||
|
||||
function runHookIn(cwd, input = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const child = execFile(
|
||||
'node',
|
||||
[HOOK],
|
||||
{ timeout: 5000, cwd, env: { ...process.env } },
|
||||
(err, stdout, stderr) => {
|
||||
resolve({
|
||||
code: child.exitCode ?? 0,
|
||||
stdout: stdout || '',
|
||||
stderr: stderr || '',
|
||||
});
|
||||
},
|
||||
);
|
||||
child.stdin.end(typeof input === 'string' ? input : JSON.stringify(input));
|
||||
});
|
||||
}
|
||||
|
||||
function makeFixture() {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'post-compact-flush-'));
|
||||
return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
|
||||
}
|
||||
|
||||
test('post-compact-flush: exits 0 with empty output when no .claude/projects/ exists', async () => {
|
||||
const { dir, cleanup } = makeFixture();
|
||||
try {
|
||||
const { code, stdout } = await runHookIn(dir);
|
||||
assert.strictEqual(code, 0, 'hook must always exit 0 — never blocks compaction');
|
||||
assert.strictEqual(stdout, '{}', 'no state file → emit empty payload (silent no-op)');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('post-compact-flush: exits 0 with empty output when state file is malformed', async () => {
|
||||
const { dir, cleanup } = makeFixture();
|
||||
try {
|
||||
mkdirSync(join(dir, '.claude/projects/2026-05-04-test'), { recursive: true });
|
||||
writeFileSync(
|
||||
join(dir, '.claude/projects/2026-05-04-test/.session-state.local.json'),
|
||||
'{not valid json',
|
||||
);
|
||||
const { code, stdout } = await runHookIn(dir);
|
||||
assert.strictEqual(code, 0, 'malformed state file → silent fail, exit 0');
|
||||
assert.strictEqual(stdout, '{}', 'no additionalContext on malformed input');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('post-compact-flush: emits additionalContext with project + next_session_label + status from valid state file', async () => {
|
||||
const { dir, cleanup } = makeFixture();
|
||||
try {
|
||||
mkdirSync(join(dir, '.claude/projects/2026-05-04-test'), { recursive: true });
|
||||
const state = {
|
||||
schema_version: 1,
|
||||
project: '.claude/projects/2026-05-04-test',
|
||||
next_session_brief_path: '.claude/projects/2026-05-04-test/brief.md',
|
||||
next_session_label: 'Session 9: Wave 2 manual delivery',
|
||||
status: 'in_progress',
|
||||
updated_at: '2026-05-04T07:00:00.000Z',
|
||||
};
|
||||
writeFileSync(
|
||||
join(dir, '.claude/projects/2026-05-04-test/.session-state.local.json'),
|
||||
JSON.stringify(state, null, 2),
|
||||
);
|
||||
const { code, stdout } = await runHookIn(dir);
|
||||
assert.strictEqual(code, 0, 'valid state → exit 0');
|
||||
const parsed = JSON.parse(stdout);
|
||||
assert.ok(parsed.additionalContext, 'must emit additionalContext for the next turn');
|
||||
assert.match(parsed.additionalContext, /\.claude\/projects\/2026-05-04-test/, 'context includes project path');
|
||||
assert.match(parsed.additionalContext, /Session 9: Wave 2 manual delivery/, 'context includes next_session_label');
|
||||
assert.match(parsed.additionalContext, /status: in_progress/, 'context includes status');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('post-compact-flush: picks the most-recently-modified state file when multiple projects exist', async () => {
|
||||
const { dir, cleanup } = makeFixture();
|
||||
try {
|
||||
mkdirSync(join(dir, '.claude/projects/older'), { recursive: true });
|
||||
mkdirSync(join(dir, '.claude/projects/newer'), { recursive: true });
|
||||
const baseState = (label) => ({
|
||||
schema_version: 1,
|
||||
project: `.claude/projects/${label}`,
|
||||
next_session_brief_path: `.claude/projects/${label}/brief.md`,
|
||||
next_session_label: `Label-${label}`,
|
||||
status: 'in_progress',
|
||||
updated_at: '2026-05-04T07:00:00.000Z',
|
||||
});
|
||||
const olderPath = join(dir, '.claude/projects/older/.session-state.local.json');
|
||||
const newerPath = join(dir, '.claude/projects/newer/.session-state.local.json');
|
||||
writeFileSync(olderPath, JSON.stringify(baseState('older')));
|
||||
// Wait one tick to ensure mtime ordering is observable on all filesystems
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
writeFileSync(newerPath, JSON.stringify(baseState('newer')));
|
||||
const { code, stdout } = await runHookIn(dir);
|
||||
assert.strictEqual(code, 0);
|
||||
const parsed = JSON.parse(stdout);
|
||||
assert.match(parsed.additionalContext, /Label-newer/, 'auto-discovery should pick the newest state file');
|
||||
assert.doesNotMatch(parsed.additionalContext, /Label-older/, 'older state file must not be selected');
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue