@osovv/vv-opencode 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +39 -8
  3. package/dist/lib/agent-tool-catalog.d.ts +144 -0
  4. package/dist/lib/agent-tool-catalog.js +2551 -0
  5. package/dist/lib/agent-tool-catalog.js.map +1 -0
  6. package/dist/lib/agent-tool-contract.d.ts +200 -0
  7. package/dist/lib/agent-tool-contract.js +407 -0
  8. package/dist/lib/agent-tool-contract.js.map +1 -0
  9. package/dist/lib/workflow-contract.d.ts +12 -0
  10. package/dist/lib/workflow-contract.js +71 -18
  11. package/dist/lib/workflow-contract.js.map +1 -1
  12. package/dist/plugins/hashline-edit/index.js +102 -97
  13. package/dist/plugins/hashline-edit/index.js.map +1 -1
  14. package/dist/plugins/hashline-edit/normalize-edits.d.ts +2 -8
  15. package/dist/plugins/hashline-edit/normalize-edits.js +32 -17
  16. package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -1
  17. package/dist/plugins/hashline-edit/schemas.d.ts +246 -0
  18. package/dist/plugins/hashline-edit/schemas.js +659 -0
  19. package/dist/plugins/hashline-edit/schemas.js.map +1 -0
  20. package/dist/plugins/hashline-edit/str-replace-editor.d.ts +6 -17
  21. package/dist/plugins/hashline-edit/str-replace-editor.js +19 -21
  22. package/dist/plugins/hashline-edit/str-replace-editor.js.map +1 -1
  23. package/dist/plugins/hashline-edit/tool-description.d.ts +1 -0
  24. package/dist/plugins/hashline-edit/tool-description.js +20 -4
  25. package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
  26. package/dist/plugins/web-tools/fetch-service.d.ts +8 -5
  27. package/dist/plugins/web-tools/fetch-service.js +30 -45
  28. package/dist/plugins/web-tools/fetch-service.js.map +1 -1
  29. package/dist/plugins/web-tools/index.js +23 -5
  30. package/dist/plugins/web-tools/index.js.map +1 -1
  31. package/dist/plugins/web-tools/schemas.d.ts +469 -0
  32. package/dist/plugins/web-tools/schemas.js +523 -0
  33. package/dist/plugins/web-tools/schemas.js.map +1 -0
  34. package/dist/plugins/web-tools/search-service.d.ts +6 -3
  35. package/dist/plugins/web-tools/search-service.js +24 -29
  36. package/dist/plugins/web-tools/search-service.js.map +1 -1
  37. package/dist/plugins/workflow/authority.d.ts +6 -0
  38. package/dist/plugins/workflow/authority.js +45 -10
  39. package/dist/plugins/workflow/authority.js.map +1 -1
  40. package/dist/plugins/workflow/authorization.js +16 -8
  41. package/dist/plugins/workflow/authorization.js.map +1 -1
  42. package/dist/plugins/workflow/checkpoints.d.ts +41 -2
  43. package/dist/plugins/workflow/checkpoints.js +176 -47
  44. package/dist/plugins/workflow/checkpoints.js.map +1 -1
  45. package/dist/plugins/workflow/delegated.d.ts +15 -1
  46. package/dist/plugins/workflow/delegated.js +65 -35
  47. package/dist/plugins/workflow/delegated.js.map +1 -1
  48. package/dist/plugins/workflow/execution.d.ts +15 -30
  49. package/dist/plugins/workflow/execution.js +133 -33
  50. package/dist/plugins/workflow/execution.js.map +1 -1
  51. package/dist/plugins/workflow/index.js +177 -112
  52. package/dist/plugins/workflow/index.js.map +1 -1
  53. package/dist/plugins/workflow/input-validation.d.ts +48 -0
  54. package/dist/plugins/workflow/input-validation.js +536 -0
  55. package/dist/plugins/workflow/input-validation.js.map +1 -0
  56. package/dist/plugins/workflow/inspection.d.ts +60 -0
  57. package/dist/plugins/workflow/inspection.js +316 -0
  58. package/dist/plugins/workflow/inspection.js.map +1 -0
  59. package/dist/plugins/workflow/protocol.d.ts +2 -0
  60. package/dist/plugins/workflow/protocol.js +26 -4
  61. package/dist/plugins/workflow/protocol.js.map +1 -1
  62. package/dist/plugins/workflow/recovery.d.ts +2 -1
  63. package/dist/plugins/workflow/recovery.js +79 -12
  64. package/dist/plugins/workflow/recovery.js.map +1 -1
  65. package/dist/plugins/workflow/repair.d.ts +1 -1
  66. package/dist/plugins/workflow/repair.js +9 -10
  67. package/dist/plugins/workflow/repair.js.map +1 -1
  68. package/dist/plugins/workflow/results.d.ts +2566 -0
  69. package/dist/plugins/workflow/results.js +1111 -0
  70. package/dist/plugins/workflow/results.js.map +1 -0
  71. package/dist/plugins/workflow/schemas.d.ts +214 -24
  72. package/dist/plugins/workflow/schemas.js +350 -89
  73. package/dist/plugins/workflow/schemas.js.map +1 -1
  74. package/dist/plugins/workflow/state.d.ts +3 -2
  75. package/dist/plugins/workflow/state.js +15 -8
  76. package/dist/plugins/workflow/state.js.map +1 -1
  77. package/dist/plugins/workflow/system-instruction.md +17 -1
  78. package/dist/plugins/workflow/tooling.d.ts +2 -18
  79. package/dist/plugins/workflow/tooling.js +477 -460
  80. package/dist/plugins/workflow/tooling.js.map +1 -1
  81. package/dist/plugins/workflow/transactions.d.ts +13 -0
  82. package/dist/plugins/workflow/transactions.js +13 -3
  83. package/dist/plugins/workflow/transactions.js.map +1 -1
  84. package/package.json +6 -3
  85. package/schemas/vvoc/v3.json +1 -1
  86. package/templates/agents/vv-code-reviewer.md +13 -4
  87. package/templates/agents/vv-implementer.md +16 -9
  88. package/templates/agents/vv-spec-reviewer.md +13 -4
  89. package/templates/skills/vv-execute/SKILL.md +3 -3
  90. package/templates/skills/vv-execute/references/tool-contracts.md +1750 -0
  91. package/templates/skills/vv-review/SKILL.md +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.6.0 (2026-09-24)
2
+
3
+ ### Summary
4
+
5
+ Version 1.6.0 strengthens the public contract of all nine vvoc-owned agent tools so agents can call them reliably without reverse-engineering installed code. Every tool now publishes a closed input schema and rejects invalid or contradictory arguments before it mutates state, edits a file, or dispatches a provider request: unknown keys are refused instead of silently stripped, and a supplied value that conflicts with the chosen action or decision is diagnosed rather than ignored. Workflow failures now return machine-readable results with a stable error code and a category (input, state, authorization, host context, persistence, or internal) plus bounded, field-level issues, and outcomes stay truthful after side effects so a reporting failure is never misreported as a rejected call or an invitation to replay. `work_item_list` also gains read-only same-session inspection of work items and generic/native executions with the loaded package and contract identity, and a generated on-demand nine-tool reference ships with the vv-execute skill. Two new gates keep this honest: a read-only contracts check wired into `check` and `pack:check`, and an isolated live-host matrix exercised against the built package. Tool names, exports, schema v3, persistence v4, defaults, and authorization semantics are unchanged, so existing workflows keep working; the tightening only stops accepting calls whose constraints were previously dropped.
6
+
7
+ * chore(grace): apply and archive C-AGENT-TOOL-CONTRACTS ([df24116](https://github.com/osovv/vv-opencode/commit/df24116))
8
+ * feat(tools): enforce strict public contracts for all nine vvoc agent tools ([0831078](https://github.com/osovv/vv-opencode/commit/0831078))
9
+
1
10
  ## 1.5.0 (2026-09-22)
2
11
 
3
12
  ### Summary
package/README.md CHANGED
@@ -461,7 +461,7 @@ For review-only reports, use `"mode": "review_only"`. In review-only mode, revie
461
461
 
462
462
  #### Delegated execution and review checkpoints
463
463
 
464
- Under the `delegated` profile the workflow plugin also registers two control tools — `work_item_decide` and `work_checkpoint` — for the primary vv-controller session only (child sessions, untrusted workspaces, and unverifiable session identity are denied).
464
+ The workflow plugin registers two control tools — `work_item_decide` and `work_checkpoint` — for the primary vv-controller session independent of the startup profile. Registration is profile-independent; enforcement is not: child sessions, untrusted workspaces, and unverifiable session identity are denied by the root-controller guard.
465
465
 
466
466
  Delegated tasks are opened with `"mode": "delegated"`, an explicitly empty `requiredReviewers` array, and a declared `writeScope` of workspace-relative files. One worker (`vv-implementer`) is launched per task with a bounded packet; attempts are bound to the host call identity, so stale or duplicate callbacks fail without side effects. A `DONE` worker parks the item in `awaiting_acceptance` — it never closes the item and the worker never accepts its own result. The controller explicitly decides with `work_item_decide`:
467
467
 
@@ -482,14 +482,14 @@ When a tracked subagent returns a recognized task envelope whose inner result is
482
482
 
483
483
  Independent review happens at checkpoints declared in the plan. An approved delegated plan carries an `<execution><mode>delegated</mode>` section with `<review_checkpoints>` (`CHECKPOINT-R-NNN` identities, each with kind, wave barrier, covered tasks, reviewed scope files, reviewer set, acceptance criteria, and verification commands) and per-task `<write_scope>` lists. Registration accepts only this supported approved native plan package (the approved spec and plan pair under `.vvoc/specs/`); a provided plan or conversation-scoped execution stays on its own source and lifecycle rather than being converted into a native package. `work_checkpoint` handles these actions:
484
484
 
485
- - `register` — with `planPath`, loads an approved active plan plus its linked approved spec, fully lints them, and binds the run to their content hashes and the canonical workspace root. With a generic `runId` instead, it appends checkpoints to that execution. Re-registering identical inputs is idempotent; changed approved inputs are explicit plan drift, never a progress reset.
485
+ - `register` — with `planPath`, loads an approved active plan plus its linked approved spec, fully lints them, and binds the run to their content hashes and the canonical workspace root. With a generic `runId` and its required `amendmentId` plus `rationale` instead, it appends a validated task batch (tasks and checkpoints) to that existing execution; it never starts a fresh run. Re-registering identical inputs is idempotent; changed approved inputs are explicit plan drift, never a progress reset.
486
486
  - `start` — after every covered task is accepted, captures a fingerprint over the checkpoint scope (native scope files and existence, or the generic accepted-attempt coverage) and opens exactly the declared reviewer set as an ordinary `review_only` item. Writes overlapping an in-flight checkpoint scope are refused.
487
487
  - `verify` — for native runs, derives `passed`, `failed`, `stale`, or `stopped` from the recorded reviewer results plus freshly recomputed fingerprints and approval hashes. A checkpoint passes only when every declared reviewer passes for the pinned snapshot; a closed review-only `FAIL` report is findings, not approval; edits during review make the generation stale. The final checkpoint seals the run only via `verify` with `complete: true` after every task is accepted, every earlier checkpoint passed, and the complete declared write scope is covered with fresh verification. Completion is refused while a declared reviewer is missing: a skipped reviewer is never fabricated into a pass.
488
- - `review` / `bind` — record one generic reviewer result (optionally binding a reviewer call identity) and settle the generation when every declared reviewer reported.
488
+ - `review` / `bind` — `review` records one generic reviewer result; `bind` is the outcome-recording alias that settles the generation from the linked review item's recorded result when every declared reviewer reported. The callID launch binding itself is a host hook, not this tool action.
489
489
  - `complete` — complete a generic execution when every active task is accepted and every assigned checkpoint passed for the current revision, reporting `controller_accepted` or `independently_reviewed` honestly.
490
490
  - `amend` — append validated tasks or checkpoints to a generic execution without replaying accepted work.
491
491
  - `authorize` / `record_approval` / `revoke_authority` — record a bounded advance authority from an eligible root-user instruction, record a truthful controller-delegated stage approval, or revoke/narrow future authority credits.
492
- - `recover` — bounded recovery for a checkpoint generation that stopped (`NEEDS_CONTEXT`) or exhausted its ordinary generations. Recovery takes the same `diagnosis`, `changedCondition`, `verification`, `recoveryId`, and optional `userMessageId` fields as task recovery, with the same one-unit autonomous-then-authorized policy per checkpoint; a generic exhausted checkpoint recovers under a recorded advance authority referenced by `runId` and `authorityId`. Recovering a stopped generation settles it into history as a `stopped` entry — it does not convert the stop into a failure, so checkpoint rework stays locked until a real failed generation exists — and recovering an exhausted checkpoint grants exactly one further generation with a fresh snapshot at the next `start`. Recovery never satisfies prerequisites, erases a `FAIL`, or seals a run: only the required passing final review does.
492
+ - `recover` — bounded recovery for a checkpoint generation that stopped (`NEEDS_CONTEXT`) or exhausted its ordinary generations. Common required fields are `diagnosis`, `changedCondition`, `verification`, and `recoveryId`. For a native-package checkpoint, recovery supports `userMessageId`: a stopped generation resumes, and an exhausted generation follows the one-unit autonomous-then-authorized policy (a fresh root-user message, or a recorded advance). For a generic checkpoint, `userMessageId` is rejected: a stopped generation is a cost-free bounded resume with no automatic budget reset, and only an *exhausted* generic checkpoint needs a recorded advance authority referenced by `runId` and `authorityId` — a stopped generic checkpoint does not require one. Recovering a stopped generation settles it into history as a `stopped` entry — it does not convert the stop into a failure, so checkpoint rework stays locked until a real failed generation exists — and recovering an exhausted checkpoint grants exactly one further generation with a fresh snapshot at the next `start`. Recovery never satisfies prerequisites, erases a `FAIL`, or seals a run: only the required passing final review does.
493
493
 
494
494
  Passed milestones stay historical: later planned edits are covered by later checkpoints or the final gate, not by an old approval. Hard stops (`BLOCKED`, `NEEDS_CONTEXT`) are never reset or bypassed by acceptance or checkpoint handling; only a separately recorded recovery decision resumes the affected work. Checkpoint state persists per session and survives restarts; a persisted `PASS` is not fresh evidence until `verify` recomputes current hashes.
495
495
 
@@ -499,6 +499,26 @@ Delegated and checkpoint state persists under `$XDG_DATA_HOME/vvoc/workflow/<ses
499
499
 
500
500
  Plans without an `<execution>` section keep their legacy meaning; executing them with different acceptance or review obligations requires an explicit agreed plan amendment, and archived plans are never rewritten. In a deterministic dispatch-count scenario, a twenty-task plan with two intermediate code-review checkpoints and one final spec+code checkpoint needs exactly four initial reviewer launches instead of forty — that is a property of the declared checkpoint policy, not a wall-clock or model-quality benchmark.
501
501
 
502
+ #### Public tool contracts and strict input
503
+
504
+ The nine vvoc-owned tools keep their existing names: `work_item_open`, `work_item_list`, `work_item_close`, `work_item_decide`, `work_checkpoint`, `hashline_edit`, `str_replace_editor`, `web_search`, and `web_fetch`. Every one now publishes a closed public input contract and rejects invalid or contradictory calls before it mutates state, edits a file, or dispatches a provider request.
505
+
506
+ This is an intentional compatibility tightening: unknown keys in a vvoc-controlled request object are rejected before mutation instead of being stripped, and a supplied value that conflicts with the selected operation or action is diagnosed instead of being silently ignored. A misspelled authority stage or reserved stop, alone or mixed with valid entries, rejects the entire authority operation — it is never filtered down to the valid subset — and cannot create an authority, claim an authorization message, consume reserve, or grant a launch. Missing optional fields keep their documented defaults; only *invalid supplied* fields are refused. Tool names, package exports, canonical vvoc config schema v3, and persistence snapshot version 4 are unchanged, no config or persistence migration is introduced, and runtime plugin changes still take effect only after an OpenCode restart.
507
+
508
+ The five workflow tools (`work_item_open`, `work_item_list`, `work_item_close`, `work_item_decide`, `work_checkpoint`) return workflow failures as machine-readable JSON carrying a stable `errorCode` plus a `category` — one of `input`, `state`, `authorization`, `host_context`, `persistence`, or `internal` — and bounded `issues` with tokenized argument paths, the violated expectation, and a safe value/type summary. The edit tools keep their existing human-readable `text`/`Error` result surface plus apply warnings rather than a JSON envelope, and the web tools keep their `ToolResult` text/metadata/attachment delivery. Web structural validation throws the bounded `ContractInputError`; provider failures keep the existing `WebProviderError` behavior (a provider-named code and message with no universal bounded-payload or JSON-shape promise). All nine share the same bounded field-level structural diagnostics, but the host is not promised to expose an output schema for any tool and edit/web output is not forced into JSON; the workflow JSON result shapes are a producer/test contract, not an assumed model-visible SDK feature. Owned structural and workflow diagnostics never echo a full payload and never parse free-form message prose to recover a lost field path; edit text and web document or binary content stay opaque. In particular:
509
+
510
+ - Invalid `execution.source.kind` is reported as an input error, not as a missing workspace root; a failed generic lookup is not disguised as a missing native-only argument.
511
+ - The generic execution descriptor accepts only `conversation-scoped` or a provided-plan variant carrying its bounded reference. The native-package path is not reachable through that descriptor: it loads only through the approved native `planPath` loader, with spec linkage, lint, and content-hash binding preserved.
512
+ - Standalone `implementation`/`review_only` items require a non-empty unique reviewer set and reject delegated write scopes or native plan bindings. Standalone `delegated` items require an empty reviewer set, an exact-file `writeScope`, and paired optional native bindings. Generic tasks accept an explicit empty or non-empty reviewer set and default `taskId` from `key` and `goal` from `title`.
513
+
514
+ Path contracts are explicit. `writeScope` entries are exact workspace-relative files and reject wildcards, absolute/home/drive paths, backslashes, traversal, empty segments, and a trailing separator; `boundary.directories` keeps its established one-trailing-separator directory-subtree normalization, so an exact file and a directory subtree are never conflated.
515
+
516
+ `work_item_list` is read-only over the current session and additively exposes generic and native executions alongside the existing `items` and `planRuns`, with the latest attempt status, current generations, remaining allowances, assigned obligations, and bounded prerequisites. The same response carries the loaded contract identity (`packageName`, `packageVersion`, `toolContractRevision`, and `referencePath`) so an agent can discover the exact reference material shipped with the running release; `toolContractRevision` is the loaded `AGENT_TOOL_CONTRACT_REVISION`. Guidance is snapshotted from the same eligibility gates the mutations use: it is a statement about current state, not a future permission, and it never promises a launch will still be available after concurrent state changes. Inspecting state or describing a recovery path neither grants recovery nor consumes a unit.
517
+
518
+ For a workflow mutation whose outcome is genuinely unknown — for example a post-side-effect reporting failure — the response never claims the change was untouched, never labels it a pre-execution input rejection, and never invites a blind replay; the truthful `committed`/`not_applied`/`rolled_back`/`unknown` outcome is preserved in the workflow result and the safe next step is to inspect with `work_item_list` before retrying. This workflow envelope is not forced onto the edit and web tools, which keep their own text/`Error` or `ToolResult` delivery.
519
+
520
+ The full nine-tool contract lives in `templates/skills/vv-execute/references/tool-contracts.md`. It is generated from the pure catalog (`bun run contracts:generate`), shipped with the skill, and kept honest by a coverage gate; it is loaded on demand from the shipped file, not generated when you read it. It is deliberately on-demand documentation rather than a manual injected on every turn, so load the section you need. A declared `writeScope` is an edit boundary, not a universal filesystem sandbox, and nothing here claims that free-form evidence strings mechanically prove a claim or that stricter schemas reduce model errors.
521
+
502
522
  ### Edit format routing
503
523
 
504
524
  `HashlineEditPlugin` resolves an edit mode per session model and exposes exactly one native edit tool to that model — either a host-owned tool or one of the plugin profiles:
@@ -526,6 +546,8 @@ The default routing table sends `deepseek` to `str_replace_editor`, `kimi`, `qwe
526
546
 
527
547
  Routing changes require an OpenCode restart, like other runtime plugin settings.
528
548
 
549
+ `hashline_edit` and `str_replace_editor` publish closed input contracts and reject conflicting or malformed fields before touching a file, without changing literal edit behavior: `replace` keeps its optional inclusive `end` (the `replace_range` alias still works), `lines` accepts a string, a string array, or `null` (an empty array or `null` deletes the range), `append`/`prepend` perform boundary insertion, `new_str` may be intentionally empty to delete, and `view_range` may end at `-1` to view to end of file. Supplying conflicting anchors, an unknown field, or a malformed range is a structural error that leaves file content, paths, freshness metadata, and the metadata sink unchanged, and the existing current-file and stale-anchor checks stay authoritative before any mutation.
550
+
529
551
  ### Tool history compaction
530
552
 
531
553
  `ToolHistoryCompactionPlugin` shrinks the context replayed to the model on every turn without touching on-disk storage. It rewrites only the in-memory message copy through the `experimental.chat.messages.transform` hook, and only the `output` of old completed tool parts — `input` and part structure (callID/type/order) are never changed, so provider tool_use/tool_result stitching stays intact.
@@ -663,6 +685,8 @@ The `web-tools` vvoc plugin toggle is enabled by default. Provider selection bel
663
685
 
664
686
  Supported search providers are `exa` (default), `brave`, and `zai`. Supported fetch providers are `native` (default, no credential required), `spider`, and `zai`. A `zai` section must set `region` to either `international` or `china`; the plugin never guesses or falls back to another region.
665
687
 
688
+ Both tools validate a closed caller contract before any provider dispatch. `web_search` accepts `query`, an optional `count` (1–20), and an optional `freshness`; `web_fetch` accepts a URL, an optional `format`, and an optional `timeout` in seconds greater than 0 and at most 120 (fractional values such as `0.5` are allowed). Unknown keys, an invalid URL scheme or shape, or an out-of-range provided value are rejected before the permission prompt or any provider request, so no provider is dispatched and no network call is made. Provider credentials are resolved from configuration once at startup as described below; structural validation only prevents the permission prompt, provider dispatch, and network use, and does not re-resolve or override that startup resolution. Document text, binary/media content, and per-call provider or credential selection are never part of the caller contract. Defaults are applied at execution time even when OpenCode forwards raw, unparsed arguments, so the effective behavior is unchanged: an omitted `count` is 8, an omitted `format` is `markdown`, and an omitted `timeout` is 30 seconds (bounded at 120). Provider and credential configuration is still resolved once at startup from `vvoc.json`; changing it takes effect only after restarting OpenCode through the same `vvoc sync` and restart flow as before.
689
+
666
690
  Direct Z.AI endpoint routing:
667
691
 
668
692
  | Region | Search | Reader | Search engine |
@@ -706,12 +730,19 @@ The `context` vvoc plugin toggle defaults to enabled. Disable it with `vvoc plug
706
730
  ## Local development
707
731
 
708
732
  ```bash
709
- bun install # Install dependencies
710
- bun run check # Typecheck + lint + format check + GRACE markup check + test
711
- bun run fmt # Auto-format source files
712
- bun run release:check # Verify package/schema release consistency
733
+ bun install # Install dependencies
734
+ bun run check # Typecheck + lint + format check + GRACE markup + tool-contract check + test
735
+ bun run fmt # Auto-format source files
736
+ bun run contracts:check # Read-only nine-tool contract completeness gate
737
+ bun run contracts:generate # Regenerate the on-demand tool-contracts reference
738
+ bun run contracts:host # Isolated live-host contract gate (see below)
739
+ bun run release:check # Verify package/schema release consistency
713
740
  ```
714
741
 
742
+ `bun run contracts:check` is a repository-only, read-only gate: it censuses owned tool registrations independently of the catalog, executes every catalog fixture through the real validators and result schemas, and fails on a fake tool, a missing fixture or result variant, an opaque known object, a changed default, or a stale generated reference. It is part of `bun run check` and requires no network or host process. Run `bun run contracts:generate` only as a deliberate, reviewed step when a contract change is intended — the generated file is checked for currency, so editing it by hand fails the gate.
743
+
744
+ `bun run contracts:host` proves the same contract against the real host. It requires a locally installed `opencode` at the pinned live version (1.18.32 recorded by the current evidence), a current `bun run build` (it exercises the built `dist/` plugins), and the existing evidence target directory `.grace/changes/active/C-AGENT-TOOL-CONTRACTS/`. The runner only checks that this directory exists: it does not validate the GRACE approval state and it does not accept an arbitrary active bundle. It never creates or recreates the directory, never writes into an archive, and never finalizes lifecycle state — approval and lifecycle are owned by the GRACE controller. It creates disposable HOME/XDG/config/workspace directories, uses only local built plugin URLs, synthetic agents/messages/credentials, and loopback OpenAI-compatible and Anthropic-compatible responders, and it sends no real provider or paid request. `bun scripts/check-tool-contracts-host.ts --probe` runs the minimal host-seam feasibility test, while the default full run executes the nine-tool matrix and writes `compatibility-evidence.json` only on a full observed pass; a missing executable, an unsupported host version, or any failed scenario exits nonzero instead of recording a skipped success. The generated evidence is one of three distinct levels and never stands in for the separately executed SDK-floor/normalizer unit fixtures (the pinned 1.18.2 projection plus OpenAI/Anthropic/Google lowering fixtures) or for real remote-provider acceptance and model quality.
745
+
715
746
  Git hooks are managed via `lefthook`.
716
747
 
717
748
  Smoke-test the built CLI against an isolated config home:
@@ -0,0 +1,144 @@
1
+ import type { ZodRawShape, ZodType } from "zod";
2
+ import { type ContractIssue, type OwnedToolContract } from "./agent-tool-contract.js";
3
+ /** One checked positive/negative input fixture with independent coverage metadata. */
4
+ export interface CatalogOperation {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly expect: "accept" | "reject";
8
+ readonly input: Record<string, unknown>;
9
+ /** Vocabulary field path to value this operation positively covers. */
10
+ readonly covers?: Readonly<Record<string, string>>;
11
+ /** Independent negative-scenario tags (e.g. unknown_key, missing_required). */
12
+ readonly tags?: readonly string[];
13
+ /** State/host prerequisites the fixture does not itself establish. */
14
+ readonly prerequisites?: readonly string[];
15
+ }
16
+ /** A model-facing closed vocabulary for one discriminated input field. */
17
+ export interface CatalogVocabulary {
18
+ readonly field: string;
19
+ readonly values: readonly string[];
20
+ }
21
+ /** An execute-time default applied by a tool schema. */
22
+ export interface CatalogDefault {
23
+ readonly field: string;
24
+ readonly value: unknown;
25
+ readonly note: string;
26
+ }
27
+ /** Declared meaning and rules for one path argument. */
28
+ export interface CatalogPathKind {
29
+ readonly field: string;
30
+ readonly kind: "exact-file" | "directory-subtree" | "absolute-file" | "url";
31
+ readonly rules: string;
32
+ }
33
+ /** One checked result-family fixture for a tool. */
34
+ export interface CatalogResultVariant {
35
+ readonly id: string;
36
+ readonly label: string;
37
+ readonly fixture: unknown;
38
+ }
39
+ /** Representative failure diagnostic path/expectation. */
40
+ export interface CatalogErrorExample {
41
+ readonly path: string;
42
+ readonly expectation: string;
43
+ }
44
+ /** Structural validation outcome exposed for fixture checks. */
45
+ export interface CatalogValidationResult {
46
+ readonly ok: boolean;
47
+ readonly data?: unknown;
48
+ readonly issues?: readonly ContractIssue[];
49
+ }
50
+ /** Aggregated catalog entry for one owned tool. */
51
+ export interface AgentToolCatalogEntry {
52
+ readonly toolId: string;
53
+ readonly summary: string;
54
+ readonly contract: OwnedToolContract<ZodRawShape>;
55
+ readonly validate: (raw: unknown) => CatalogValidationResult;
56
+ readonly resultSchema: ZodType;
57
+ readonly vocabularies: readonly CatalogVocabulary[];
58
+ readonly operations: readonly CatalogOperation[];
59
+ readonly defaults: readonly CatalogDefault[];
60
+ readonly prerequisites: readonly string[];
61
+ readonly conditionals: readonly string[];
62
+ readonly pathKinds: readonly CatalogPathKind[];
63
+ readonly results: readonly CatalogResultVariant[];
64
+ readonly errors: readonly CatalogErrorExample[];
65
+ readonly normalization: readonly string[];
66
+ }
67
+ /** Package-relative path of the generated reference document. */
68
+ export declare const CONTRACT_REFERENCE_PACKAGE_PATH = "templates/skills/vv-execute/references/tool-contracts.md";
69
+ /**
70
+ * Recorded pre-change contract-size baseline with provenance.
71
+ * Measured once from the clean starting commit; future checks compare against the
72
+ * catalog-derived current measurement rather than reading mutable git history.
73
+ */
74
+ export declare const CONTRACT_SIZE_BASELINE: {
75
+ readonly provenance: {
76
+ readonly commit: "f4319f8";
77
+ readonly sdk: "@opencode-ai/plugin@1.18.2";
78
+ readonly method: "source-extraction";
79
+ readonly detail: "UTF-8 byte sizes measured at the clean starting commit f4319f8 by extracting each owned tool's model-facing description string and projecting its pre-change registered argument map with the pinned SDK (io:\"input\"). This is a declaration/projection size, not a provider-exact token count and not a model-quality or model-effectiveness claim.";
80
+ readonly procedure: "Reproduce the baseline: `git show f4319f8:<tool-source> | extract the model-facing description`, then project each pre-change registered argument map with @opencode-ai/plugin@1.18.2 via tool.schema.toJSONSchema(schema, { io: \"input\" }) and sum Buffer.byteLength(value, \"utf8\"). \"After\" is the catalog-derived `measureCatalogContractSize()` value printed below as \"Current model-facing size\"; compare it to this baseline. Numbers reported are exact UTF-8 byte counts, never estimated token counts.";
81
+ };
82
+ readonly before: {
83
+ readonly descriptionBytes: 8796;
84
+ readonly inputSchemaBytes: 6815;
85
+ };
86
+ };
87
+ /** The nine owned tool catalog entries in stable registration order. */
88
+ export declare const agentToolCatalog: readonly AgentToolCatalogEntry[];
89
+ /** Sorted tool ids covered by the catalog. */
90
+ export declare const AGENT_TOOL_CATALOG_TOOL_IDS: readonly string[];
91
+ /** Sorted tool ids covered by the catalog. */
92
+ export declare function catalogToolIds(): string[];
93
+ /** Current description/input-schema UTF-8 byte measurement derived from the catalog. */
94
+ export declare function measureCatalogContractSize(entries?: readonly AgentToolCatalogEntry[]): {
95
+ descriptionBytes: number;
96
+ inputSchemaBytes: number;
97
+ totalBytes: number;
98
+ };
99
+ /**
100
+ * Run every catalog fixture through the actual exporting validator, and every
101
+ * result-family fixture through the actual result schema. This is the shared
102
+ * fixture gate used by both the catalog test and scripts/check-tool-contracts.ts.
103
+ */
104
+ export declare function validateAgentToolCatalog(): {
105
+ ok: boolean;
106
+ failures: string[];
107
+ checked: number;
108
+ };
109
+ /** One ancestor union option that a fixture must also satisfy for a nested branch. */
110
+ export interface ResultBranchAncestor {
111
+ readonly valuePath: readonly string[];
112
+ readonly schema: unknown;
113
+ }
114
+ /** One union branch location reachable from a result schema, with its fixture value path. */
115
+ export interface ResultBranchLocation {
116
+ readonly path: string;
117
+ readonly valuePath: readonly string[];
118
+ readonly schema: unknown;
119
+ readonly ancestors: readonly ResultBranchAncestor[];
120
+ }
121
+ export declare function collectResultBranches(schema: unknown): ResultBranchLocation[];
122
+ /** Result-schema union branches a single catalog entry's fixtures do not cover. */
123
+ export declare function resultBranchGaps(entry: AgentToolCatalogEntry): string[];
124
+ /** Result-schema union branches not covered by any catalog result fixture. */
125
+ export declare function resultCoverageGaps(entries?: readonly AgentToolCatalogEntry[]): string[];
126
+ /**
127
+ * Find arbitrary (non-closed) object or unconstrained nodes in a published input schema.
128
+ * A node is opaque when it accepts an arbitrary value: an object that does not close
129
+ * `additionalProperties` to false (including a missing `additionalProperties`), an empty
130
+ * or keyword-free schema, a `true` schema, or an unresolvable `$ref`. Known object
131
+ * properties, unions (`anyOf`/`oneOf`/`allOf`), array items, and resolvable local refs
132
+ * are visited. Genuine opaque input regions are not permitted for public inputs; there is
133
+ * no whole-tool exemption.
134
+ */
135
+ export declare function findOpaqueInputObjects(schema: unknown, basePath?: string, root?: unknown, visiting?: ReadonlySet<string>): string[];
136
+ /**
137
+ * Render the deterministic, on-demand tool-contracts reference from the catalog.
138
+ * No timestamps, absolute paths, or environment values are embedded, so the
139
+ * output is byte-stable and can be checked for currency by `bun run contracts:check`.
140
+ * Each checked example's outcome is derived from running the actual validator, so
141
+ * mislabeled metadata cannot be published as a passing example. `entries` is
142
+ * injectable so self-tests can validate an isolated catalog without the global one.
143
+ */
144
+ export declare function renderToolContractsReference(entries?: readonly AgentToolCatalogEntry[]): string;