feat(ultraplan-local): Bug 4 — wire --cleanup into /ultracontinue-local [skip-docs]
Step 10 of v3.4.1 plan. commands/ultracontinue-local.md: - New Phase 0.5 between Phase 0 and Phase 1 — terminal cleanup mode triggered by parsed flags['--cleanup'] === true. Requires explicit positional[0] (no "clean all"), no template placeholders in the Bash invocation. Passes through to cleanupProject via inline ESM. Cleanup never falls through to Phase 1/2/3/4. - Phase 0 usage block updated to document --cleanup and --cleanup --confirm forms alongside the legacy <project-dir> form. tests/commands/ultracontinue.test.mjs: - Test (Bug 4 prose) — Phase 0.5 header present, references cleanupProject and flags['--cleanup'], appears between Phase 0 and Phase 1 in document order, usage mentions --cleanup --confirm. - Test (f-1) dry-run on completed project lists candidates without deleting; both files still on disk. - Test (f-2 + f-3) confirm-mode deletes both files; subsequent invocation on the already-cleaned dir signals CLEANUP_NO_STATE_FILE (deterministic terminal state, idempotent for operators). Tests 355 -> 358 (+3). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
7ffaa82207
commit
9fa83bdf2f
2 changed files with 133 additions and 3 deletions
|
|
@ -267,3 +267,85 @@ test('ultracontinue Bug 2 — Phase 2 contains no {state-file-path} or any {curl
|
|||
'Phase 2 must document the deterministic Read tool flow',
|
||||
);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Step 10 — Bug 4 regression tests (Phase 0.5 wire-up + cleanup f-1/f-2/f-3)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
test('ultracontinue Bug 4 — Phase 0.5 documents cleanup mode dispatch', () => {
|
||||
const cmd = readCommand();
|
||||
assert.match(cmd, /## Phase 0\.5 /, 'Phase 0.5 header must be present');
|
||||
// Phase 0.5 must come BETWEEN Phase 0 and Phase 1.
|
||||
const idx05 = cmd.indexOf('## Phase 0.5 ');
|
||||
const idx1 = cmd.indexOf('## Phase 1 ');
|
||||
assert.ok(idx05 !== -1 && idx1 !== -1 && idx05 < idx1,
|
||||
'Phase 0.5 must appear before Phase 1');
|
||||
// Must reference cleanupProject and parsed flags['--cleanup'].
|
||||
const phase05 = extractPhase(cmd, '## Phase 0.5 ');
|
||||
assert.match(phase05, /cleanupProject/, 'Phase 0.5 must invoke cleanupProject');
|
||||
assert.match(phase05, /flags\['--cleanup'\]/, "Phase 0.5 must dispatch via flags['--cleanup']");
|
||||
// Usage block must document both forms.
|
||||
assert.match(cmd, /--cleanup --confirm/, 'usage must mention --cleanup --confirm');
|
||||
});
|
||||
|
||||
test('ultracontinue Bug 4 (f-1) dry-run lists candidates without deleting', async () => {
|
||||
const { cleanupProject } = await import('../../lib/util/cleanup.mjs');
|
||||
const root = mkdtempSync(join(tmpdir(), 'ultracontinue-cleanup-'));
|
||||
try {
|
||||
const dir = join(root, 'project-completed');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, '.session-state.local.json'), JSON.stringify({
|
||||
schema_version: 1,
|
||||
project: dir,
|
||||
next_session_brief_path: join(dir, 'brief.md'),
|
||||
next_session_label: 'Done',
|
||||
status: 'completed',
|
||||
updated_at: '2026-05-04T16:00:00.000Z',
|
||||
}, null, 2));
|
||||
writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'),
|
||||
'---\nproduced_by: ultraexecute-local\nproduced_at: 2026-05-04T16:00:00.000Z\n---\n\n# Done\n');
|
||||
const r = cleanupProject(dir, { dryRun: true });
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
assert.equal(r.parsed.wouldDelete.length, 2);
|
||||
assert.equal(readFileSync(join(dir, '.session-state.local.json'), 'utf8').length > 0, true);
|
||||
assert.equal(readFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'), 'utf8').length > 0, true);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('ultracontinue Bug 4 (f-2) confirm deletes and (f-3) idempotent re-run handles already-clean dir', async () => {
|
||||
const { cleanupProject } = await import('../../lib/util/cleanup.mjs');
|
||||
const { existsSync } = await import('node:fs');
|
||||
const root = mkdtempSync(join(tmpdir(), 'ultracontinue-cleanup-'));
|
||||
try {
|
||||
const dir = join(root, 'project-completed');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, '.session-state.local.json'), JSON.stringify({
|
||||
schema_version: 1,
|
||||
project: dir,
|
||||
next_session_brief_path: join(dir, 'brief.md'),
|
||||
next_session_label: 'Done',
|
||||
status: 'completed',
|
||||
updated_at: '2026-05-04T16:00:00.000Z',
|
||||
}, null, 2));
|
||||
writeFileSync(join(dir, 'NEXT-SESSION-PROMPT.local.md'),
|
||||
'---\nproduced_by: ultraexecute-local\nproduced_at: 2026-05-04T16:00:00.000Z\n---\n\n# Done\n');
|
||||
|
||||
// f-2: confirm deletes
|
||||
const r2 = cleanupProject(dir, { dryRun: false, confirm: true });
|
||||
assert.equal(r2.valid, true, JSON.stringify(r2.errors));
|
||||
assert.equal(r2.parsed.deleted.length, 2);
|
||||
assert.equal(existsSync(join(dir, '.session-state.local.json')), false);
|
||||
assert.equal(existsSync(join(dir, 'NEXT-SESSION-PROMPT.local.md')), false);
|
||||
|
||||
// f-3: idempotent re-run on a fully-cleaned dir reports CLEANUP_NO_STATE_FILE
|
||||
// (no state file → nothing to clean) — a deterministic terminal signal,
|
||||
// not a crash. Operators can ignore it.
|
||||
const r3 = cleanupProject(dir, { dryRun: false, confirm: true });
|
||||
assert.equal(r3.valid, false);
|
||||
assert.ok(r3.errors.find(e => e.code === 'CLEANUP_NO_STATE_FILE'));
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue