Skip to content

verbspec: single-source the Anthropic report tool from a VerbSpec (Thread 2 Step 1) - #18

Merged
bdelanghe merged 1 commit into
mainfrom
verbspec-report-tool
Jun 23, 2026
Merged

verbspec: single-source the Anthropic report tool from a VerbSpec (Thread 2 Step 1)#18
bdelanghe merged 1 commit into
mainfrom
verbspec-report-tool

Conversation

@bdelanghe

Copy link
Copy Markdown
Contributor

Thread 2, Step 1 of the verbspec integration (the highest-value, smallest surface).

What

The report tool — the Anthropic structured-output contract {score, findings} — was a
hand-written REPORT_TOOL.input_schema in anthropic.mjs. This authors it once as a
verbspec VerbSpec
and projects it to the Anthropic tool surface via toAnthropicTool:

export const reportVerb = defineVerb({
  id: "report",
  summary: "Report the audit result for one copy string.",
  actor: "audit",
  input: z.object({
    score: z.number().int().min(0).max(10).describe("0 = unusable, 10 = excellent for its type."),
    findings: z.array(z.string()).describe("Concrete, actionable problems. Empty if none."),
  }),
  output: z.object({ ok: z.boolean() }), // unused — report is a tool-use surface
  run: () => ({ ok: true }),
});
const REPORT_TOOL = /* toAnthropicTool(reportVerb), with z.toJSONSchema's $schema stripped */;

It's the same typed contract a CLI / MCP / OpenAPI projection of the verb reads, so the
structured-output schema can't drift from them — the point of verbspec.

Behavior unchanged

  • The projected input_schema deep-equals the previous hand-written one (integer
    0..10, string[], required:[score,findings], additionalProperties:false,
    identical descriptions). z.toJSONSchema's $schema draft pointer is stripped, so
    the wire payload is byte-equivalent.
  • buildRequest / parseResponse are untouched — the LLM path is identical to v0.3.x
    (tool_choice still forces report; parseResponse still reads tool_use.input).
  • test.mjs now also pins the exact projected schema (deepEqual), so a verbspec/zod
    bump can't silently drift the contract.

Dependencies

verbspec (npm:@jsr/bounded-systems__verbspec) + its zod peer become regular runtime
deps
anthropic.mjs is imported by audit.mjs and test.mjs, so the core CI install
(npm ci --omit=optional) pulls them; only the heavy cas/anchored-chain CAS backends
stay optional. The consumer site's prose-only gate (prose.mjs) is unaffected — it
never imports anthropic.mjs. (ci.yml comment/step-name updated to reflect that the core
now installs verbspec; the "JSR-free core" framing is retired — only the CAS backends are
optional.)

Test — full ci.yml sequence locally (Node 24)

npm ci --omit=optional   → 11 pkgs (prose + verbspec + zod), cas NOT installed ✓
node test.mjs ✓ (incl. the new pinned-schema assertion)   audit ✓   cache 0 miss ✓   extract ✓
npm install --include=optional → +3 (CAS)   socket store: cold · warm 0 miss ✓

Plus a direct deepEqual of the projected REPORT_TOOL against the original schema.

Follow-up (Step 2, separate PR): author audit + extract as VerbSpecs → project to CLI

  • MCP surfaces.

PR run sheet

  1. Independent PR — Step 1 only (report tool); the larger CLI/MCP work is a follow-up.
  2. Changed codepaths verified — full ci.yml sequence + projected-schema deepEqual + contract test.
  3. Root cause identified — N/A (refactor to single-source the schema; no bug).
  4. No duplication — the hand-written schema is replaced, not duplicated; verb is the single source.
  5. No unrelated changes — untracked RELEASE-v0.3.0.md left out.

🤖 Generated with Claude Code

…read 2 Step 1)

Author the `report` verb ONCE as a verbspec VerbSpec (typed Zod input: score
integer 0..10, findings string[]) and project it to the Anthropic tool surface via
toAnthropicTool — instead of hand-writing REPORT_TOOL.input_schema. Same typed
contract a CLI/MCP/OpenAPI projection reads, so the structured-output schema can't
drift from them.

The projected input_schema deep-equals the previous hand-written one; z.toJSONSchema's
`$schema` draft pointer is stripped so the wire payload is byte-equivalent. buildRequest
/ parseResponse are untouched — the LLM path is identical to v0.3.x (tool_choice still
forces `report`; parseResponse still reads tool_use.input). test.mjs now also pins the
exact projected schema, guarding against a verbspec/zod bump silently drifting the contract.

Deps: verbspec (npm:@jsr/...) + its zod peer become regular runtime deps — anthropic.mjs
is imported by audit.mjs + test.mjs, so the core CI install (npm ci --omit=optional)
pulls them; only the heavy cas/anchored-chain CAS backends stay optional. The site's
prose-only gate (prose.mjs) is unaffected — it never imports anthropic.mjs.

Verified locally: full ci.yml sequence green (core install = prose+verbspec, no CAS;
test/audit/cache/extract; optional CAS install + socket store).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bdelanghe
bdelanghe merged commit 89114ff into main Jun 23, 2026
1 check passed
@bdelanghe
bdelanghe deleted the verbspec-report-tool branch June 23, 2026 04:31
bdelanghe added a commit that referenced this pull request Jun 23, 2026
Bump 0.3.1 → 0.4.0 for the verbspec surfaces merged since v0.3.1: audit + extract authored once as VerbSpecs → CLI + MCP (string-audit-mcp bin), Anthropic report tool single-sourced from a VerbSpec (#18, #19); cas/anchored-chain made optional (#13, #15, #17). The release workflow auto-tags v0.4.0 on merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant