Skip to content

Commit ebb5630

Browse files
lukstaficlaude
andcommitted
fix(mag): briefing queues /compact after feedback-digest (task-304a02a6)
Swap order in magBriefing() so the on-disk queue becomes briefing → feedback-digest → /compact. /compact had been wiping the briefing context before feedback-digest could read it; now /compact lands last so digest sees prior context and the next session starts fresh. Comment block above /compact rewritten to drop the now-false "directly behind" rationale; task-a00fc0d9 reference preserved. Test renamed to assert the new invariant (last item is /compact, middle item is feedback-digest in clean state) and a sibling test covers the gated branch (length 2 with /compact still last). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 778d7c5 commit ebb5630

2 files changed

Lines changed: 47 additions & 11 deletions

File tree

src/mag-auto-compact.test.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2-
import { mkdirSync, readFileSync } from "fs";
2+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
33
import { join } from "path";
44
import { withSyntheticHarness } from "./test-utils.ts";
55

@@ -27,16 +27,49 @@ function readQueue(): Record<string, unknown>[] {
2727
}
2828

2929
describe("magBriefing auto-compact follow-up", () => {
30-
test("enqueues /compact directly behind the briefing entry", async () => {
30+
test("enqueues /compact as the final item, with feedback-digest between briefing and /compact", async () => {
31+
// Harness condition: clean state — no pre-existing pending feedback-digest
32+
// in queue.jsonl, no cooldown state file. The synthetic harness creates a
33+
// fresh tmp dir each test, so both gates open and tryQueueFeedbackDigest
34+
// actually enqueues. If that condition stops holding, items[1] would not be
35+
// "feedback-digest" and the middle-slot assertion would fail loudly.
3136
const { magBriefing } = await import("./mag.ts");
3237
magBriefing(false);
3338

3439
const items = readQueue();
35-
// Must be at least: briefing, /compact (feedback-digest may follow).
36-
expect(items.length).toBeGreaterThanOrEqual(2);
40+
// briefing → feedback-digest → /compact. /compact must always land last
41+
// (the AC's invariant); feedback-digest in the middle is the ungated path.
42+
expect(items).toHaveLength(3);
3743
expect(items[0]!.action).toBe("briefing");
38-
expect(items[1]!.action).toBe("message");
39-
expect(items[1]!.content).toBe("/compact");
44+
expect(items[1]!.action).toBe("feedback-digest");
45+
expect(items[items.length - 1]!.action).toBe("message");
46+
expect(items[items.length - 1]!.content).toBe("/compact");
47+
});
48+
49+
test("when feedback-digest is gated, queue is briefing → /compact (length 2)", async () => {
50+
// Harness condition: pre-seed queue.jsonl with a pending feedback-digest
51+
// entry for "ludics" so queueHasPendingFeedbackDigest() returns true and
52+
// tryQueueFeedbackDigest short-circuits with { queued: false }. Without
53+
// this seed, digest would fire and the length-2 assertion would fail.
54+
const qf = join(getTmpDir(), "mag", "queue.jsonl");
55+
writeFileSync(
56+
qf,
57+
JSON.stringify({ id: "seed", action: "feedback-digest", repo: "ludics" }) + "\n",
58+
);
59+
60+
const { magBriefing } = await import("./mag.ts");
61+
magBriefing(false);
62+
63+
const items = readQueue();
64+
// Drop the pre-seeded sentinel; only assert on what magBriefing wrote.
65+
const written = items.slice(1);
66+
expect(written).toHaveLength(2);
67+
expect(written[0]!.action).toBe("briefing");
68+
expect(written[1]!.action).toBe("message");
69+
expect(written[1]!.content).toBe("/compact");
70+
// /compact must be last regardless of digest gating.
71+
expect(items[items.length - 1]!.action).toBe("message");
72+
expect(items[items.length - 1]!.content).toBe("/compact");
4073
});
4174
});
4275

src/mag.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,17 +3314,20 @@ export function magBriefing(wait: boolean = true, timeout: number = 300): void {
33143314
const requestId = queueRequest({ action: "briefing" });
33153315
console.log(`Queued briefing request: ${requestId}`);
33163316

3317-
// Auto-compact after briefing — checkpoint compaction (task-a00fc0d9 /
3318-
// docs/proposals/auto-compact-after-checkpoints.md). Enqueued directly
3319-
// behind the briefing so it lands before any later automated enqueues.
3320-
queueRequest({ action: "message", content: "/compact" });
3321-
33223317
// Auto-queue feedback-digest once daily alongside the briefing trigger.
3318+
// Lands before /compact so digest can consume the briefing's in-flight
3319+
// context (task-304a02a6 / docs/proposals/task-304a02a6-reorder-briefing-auto-queue.md).
33233320
const fdResult = tryQueueFeedbackDigest("ludics");
33243321
if (fdResult.queued) {
33253322
console.error("ludics: briefing queued feedback-digest for ludics");
33263323
}
33273324

3325+
// Auto-compact after briefing — checkpoint compaction (task-a00fc0d9 /
3326+
// docs/proposals/auto-compact-after-checkpoints.md). Enqueued unconditionally
3327+
// as the LAST follow-up so the next session starts fresh, after
3328+
// feedback-digest has had a chance to consume the briefing context.
3329+
queueRequest({ action: "message", content: "/compact" });
3330+
33283331
if (!wait) {
33293332
console.log("Mag will process when ready");
33303333
return;

0 commit comments

Comments
 (0)