From 3d6a45e4d7fb8074445632f5bb6ad025f2f7f11f Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 11 Aug 2026 14:24:35 +0000 Subject: [PATCH] fix(runner): kill stale semantic-catalog counts and drift-check the doc The catalog doc claimed 36 tools / 22 optional while the catalog holds 37 / 23 (the 36-vs-37 drift the reconciliation plan flagged). Correct the prose and add src/catalog/catalog-docs.test.ts, which recomputes the total/always/optional counts and the operation-id membership from the catalog so a stale hand count can no longer pass. Refs PAP-17025. Co-Authored-By: Claude Opus 4.8 --- .../docs/capability-semantic-tools.md | 7 ++- .../src/catalog/catalog-docs.test.ts | 60 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/paperclip-runner/src/catalog/catalog-docs.test.ts diff --git a/packages/paperclip-runner/docs/capability-semantic-tools.md b/packages/paperclip-runner/docs/capability-semantic-tools.md index 0014086153a..5cfa1eb634c 100644 --- a/packages/paperclip-runner/docs/capability-semantic-tools.md +++ b/packages/paperclip-runner/docs/capability-semantic-tools.md @@ -17,9 +17,12 @@ list, not a disposition — they are never exposed as tools. See [capability disposition](capability-disposition.md) and [authorization and exposure](capability-authorization-and-exposure.md). -The catalog holds **36 tools**: 14 always-agent tools and 22 optional tools +The catalog holds **37 tools**: 14 always-agent tools and 23 optional tools across 10 groups. + + ### Always-agent tools (14) `get_task_context`, `get_task_history`, `list_documents`, `read_document`, @@ -27,7 +30,7 @@ across 10 groups. `finish_task`, `block_task`, `request_review`, `write_document`, `request_human_input`, `register_deliverable`, `inspect_operation_result`. -### Optional tools (22), by group +### Optional tools (23), by group | Group | Tools | | --- | --- | diff --git a/packages/paperclip-runner/src/catalog/catalog-docs.test.ts b/packages/paperclip-runner/src/catalog/catalog-docs.test.ts new file mode 100644 index 00000000000..fd23eb97906 --- /dev/null +++ b/packages/paperclip-runner/src/catalog/catalog-docs.test.ts @@ -0,0 +1,60 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +import { + CAPABILITY_SEMANTIC_TOOL_CATALOG as SCENARIO_CATALOG, + capabilitySemanticTool, +} from "../tools/capability-semantic-tool-catalog.js"; + +const docPath = resolve( + dirname(fileURLToPath(import.meta.url)), + "..", + "..", + "docs", + "capability-semantic-tools.md", +); +const doc = readFileSync(docPath, "utf8"); + +/** The catalog counts region, between the disposition prose and descriptor shape. */ +function countsRegion(): string { + const start = doc.indexOf("## Two dispositions"); + const end = doc.indexOf("## Descriptor shape"); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + return doc.slice(start, end); +} + +describe("capability-semantic-tools.md stays in sync with the catalog", () => { + const always = SCENARIO_CATALOG.filter((tool) => tool.disposition === "always_agent_tool"); + const optional = SCENARIO_CATALOG.filter((tool) => tool.disposition === "optional_agent_tool"); + + it("states the current total, always, and optional counts", () => { + const region = countsRegion(); + const total = /holds \*\*(\d+) tools\*\*/.exec(region)?.[1]; + const alwaysProse = /(\d+) always-agent tools and (\d+) optional tools/.exec(region); + const alwaysHeader = /### Always-agent tools \((\d+)\)/.exec(region)?.[1]; + const optionalHeader = /### Optional tools \((\d+)\)/.exec(region)?.[1]; + + expect(Number(total)).toBe(SCENARIO_CATALOG.length); + expect(Number(alwaysProse?.[1])).toBe(always.length); + expect(Number(alwaysProse?.[2])).toBe(optional.length); + expect(Number(alwaysHeader)).toBe(always.length); + expect(Number(optionalHeader)).toBe(optional.length); + // Guard against the historical 36-vs-37 drift the reconciliation plan flagged. + expect(SCENARIO_CATALOG.length).toBe(37); + }); + + it("lists exactly the catalog's operation ids in the counts region", () => { + const region = countsRegion(); + const backticked = new Set( + [...region.matchAll(/`([a-z_]+)`/g)] + .map((match) => match[1]) + .filter((id) => capabilitySemanticTool(id) !== undefined), + ); + expect([...backticked].sort()).toEqual( + SCENARIO_CATALOG.map((tool) => tool.operationId).sort(), + ); + }); +});