feat: export continuity plugin MVP packaging

This commit is contained in:
2026-04-24 17:33:01 +08:00
parent cb34935b28
commit 7d62b1b84e
23 changed files with 1664 additions and 2 deletions

View File

@@ -51,6 +51,10 @@ async function prepareTempWorkspace() {
await fs.mkdir(path.join(tempWorkspace, 'hooks', 'force-recall'), { recursive: true });
await fs.mkdir(path.join(tempWorkspace, 'docs'), { recursive: true });
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'adapters'), { recursive: true });
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config'), { recursive: true });
await fs.mkdir(path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity'), { recursive: true });
const copies = [
[wrapperPath, path.join(tempWorkspace, 'scripts', 'long_task_governor_wrapper.mjs')],
[gateLockPath, path.join(tempWorkspace, 'scripts', 'long_task_gate_lock.mjs')],
@@ -59,6 +63,13 @@ async function prepareTempWorkspace() {
[handlerPath, path.join(tempWorkspace, 'hooks', 'force-recall', 'handler.ts')],
[path.join(repoRoot, 'docs', 'RULEBOOK.md'), path.join(tempWorkspace, 'docs', 'RULEBOOK.md')],
[path.join(repoRoot, 'SOUL.md'), path.join(tempWorkspace, 'SOUL.md')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'index.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'index.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'adapters', 'force-recall.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'adapters', 'force-recall.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'config', 'schema.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'config', 'schema.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'evaluator.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'evaluator.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'receipt-validator.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'receipt-validator.mjs')],
[path.join(repoRoot, 'plugins', 'continuity', 'src', 'continuity', 'receipt-store.mjs'), path.join(tempWorkspace, 'plugins', 'continuity', 'src', 'continuity', 'receipt-store.mjs')],
];
for (const [src, dest] of copies) {
@@ -285,6 +296,49 @@ async function main() {
assert.equal(neutralSnakeCaseResult.gateStatus, 'pass', 'neutral snake_case non-dispatch action should not trigger dispatch-evidence requirement');
assert.doesNotMatch(JSON.stringify(neutralSnakeCaseResult), /autoChainDispatchEvidence/, 'neutral snake_case non-dispatch action should not mention dispatch-evidence requirement');
const pluginPathInjected = await withPatchedWrapperWorkspace({
classification: 'long_task',
silentCandidate: true,
needsCheckpoint: true,
needsSubagent: false,
needsOwnerDecision: false,
silentLaunchOk: true,
planId: 'plan-plugin-path',
currentTask: 'task-plugin-path',
taskState: 'complete',
replyClosureState: 'completed',
requiredNextAction: 'dispatch_follow_up_subagent',
autoChainDispatchEvidence: {
action: 'dispatch_follow_up_subagent',
dispatched: true,
event: 'dispatch',
},
progressEvidence: { sessionKey: 'task-plugin-path' },
externalizedCheckpointPath: 'checkpoints/task-plugin-path.json',
handoff: { mode: 'direct_reply' },
dispatchReceipt: {
planId: 'plan-plugin-path',
currentTask: 'task-plugin-path',
nextDerivedAction: {
type: 'dry_run_dispatch',
action: 'dispatch_spec_review',
},
dispatchedAt: '2026-04-24T17:00:00+08:00',
},
}, async (workspaceDir) => {
const defaultsPath = path.join(workspaceDir, 'plugins', 'continuity', 'src', 'config', 'defaults.mjs');
const defaultsSource = await fs.readFile(defaultsPath, 'utf8');
await fs.writeFile(
defaultsPath,
defaultsSource.replace('APPROVED_PLAN_CONTINUITY_GATE', 'PLUGIN_CONTINUITY_GATE'),
'utf8',
);
return runScenario(forceRecall, requestText, workspaceDir);
});
assert.match(pluginPathInjected, /\[PLUGIN_CONTINUITY_GATE\]/, 'hook should inject continuity block from plugin adapter path, not only local fallback builder');
assert.match(pluginPathInjected, /status=pass/, 'plugin adapter path should still pass when a bound dispatch receipt exists');
assert.doesNotMatch(pluginPathInjected, /\[APPROVED_PLAN_CONTINUITY_GATE\]/, 'plugin adapter label override should replace the legacy fallback block label when plugin path is active');
const passInjected = await withPatchedWrapperWorkspace({
classification: 'long_task',
silentCandidate: true,