From ef16c6b494f83270cf57504870fbf8adda024224 Mon Sep 17 00:00:00 2001 From: creayma Date: Fri, 31 Jul 2026 10:18:36 +0800 Subject: [PATCH 1/3] fix(templates): route WorkBuddy to the portable HTML report The WorkBuddy host adapter shipped claiming self-contained HTML + Markdown output in the adapter matrix, docs site, and CHANGELOG 0.4.0, but templates/reporting/routing.md never listed WorkBuddy in the Portable HTML report route, so agents on WorkBuddy were never routed to the shipped output. Add WorkBuddy to the route, update the two hard-coded host-list assertions, and add a derived support-declaration check so every matrix host claiming portable HTML output must appear in the routing row (one-directional, so a pending durable-report gap can drop the matrix claim first). Spec: docs/specs/2026-07-30-routing-workbuddy-host.md. Validated red-green (the new check fails with 'missing matrix HTML host: WorkBuddy' on the old routing.md) and with the full suite: 1019 pass / 0 fail on Node 22.20.0. Co-authored-by: QoderAI (Qwen 3.8 Max) --- .../2026-07-30-routing-workbuddy-host.md | 72 +++++++++++++++++++ templates/reporting/routing.md | 2 +- test/better-harness-skill.test.mjs | 2 +- test/style-templates.test.mjs | 2 +- test/support-declarations.test.mjs | 22 ++++++ 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 docs/specs/2026-07-30-routing-workbuddy-host.md diff --git a/docs/specs/2026-07-30-routing-workbuddy-host.md b/docs/specs/2026-07-30-routing-workbuddy-host.md new file mode 100644 index 0000000..481189f --- /dev/null +++ b/docs/specs/2026-07-30-routing-workbuddy-host.md @@ -0,0 +1,72 @@ +# Report Routing WorkBuddy Host Gap + +Add WorkBuddy to the Portable HTML report route in +`templates/reporting/routing.md` and add a derived consistency test so a +future host with portable HTML output cannot be omitted from report routing +again. + +## Traceability + +- Spec ID: 2026-07-30-routing-workbuddy-host +- Story: follow-up to the WorkBuddy host adapter (`4b871d9`, + CHANGELOG 0.4.0) +- Status: Implemented + +## Intent + +The WorkBuddy host adapter shipped with a `self-contained HTML + Markdown` +Default Output claim in the [Host Adapter Matrix](../adapters/README.md), and +the matrix Output Modes section, the docs site (English and zh-Hans), and +CHANGELOG 0.4.0 all state that WorkBuddy portable HTML routing is +implemented. However, the Portable HTML report route in +[`templates/reporting/routing.md`](../../templates/reporting/routing.md) +still lists only "Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, or +Pi". Because `routing.md` is the routing switchboard agents load through +`SKILL.md`, an agent running on WorkBuddy is never routed to the portable +HTML report. + +The omission survived because the two guarding assertions +(`test/better-harness-skill.test.mjs`, `test/style-templates.test.mjs`) +hard-code the host list, and the A-06 support-declaration consistency tests +(`test/support-declarations.test.mjs`) never compare the adapter matrix +against `routing.md`. + +## Acceptance Scenarios + +- AC-1: The Portable HTML report row in `templates/reporting/routing.md` + lists WorkBuddy alongside the existing hosts; no other route changes. +- AC-2: The hard-coded routing assertions in + `test/better-harness-skill.test.mjs` and `test/style-templates.test.mjs` + match the updated host list. +- AC-3: `test/support-declarations.test.mjs` gains a derived check: every + adapter-matrix host whose Default Output cell claims + `self-contained HTML + Markdown` must appear in the Portable HTML report + route of `routing.md`. The check is one-directional so a host may be + removed from the matrix claim first (as the in-flight A-05 change does for + Cursor) without breaking routing. +- AC-4: `npm test` passes; `node --test test/doc-link-graph.test.mjs` passes + with this spec's links resolving. + +## Non-Goals + +- No change to the Cursor entries in `routing.md` or the adapter matrix; the + Cursor durable-report gap stays tracked by the in-flight A-05 pull request + and roadmap item HA-03. +- No new WorkBuddy capability, output mode, or renderer change; this only + routes the already-shipped portable HTML output. + +## Plan + +1. Add WorkBuddy to the Portable HTML report row in + `templates/reporting/routing.md`. +2. Update the two hard-coded host-list assertions to match. +3. Add the derived matrix-to-routing consistency test to + `test/support-declarations.test.mjs`. + +## Test Evidence + +- `node --test test/support-declarations.test.mjs` +- `node --test test/better-harness-skill.test.mjs` +- `node --test test/style-templates.test.mjs` +- `node --test test/doc-link-graph.test.mjs` +- Full suite: `npm test` diff --git a/templates/reporting/routing.md b/templates/reporting/routing.md index 862749d..c011b50 100644 --- a/templates/reporting/routing.md +++ b/templates/reporting/routing.md @@ -28,6 +28,6 @@ files from other routes. | Route | Use when | Artifacts | Runtime owner | | --- | --- | --- | --- | | Qoder Canvas report | Active host is Qoder | renderer-owned `findings.json`, `canvas.json`, `report.canvas.tsx` | `qoder-canvas.md` | -| Portable HTML report | Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, or Pi, or a portable visual is explicitly requested | renderer-owned `findings.json`, `report.md`, `report.html` | `html-visual.md` | +| Portable HTML report | Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy, or a portable visual is explicitly requested | renderer-owned `findings.json`, `report.md`, `report.html` | `html-visual.md` | | Markdown only | Markdown without a visual companion is explicitly requested | `report.md`, `findings.json` | none | | Inline only | Inline or no-files output is explicitly requested | none; inline analysis writes nothing | none | diff --git a/test/better-harness-skill.test.mjs b/test/better-harness-skill.test.mjs index 1859645..dc5525b 100644 --- a/test/better-harness-skill.test.mjs +++ b/test/better-harness-skill.test.mjs @@ -77,7 +77,7 @@ test("non-Qoder providers default to validated durable HTML with an explicit inl assert.match(skill, /HTML artifacts: findings\.json, report\.md, report\.html/); assert.match(skill, /Succeed only on\s+`status: pass`/); assert.match(skill, /Never hand-write\s+Canvas, Markdown, or HTML/); - assert.match(routing, /Portable HTML report \| Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, or Pi/); + assert.match(routing, /Portable HTML report \| Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy/); assert.match(routing, /Inline only \| Inline or no-files output is explicitly requested \| none; inline analysis writes nothing/); assert.match(adapters, /Claude Code[^\n]+scripts\/session-analysis\/platforms\/claude\.mjs[^\n]+self-contained HTML \+ Markdown/); assert.match(adapters, /Cursor[^\n]+scripts\/session-analysis\/platforms\/cursor\.mjs[^\n]+self-contained HTML \+ Markdown/); diff --git a/test/style-templates.test.mjs b/test/style-templates.test.mjs index c96126c..b9421be 100644 --- a/test/style-templates.test.mjs +++ b/test/style-templates.test.mjs @@ -75,7 +75,7 @@ test("harness report routing owns output-mode selection and exclusions", () => { assert.match(reportRouting, /Choose exactly one output route/); assert.match(reportRouting, /Qoder Canvas report/); assert.match(reportRouting, /Portable HTML report/); - assert.match(reportRouting, /Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, or Pi/); + assert.match(reportRouting, /Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy/); assert.match(reportRouting, /Markdown only/); assert.match(reportRouting, /Inline only/); assert.match(reportRouting, /inline analysis writes nothing/); diff --git a/test/support-declarations.test.mjs b/test/support-declarations.test.mjs index ea0ebb7..eb8f6fb 100644 --- a/test/support-declarations.test.mjs +++ b/test/support-declarations.test.mjs @@ -13,6 +13,7 @@ const SUPPORTED_PLATFORMS = ["qoder", "codex", "claude", "cursor", "qwen", "copi const cliPath = path.join(process.cwd(), "scripts", "better-harness.mjs"); const adapterMatrixPath = path.join(process.cwd(), "docs", "adapters", "README.md"); +const reportRoutingPath = path.join(process.cwd(), "templates", "reporting", "routing.md"); function runBetterHarness(args) { return spawnSync(process.execPath, [cliPath, ...args], { @@ -119,3 +120,24 @@ test("host adapter matrix documents exactly the supported platforms", () => { assertSameSet(documentedProviders, "adapter matrix configured-asset providers"); assertSameSet(documentedPlatforms, "adapter matrix session platforms"); }); + +test("adapter-matrix portable HTML hosts appear in the portable HTML report route", () => { + const matrix = readFileSync(adapterMatrixPath, "utf8"); + const routing = readFileSync(reportRoutingPath, "utf8"); + + // Hosts whose matrix Default Output cell claims the portable HTML pipeline. + // One-directional on purpose: a host may drop the matrix claim first (for + // example a pending durable-report gap) without breaking report routing. + const htmlHosts = matrix + .split("\n") + .map((line) => line.split("|").map((cell) => cell.trim())) + .filter((cells) => cells[6] === "self-contained HTML + Markdown") + .map((cells) => cells[1]); + assert.ok(htmlHosts.length > 0, "adapter matrix declares no self-contained HTML + Markdown hosts"); + + const routeHosts = routing.match(/\| Portable HTML report \| Active host is ([^|]+)\|/u)?.[1]; + assert.ok(routeHosts, "reporting/routing.md does not declare a Portable HTML report route"); + for (const host of htmlHosts) { + assert.ok(routeHosts.includes(host), `Portable HTML report route is missing matrix HTML host: ${host}`); + } +}); From 55d44ccad0251628d84a365f6773b978cb832d31 Mon Sep 17 00:00:00 2001 From: creayma Date: Fri, 31 Jul 2026 11:05:46 +0800 Subject: [PATCH 2/3] docs(changelog): record WorkBuddy portable HTML routing fix Add the Unreleased Fixed entry for the routing.md WorkBuddy route and the derived support-declaration check, keeping the PR checklist's CHANGELOG requirement satisfied. Validated with npm test (1019 pass / 0 fail) and npm run pack:verify (354 npm entries, 377 runtime zip entries). Co-authored-by: QoderAI (Qwen 3.8 Max) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0806589..b4a7268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ observable behavior and compatibility, not every internal refactor. ## Unreleased +### Fixed + +- The Portable HTML report route in `templates/reporting/routing.md` now + lists WorkBuddy, so agents on WorkBuddy are routed to the self-contained + HTML + Markdown output the 0.4.0 host adapter already ships. A derived + support-declaration check now requires every adapter-matrix host claiming + portable HTML output to appear in that routing row. + ## 0.4.0 - 2026-07-30 ### Added From 4cddf91eaea21c104da74226e6b5e56a5f41e913 Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Fri, 31 Jul 2026 13:36:30 +0800 Subject: [PATCH 3/3] test(templates): require exact portable HTML host entries Close the substring-matching hole in the WorkBuddy routing guard so a prefix such as WorkBuddy Enterprise cannot satisfy the WorkBuddy declaration. Tighten the duplicated route assertions and update docs/specs/2026-07-30-routing-workbuddy-host.md with the exact-entry regression contract. Validated with the focused 31-test routing and documentation suite, the full npm test suite on Node 22.20.0, and npm run pack:verify (354 npm entries and 377 runtime ZIP entries). Co-authored-by: Codex (GPT 5.6 Sol) --- .../2026-07-30-routing-workbuddy-host.md | 14 +++--- test/better-harness-skill.test.mjs | 5 ++- test/style-templates.test.mjs | 5 ++- test/support-declarations.test.mjs | 43 ++++++++++++++----- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/docs/specs/2026-07-30-routing-workbuddy-host.md b/docs/specs/2026-07-30-routing-workbuddy-host.md index 481189f..8aca132 100644 --- a/docs/specs/2026-07-30-routing-workbuddy-host.md +++ b/docs/specs/2026-07-30-routing-workbuddy-host.md @@ -40,10 +40,11 @@ against `routing.md`. match the updated host list. - AC-3: `test/support-declarations.test.mjs` gains a derived check: every adapter-matrix host whose Default Output cell claims - `self-contained HTML + Markdown` must appear in the Portable HTML report - route of `routing.md`. The check is one-directional so a host may be - removed from the matrix claim first (as the in-flight A-05 change does for - Cursor) without breaking routing. + `self-contained HTML + Markdown` must appear as an exact host entry in the + Portable HTML report route of `routing.md`. A prefix collision such as + `WorkBuddy` versus `WorkBuddy Enterprise` must fail. The check is + one-directional so a host may be removed from the matrix claim first (as the + in-flight A-05 change does for Cursor) without breaking routing. - AC-4: `npm test` passes; `node --test test/doc-link-graph.test.mjs` passes with this spec's links resolving. @@ -61,11 +62,14 @@ against `routing.md`. `templates/reporting/routing.md`. 2. Update the two hard-coded host-list assertions to match. 3. Add the derived matrix-to-routing consistency test to - `test/support-declarations.test.mjs`. + `test/support-declarations.test.mjs`, parsing the route declaration into + normalized host entries and covering a host-prefix collision. ## Test Evidence - `node --test test/support-declarations.test.mjs` +- Mutation regression: replacing the exact `WorkBuddy` route entry with + `WorkBuddy Enterprise` must report WorkBuddy as missing. - `node --test test/better-harness-skill.test.mjs` - `node --test test/style-templates.test.mjs` - `node --test test/doc-link-graph.test.mjs` diff --git a/test/better-harness-skill.test.mjs b/test/better-harness-skill.test.mjs index dc5525b..dc801ee 100644 --- a/test/better-harness-skill.test.mjs +++ b/test/better-harness-skill.test.mjs @@ -77,7 +77,10 @@ test("non-Qoder providers default to validated durable HTML with an explicit inl assert.match(skill, /HTML artifacts: findings\.json, report\.md, report\.html/); assert.match(skill, /Succeed only on\s+`status: pass`/); assert.match(skill, /Never hand-write\s+Canvas, Markdown, or HTML/); - assert.match(routing, /Portable HTML report \| Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy/); + assert.match( + routing, + /Portable HTML report \| Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy, or a portable visual is explicitly requested \|/, + ); assert.match(routing, /Inline only \| Inline or no-files output is explicitly requested \| none; inline analysis writes nothing/); assert.match(adapters, /Claude Code[^\n]+scripts\/session-analysis\/platforms\/claude\.mjs[^\n]+self-contained HTML \+ Markdown/); assert.match(adapters, /Cursor[^\n]+scripts\/session-analysis\/platforms\/cursor\.mjs[^\n]+self-contained HTML \+ Markdown/); diff --git a/test/style-templates.test.mjs b/test/style-templates.test.mjs index b9421be..efd98d6 100644 --- a/test/style-templates.test.mjs +++ b/test/style-templates.test.mjs @@ -75,7 +75,10 @@ test("harness report routing owns output-mode selection and exclusions", () => { assert.match(reportRouting, /Choose exactly one output route/); assert.match(reportRouting, /Qoder Canvas report/); assert.match(reportRouting, /Portable HTML report/); - assert.match(reportRouting, /Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy/); + assert.match( + reportRouting, + /Active host is Claude Code, Codex, Cursor, Qwen Code, GitHub Copilot, Pi, or WorkBuddy, or a portable visual is explicitly requested \|/, + ); assert.match(reportRouting, /Markdown only/); assert.match(reportRouting, /Inline only/); assert.match(reportRouting, /inline analysis writes nothing/); diff --git a/test/support-declarations.test.mjs b/test/support-declarations.test.mjs index eb8f6fb..14136d2 100644 --- a/test/support-declarations.test.mjs +++ b/test/support-declarations.test.mjs @@ -30,6 +30,29 @@ function assertSameSet(actual, label) { assert.deepEqual(sortedSet(actual), sortedSet(SUPPORTED_PLATFORMS), `${label} disagrees with the supported platform set`); } +function portableHtmlMatrixHosts(matrix) { + return matrix + .split("\n") + .map((line) => line.split("|").map((cell) => cell.trim())) + .filter((cells) => cells[6] === "self-contained HTML + Markdown") + .map((cells) => cells[1]); +} + +function portableHtmlRouteHosts(routing) { + const declaration = routing.match( + /\| Portable HTML report \| Active host is (.+?), or a portable visual is explicitly requested \|/u, + )?.[1]; + assert.ok(declaration, "reporting/routing.md does not declare a Portable HTML report route"); + return new Set(declaration.split(/,\s*(?:or\s+)?/u).map((host) => host.trim())); +} + +function missingPortableHtmlRouteHosts(matrix, routing) { + const htmlHosts = portableHtmlMatrixHosts(matrix); + assert.ok(htmlHosts.length > 0, "adapter matrix declares no self-contained HTML + Markdown hosts"); + const routeHosts = portableHtmlRouteHosts(routing); + return htmlHosts.filter((host) => !routeHosts.has(host)); +} + test("agent-customize provider registry declares exactly the supported platforms", () => { assertSameSet([...PROVIDER_COLLECTORS.keys()], "PROVIDER_COLLECTORS"); @@ -128,16 +151,14 @@ test("adapter-matrix portable HTML hosts appear in the portable HTML report rout // Hosts whose matrix Default Output cell claims the portable HTML pipeline. // One-directional on purpose: a host may drop the matrix claim first (for // example a pending durable-report gap) without breaking report routing. - const htmlHosts = matrix - .split("\n") - .map((line) => line.split("|").map((cell) => cell.trim())) - .filter((cells) => cells[6] === "self-contained HTML + Markdown") - .map((cells) => cells[1]); - assert.ok(htmlHosts.length > 0, "adapter matrix declares no self-contained HTML + Markdown hosts"); - - const routeHosts = routing.match(/\| Portable HTML report \| Active host is ([^|]+)\|/u)?.[1]; - assert.ok(routeHosts, "reporting/routing.md does not declare a Portable HTML report route"); - for (const host of htmlHosts) { - assert.ok(routeHosts.includes(host), `Portable HTML report route is missing matrix HTML host: ${host}`); + for (const host of missingPortableHtmlRouteHosts(matrix, routing)) { + assert.fail(`Portable HTML report route is missing matrix HTML host: ${host}`); } + + const prefixCollisionRouting = routing.replace( + ", or WorkBuddy, or a portable visual is explicitly requested", + ", or WorkBuddy Enterprise, or a portable visual is explicitly requested", + ); + assert.notEqual(prefixCollisionRouting, routing, "prefix-collision fixture did not replace the WorkBuddy route entry"); + assert.deepEqual(missingPortableHtmlRouteHosts(matrix, prefixCollisionRouting), ["WorkBuddy"]); });