// SPDX-License-Identifier: MIT // Part of pi-steering. import assert from "node:assert/strict"; import { describe, it } from "node:test"; import type { Observer, Rule } from "../schema.ts"; import { dropUnusedObservers } from "./drop-unused-observers.ts"; /** Minimal Observer for tests. */ function mkObserver(name: string, writes?: readonly string[]): Observer { const base = { name, watch: { toolName: "bash" as const }, onResult: () => {}, } satisfies Observer; return writes === undefined ? base : { ...base, writes }; } /** Minimal Rule with a specific `happened` shape. */ function mkRuleWithHappened(name: string, event: string, since?: string): Rule { return { name, tool: "bash", field: "command", pattern: /^x/, when: { happened: since ? { event, in: "agent_loop", since } : { event, in: "agent_loop" }, }, reason: "test", }; } /** Rule with no happened at all. */ function mkRuleNoHappened(name: string): Rule { return { name, tool: "bash", field: "command", pattern: /^x/, reason: "test", }; } describe("dropUnusedObservers", () => { it("keeps observer whose write is consumed by a rule's happened.event", () => { const observers = [mkObserver("a", ["X"])]; const rules = [mkRuleWithHappened("r1", "X")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 1); assert.equal(kept[0]?.name, "a"); assert.equal(dropped.length, 0); }); it("drops observer whose writes are not consumed", () => { const observers = [mkObserver("a", ["X"])]; const rules = [mkRuleNoHappened("r1")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 0); assert.equal(dropped.length, 1); assert.equal(dropped[0]?.name, "a"); assert.deepEqual(dropped[0]?.writes, ["X"]); assert.equal(dropped[0]?.reason, "unused-writes"); }); it("keeps observer when at least one of its writes is consumed", () => { const observers = [mkObserver("a", ["X", "Y"])]; const rules = [mkRuleWithHappened("r1", "X")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 1); assert.equal(dropped.length, 0); }); it("keeps observer with `writes: undefined` (conservative)", () => { const observers = [mkObserver("a", undefined)]; const rules = [mkRuleNoHappened("r1")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 1); assert.equal(dropped.length, 0); }); it("keeps observer with empty `writes: []` (conservative parity)", () => { const observers = [mkObserver("a", [])]; const rules = [mkRuleNoHappened("r1")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 1); assert.equal(dropped.length, 0); }); it("keeps observer whose write is consumed only by happened.since", () => { const observers = [mkObserver("a", ["X"])]; const rules = [mkRuleWithHappened("r1", "Y", /* since */ "X")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 1); assert.equal(dropped.length, 0); }); it("partitions a mixed observer set correctly", () => { const observers = [ mkObserver("consumed", ["X"]), mkObserver("unused", ["Z"]), mkObserver("undeclared"), ]; const rules = [mkRuleWithHappened("r1", "X")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.deepEqual(kept.map((o) => o.name).sort(), [ "consumed", "undeclared", ]); assert.deepEqual( dropped.map((d) => d.name), ["unused"], ); }); it("drops everything when no rule uses `when.happened`", () => { const observers = [mkObserver("a", ["X"]), mkObserver("b", ["Y", "Z"])]; const rules = [mkRuleNoHappened("r1"), mkRuleNoHappened("r2")]; const { kept, dropped } = dropUnusedObservers(observers, rules); assert.equal(kept.length, 0); assert.equal(dropped.length, 2); }); it("returns empty arrays for empty inputs", () => { const { kept, dropped } = dropUnusedObservers([], []); assert.equal(kept.length, 0); assert.equal(dropped.length, 0); }); it("honors caller-applied rule filtering (disabledRules)", () => { // Caller is responsible for filtering disabled rules before // passing to this helper — demonstrate that a rule NOT in the // input list does NOT cause its observer writes to stay alive. const observers = [mkObserver("a", ["X"])]; const rulesAfterDisable: Rule[] = []; // r1 (consumer) disabled → filtered out const { kept, dropped } = dropUnusedObservers(observers, rulesAfterDisable); assert.equal(kept.length, 0); assert.equal(dropped.length, 1); }); });