* test(#4699): add failing-first coverage for skipping complete phases in next_phase * fix(#4699): skip already-complete phases in the next_phase cascade Both next-phase scans selected the numerically lowest phase above N without consulting completion state, so completing a reopened phase persisted an already-[x] phase as STATE.md current_phase while roadmap.analyze correctly named the outstanding one (issue repro: completing 2 with phases 1 and 3 already [x] returned next_phase 03). The cascade collects the complete phase numbers from the roadmap checkboxes (milestone-scoped, comparePhaseNum-deduped) and skips them in both the disk scan and the roadmap scan; a [x] checkbox row and its heading sibling both name a phase that is never next. Heading-only and checkbox-less roadmaps behave exactly as before. * test(#4699): align the negative-control expectation with the disk spelling * test(#4699): pin the STATE.md persistence and the all-later-complete tail corner Review findings: the regression never asserted STATE.md current_phase (the issue's actual harm), and the all-later-phases-[x] corner (is_last_phase true, next_phase null) was unpinned. A changeset fragment is included. * docs(#4699): backfill changeset PR number * test(#4700): add failing-first coverage for the queued headless mine * fix(#4700): queue the headless mempalace mine and surface skipped captures The capture's mine ran in the foreground with no lock handling: MemPalace wraps every mine in a per-palace lock, so any concurrent writer (two phases finishing a stage at once, a git-hook refresh mining the same palace) made it exit 1 (MineAlreadyRunning) and the onError: skip step silently dropped the capture — unlost for CONTEXT/PLAN/SUMMARY files that can be re-filed, unrecoverable for execute:wave:post problem-fix pairs. The mine now queues via --daemon --background (MemPalace #2029: the daemon holds a job refused the lock and runs it when the holder exits), and the report step gains the queued and skipped outcomes per #4700's requirement that a skipped capture never stay silent. Option 2 (write_routing.cli) is unreleased at MemPalace 3.9.0; option 3 (retry) re-enters the same lock race — both declined in the PR body. * fix(#4700): queue the wave:post problems fragment's headless mine too The issue names the execute:wave:post problem-fix pair as the unrecoverable loss (no source file to re-file later); the capture-problems fragment's headless mine ran foreground like the capture capability's did. Same fix: --daemon --background, with the lock-deferral rationale inline. * docs(#4700): backfill changeset PR number * fix(#4682): register the stale-reverification part in the capability registry The new steps/ part is a shipped workflow file; gen-capability-registry --check requires it in the committed registry. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -22,6 +22,8 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const { splitLines } = require('../gsd-core/bin/lib/text-lines.cjs');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
const SKILL_FILES = [
|
||||
@@ -148,3 +150,53 @@ describe('#2414 — rooms: entries are dicts with a name key (not bare strings)'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ── #4700 — the headless mine must queue on the palace lock, and a skipped
|
||||
// capture must be visible in the report. MemPalace wraps every mine in a
|
||||
// per-palace lock; a concurrent writer makes a plain foreground `mine` exit 1
|
||||
// (MineAlreadyRunning) and the capability's onError: skip silently dropped the
|
||||
// capture. The shipped command queues via --daemon --background (MemPalace
|
||||
// #2029), and the report step names the queued/skipped outcome.
|
||||
|
||||
describe('#4700 — headless mine queues on the palace lock', () => {
|
||||
const SURFACES = [
|
||||
'commands/gsd/mempalace-capture.md',
|
||||
'skills/gsd-mempalace-capture/SKILL.md',
|
||||
];
|
||||
|
||||
test('every mempalace mine command line queues via --daemon --background (#4700)', () => {
|
||||
for (const rel of SURFACES) {
|
||||
const lines = splitLines(fs.readFileSync(path.join(ROOT, rel), 'utf8'));
|
||||
let sawMine = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (!MINE_CMD_RE.test(line)) continue;
|
||||
sawMine = true;
|
||||
assert.match(
|
||||
line, /--daemon\s+--background/,
|
||||
`${rel}:${i + 1}: "mempalace mine" must queue via --daemon --background so a held palace lock defers the write instead of dropping it (#4700). Offending line: ${line.trim()}`,
|
||||
);
|
||||
}
|
||||
assert.ok(sawMine, `${rel}: expected at least one mempalace mine command line`);
|
||||
}
|
||||
});
|
||||
|
||||
test('the wave:post problems fragment queues its headless mine too (#4700)', () => {
|
||||
const frag = 'capabilities/mempalace/fragments/capture-problems.md';
|
||||
const content = fs.readFileSync(path.join(ROOT, frag), 'utf8');
|
||||
assert.match(
|
||||
content, /mempalace mine --daemon --background/,
|
||||
`${frag}: the headless mine must queue via --daemon --background — the issue names the execute:wave:post problem-fix pair as the unrecoverable loss (#4700)`,
|
||||
);
|
||||
});
|
||||
|
||||
test('the report step names queued and skipped captures (#4700)', () => {
|
||||
for (const rel of SURFACES) {
|
||||
const content = fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
||||
assert.match(content, /queued/,
|
||||
`${rel}: the report step must surface the queued outcome (#4700)`);
|
||||
assert.match(content, /skipped/i,
|
||||
`${rel}: the report step must name skipped captures rather than staying silent (#4700)`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user