fix(lanes): clear stale detection on real stage transitions, exclude scratch dirs from implement detect
setStage() left detected_stage/signal/at untouched across a real transition, so a prior task's leftover inference (e.g. `tests` from earlier work) both misrepresented a fresh task's progress and — because recordDetection is forward-only — silently rejected every real detection behind it until the old one aged past DETECTION_TTL_MS. A real stage change now clears the detection columns; a same-stage heartbeat leaves a live detection alone. Also excludes `.superpowers/` from the `implement` node's Write detect rule - brainstorm-companion scratch files were being counted as implementation work. fix(client): disable Node's default --experimental-webstorage in tests Node 25 enables --experimental-webstorage by default, which defines a broken global `localStorage` (no backing file configured) ahead of jsdom's own full polyfill. Every test touching real localStorage failed with "localStorage.clear/getItem is not a function" — not flaky, not test-specific, just this one Node default. NODE_OPTIONS=--no-experimental-webstorage on the test scripts lets jsdom's polyfill take over as before.
This commit is contained in:
@@ -587,6 +587,39 @@ describe("stage detection", () => {
|
||||
lanes.deleteLane(l.id);
|
||||
});
|
||||
|
||||
it("a real stage transition clears a stale detection and unblocks a fresh one behind it", () => {
|
||||
const l = lanes.createLane({ cwd: "/tmp/wt-detect-stale-cross-task" });
|
||||
// A prior, unrelated task's tool calls left `tests` as the detected stage.
|
||||
lanes.recordDetection(l.id, { nodeId: "tests", signal: "`npm test`" });
|
||||
assert.equal(lanes.getLane(l.id).detected_stage, "tests");
|
||||
|
||||
// A new task starts and the agent declares an EARLIER stage. Without the
|
||||
// fix, the leftover `tests` detection both misrepresents the new task's
|
||||
// progress and (via forward-only) rejects every real detection for it
|
||||
// until `tests` ages past DETECTION_TTL_MS.
|
||||
const after = lanes.setStage(l.id, { stage: "plan" });
|
||||
assert.equal(after.detected_stage, null);
|
||||
assert.equal(after.detected_signal, null);
|
||||
assert.equal(after.detected_at, null);
|
||||
|
||||
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "implement", signal: "`Edit`" }), {
|
||||
written: true,
|
||||
});
|
||||
assert.equal(lanes.getLane(l.id).detected_stage, "implement");
|
||||
lanes.deleteLane(l.id);
|
||||
});
|
||||
|
||||
it("re-declaring the SAME stage (a heartbeat) leaves a live detection alone", () => {
|
||||
const l = lanes.createLane({ cwd: "/tmp/wt-detect-heartbeat-noop" });
|
||||
lanes.setStage(l.id, { stage: "plan" });
|
||||
assert.deepEqual(lanes.recordDetection(l.id, { nodeId: "implement", signal: "`Edit`" }), {
|
||||
written: true,
|
||||
});
|
||||
lanes.setStage(l.id, { stage: "plan", note: "still planning" });
|
||||
assert.equal(lanes.getLane(l.id).detected_stage, "implement");
|
||||
lanes.deleteLane(l.id);
|
||||
});
|
||||
|
||||
it("migration: detection columns are added to a database holding an old-schema lanes row", () => {
|
||||
const tmpPath = pathMod.join(
|
||||
os.tmpdir(),
|
||||
@@ -744,12 +777,18 @@ describe("stage detection", () => {
|
||||
it("detection expiry: stale detection behind declared stage still rejects", () => {
|
||||
const { db } = require("../db");
|
||||
const l = lanes.createLane({ cwd: "/tmp/wt-detect-expiry-declared" });
|
||||
// Stand up the detection BEFORE declaring, otherwise declared-wins refuses
|
||||
// it and there is no standing detection left to age.
|
||||
lanes.recordDetection(l.id, { nodeId: "tests" });
|
||||
lanes.setStage(l.id, { stage: "review" });
|
||||
// setStage now clears detected_* on every real transition (a fresh
|
||||
// declaration supersedes older inference), so a standing detection is
|
||||
// written directly here, bypassing that, to isolate the rule this test
|
||||
// is actually about: declared-wins is never relaxed by staleness, no
|
||||
// matter how the stale detection got there.
|
||||
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
|
||||
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(oldTime, l.id);
|
||||
db.prepare("UPDATE lanes SET detected_stage = ?, detected_at = ? WHERE id = ?").run(
|
||||
"tests",
|
||||
oldTime,
|
||||
l.id
|
||||
);
|
||||
|
||||
// Try backward detection to implement - should still be rejected because declared stage is ahead
|
||||
const result = lanes.recordDetection(l.id, { nodeId: "implement" });
|
||||
|
||||
Reference in New Issue
Block a user