Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/daemon/runtime/codebase/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,21 @@ function parsePath(path: string): { command: string; remainder: string } {
const cleaned = path.replace(/^\/+/, "").replace(/^graph\//, "").replace(/\.md$/, "");
const slash = cleaned.indexOf("/");
if (slash < 0) return { command: cleaned.toLowerCase(), remainder: "" };
return { command: cleaned.slice(0, slash).toLowerCase(), remainder: decodeURIComponent(cleaned.slice(slash + 1)) };
return { command: cleaned.slice(0, slash).toLowerCase(), remainder: safeDecode(cleaned.slice(slash + 1)) };
}

/**
* URL-decode a remainder, falling back to the RAW string on a malformed `%` escape.
* `decodeURIComponent` throws `URIError` on a lone/short `%` (e.g. a `find/100%` pattern);
* {@link handleGraphVfs} promises it "never throws", so an undecodable remainder is passed
* through verbatim rather than blowing up the whole render.
*/
function safeDecode(s: string): string {
try {
return decodeURIComponent(s);
} catch {
return s;
}
}

// ════════════════════════════════════════════════════════════════════════════
Expand Down
1 change: 1 addition & 0 deletions tests/cli/daemon-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ describe("PRD-064h schtasks controller, register/restart/status argv (injected r
),
).toThrow();
},
30_000,
);

it("isRegistered is true when /Query succeeds, false when it throws (task absent)", () => {
Expand Down
3 changes: 2 additions & 1 deletion tests/cli/health-probes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ describe("PRD-021b b-AC-5 — status reports the real D1–D5 health", () => {
const lines = await health.evaluate();
const ids = lines.map((l) => l.id);
expect(ids).toEqual(["D1", "D2", "D3", "D4", "D5"]);
});
},
30_000);

it("D5 (capture wired) reports HEALTHY when the Claude Code plugin is installed + enabled", async () => {
const probes = buildHealthProbes(createFakeDaemonClient({ alive: true }), fakePluginRunner(true));
Expand Down
8 changes: 8 additions & 0 deletions tests/daemon/runtime/codebase/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,14 @@ describe("d-AC-5 zero network — handleGraphVfs reads only the local snapshot",
expect(() => handleGraphVfs("graph/bogus/thing", buildFixture())).not.toThrow();
expect(handleGraphVfs("graph/bogus/thing", buildFixture())).toContain("unknown");
});

it("d-AC-5 a remainder with a malformed % escape does not throw (never throws contract)", () => {
// `decodeURIComponent` throws URIError on a lone/short `%`; the renderer must not.
for (const path of ["graph/find/100%", "graph/show/a%zz", "graph/neighborhood/src/x%.ts"]) {
expect(() => handleGraphVfs(path, buildFixture())).not.toThrow();
expect(typeof handleGraphVfs(path, buildFixture())).toBe("string");
}
});
});

// ── Supporting endpoints (FR-4 / FR-7) ──────────────────────────────────────────
Expand Down
12 changes: 8 additions & 4 deletions tests/daemon/runtime/logs/log-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ afterEach(() => {
describe("PRD-043a openLogStore persists to disk", () => {
it("AC-1: records written before a restart are queryable after re-opening the same logs.db", () => {
// Write three records, then CLOSE (simulating the daemon stopping).
const first = openLogStore({ baseDir: dir });
// Mock clock so test records (2026-06-20) don't get pruned as "too old" by the startup sweep.
const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") };
const first = openLogStore({ baseDir: dir, clock: testClock });
expect(first.persistent).toBe(true);
for (let i = 0; i < 3; i++) first.appendRequest(rec(i));
first.close();

// A FRESH store opens the SAME on-disk file (the daemon restarting) and reads the records back.
const second = openLogStore({ baseDir: dir });
const second = openLogStore({ baseDir: dir, clock: testClock });
const page = second.queryRequests(resolveHistoryQuery({}));
expect(page.records).toHaveLength(3);
// Newest first.
Expand All @@ -79,7 +81,8 @@ describe("PRD-043a openLogStore persists to disk", () => {
});

it("AC-6: the request_log schema carries ONLY the record fields — no header/token/body column", () => {
const store = openLogStore({ baseDir: dir });
const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") };
const store = openLogStore({ baseDir: dir, clock: testClock });
store.appendRequest(rec(0));
const page = store.queryRequests(resolveHistoryQuery({}));
const record = page.records[0] as Record<string, unknown>;
Expand All @@ -95,7 +98,8 @@ describe("PRD-043a openLogStore persists to disk", () => {
});

it("the db file lands under .daemon/logs.db (mirrors the secrets .daemon pattern)", () => {
const store = openLogStore({ baseDir: dir });
const testClock = { now: () => Date.parse("2026-06-20T00:00:00.000Z") };
const store = openLogStore({ baseDir: dir, clock: testClock });
store.appendRequest(rec(0));
store.close();
// The file exists at the documented path.
Expand Down
3 changes: 2 additions & 1 deletion tests/daemon/runtime/pipeline/memory-redrive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ describe("b-AC-4: readTerminalControlledWriteJobs reads only TERMINAL memory_con
const jobs = readTerminalControlledWriteJobs({ baseDir: dir });
expect(jobs).toHaveLength(1);
expect(jobs[0]!.content).toBe("terminal fact");
});
},
30_000);

it("returns [] when the local-queue.db does not exist yet (read-through fail-soft)", () => {
// The temp dir has no `.daemon/local-queue.db` — the reader must NOT fabricate one.
Expand Down
Loading