Skip to content

feat: agent accessibility (natspec, discovery docs, @splits/pact cli + mcp + skill) - #67

Closed
r0ohafza wants to merge 6 commits into
mainfrom
feat/agent-accessibility
Closed

feat: agent accessibility (natspec, discovery docs, @splits/pact cli + mcp + skill)#67
r0ohafza wants to merge 6 commits into
mainfrom
feat/agent-accessibility

Conversation

@r0ohafza

@r0ohafza r0ohafza commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Implements the agent-accessibility spec (.scratch/agent-accessibility/spec.md, signed off 2026-08-24): NatSpec on the contracts, agent-facing docs and discovery files, and the @splits/pact CLI / MCP server / skill package.

Changed user flows

  • Browser app: none behaviorally. Per-page <meta name="description"> / og:description text now differs per page (was one shared sentence).
  • New static routes on the site: /llms.txt, /.well-known/pact.json, /docs/{architecture,contracts,integrate}.md, rendered at prebuild from src/generated/offering-contracts.ts (build fails if a landing doc carries a different factory address or deploy block).
  • New packages/pact (npx @splits/pact): 26 commands across offering, buy, funds, fail, voucher, admin, config; same table as MCP stdio tools via --mcp and as generated skill files via skills add (plus the hand-written pact knowledge skill). Writes go through one guardrail path: factory-child check by OfferingCreated scan before any USDC approve, per-tx eth_call preflight with decoded reverts, unsigned transaction output by default, signed sends only with PACT_PRIVATE_KEY, --dry-run on every write, non-zero exit whenever nothing was sent. Voucher issuance persists to ~/.pact/ledger/<chainId>/<offering>.json (same row shape as the browser ledger; union-by-id, revokedAt wins).
  • Contracts: NatSpec only, no ABI or bytecode change.

Test coverage changed

  • Contracts: forge test (87) unchanged; forge build + build:contracts regenerate identically.
  • Discovery: src/lib/discovery.test.ts (manifest + llms.txt shape), src/lib/og.test.ts updated for per-page descriptions; tests/og-metadata.spec.ts still covers the rendered tags.
  • CLI unit: packages/pact/src/cli.test.ts (12 scenarios over a fake chain: chain pin, factory-child refusal before approve, preflight abort, unsigned vs key mode, --dry-run, --max-cost, voucher round trip in both modes), ledger.test.ts (merge rule, file storage).
  • CLI e2e: tests/pact-cli.spec.ts executes the bin from the npm pack tarball against the shared anvil: config → create → quote → buy (key mode and unsigned relay) → voucher issue/complete → buy private → withdraw → cap-table; failure path (mark, key-mode and unsigned refund, sweep); guardrail negatives (foreign address leaves allowance at 0, PACT_CHAIN_ID mismatch, non-owner close fails preflight with nothing sent); MCP stdio smoke (tools/list = 26, offering_get).
  • CI: test:e2e now builds the package first; check:e2e-impact covers packages/pact/**; tsconfig/vitest/oxlint/prettier include the workspace.

Validation

  • npm run typecheck
  • npm test
  • npm run test:contracts
  • npm run test:e2e
  • Manual browser review, when user-facing behavior changed (dev server serves the new static files; skills add installed 8 skills into a scratch dir; --mcp answered initialize/tools/list under a hand-rolled stdio client)

Full npm run validate result: passed (typecheck, oxlint, prettier, 98 unit tests, forge fmt + 87 forge tests, build:contracts, 15 Playwright tests).

E2E impact override: none

Deviations from the spec text

  • The spec's literal factory/deploy block (0xE07b… / 49935597) predate the current pin (0x68DA… / 50274529); every surface derives from the pin per the spec's own hard-fact rule. The spec's "tools/list = 25" is 26 with config, which its §4.2 table lists.
  • Guardrails are one shared runWrite path plus assertFactoryChild rather than literal incur middleware (middleware cannot see command args).
  • Added beyond the spec's env list: PACT_FACTORY_DEPLOY_BLOCK (needed whenever the factory is overridden, e.g. anvil). Env vars are documented under pact config --help since incur does not render a root env schema.
  • @modelcontextprotocol/server (pinned alpha, same as incur's) is a declared runtime dependency kept external to the bundle; the e2e symlinks the repo's node_modules in place of a registry install.
  • The MCP smoke uses a ~30-line JSON-RPC stdio client instead of StdioClientTransport.

Still manual after merge

npm publish -w packages/pact --access public, the splits.org DNS TXT for mcp-publisher login dns, mcp-publisher publish, then the awesome-agent-skills PR (spec §6–§7). Until publish, the npx @splits/pact pointers on the site and README resolve to nothing.

Every public/external function now carries @param/@return, and the getters
agents poll (state, minMet, raised, publicUnits, unitsSold, deposits,
allocationConsumed) carry @notice. No ABI change; userdoc/devdoc reach
explorers on the next instance verification.
…n block

Adds docs/integrate.md as the canonical direct-call guide and a prebuild
step that renders /llms.txt, /.well-known/pact.json, and markdown mirrors
under /docs/ from the generated contract pin, failing the build if a
landing doc carries a different factory address. Per-page OG descriptions.
Adds the packages/pact workspace: one incur command table (26 commands
across offering, buy, funds, fail, voucher, admin, config) served as an
argv CLI, an MCP stdio server via --mcp, and generated skill files via
skills add, with a hand-written knowledge skill included. Writes go
through one guardrail path: factory-child check before any approve,
eth_call preflight per transaction, unsigned transaction output by
default and signed sends only with PACT_PRIVATE_KEY. Vouchers persist to a
file ledger sharing the browser codec. The e2e spec drives the npm pack
tarball against anvil in both signing modes plus an MCP stdio smoke.
Dedupe the offering arg schema, token parsing, and call encoder; derive
server.json env docs from the env schema; add examples to every command;
check the USDC balance behind the unsigned-mode buy preflight; refuse to
start the MCP server on a chain mismatch; keep the MCP server dependency
external to the bundle; guard the deploy block in docs/integrate.md; and
exercise an unsigned refund relay in the e2e.
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pact Ready Ready Preview Aug 25, 2026 5:56pm

Request Review

One client-parameterised chain module, consumed by two adapters: the
browser (wagmi) and the @splits/pact CLI (viem). Contract shape, event
decoding, the scan policy (whole range first, 10k-block chunks on
failure), the factory-child invariant, and the buy plan now exist once.

- packages/core (private, zero-build, inlined into the CLI bundle): the
  moved pure modules and generated ABIs, plus chain/reads.ts
  (readOffering, quote, decoders, scan*, findFactoryChild, capTable) and
  chain/writes.ts (Call, encoders, approveCall, planBuy). Amounts stay
  bigint; records carry blockNumber/transactionHash/logIndex.
- Browser: onchain.ts is the send adapter (sendCalls over EIP-5792 or
  sequential transactions, buys built from core's plan); offerings.ts is
  the cache adapter (bigint stored as decimal strings, corrupt or
  pre-core caches rescan). The eight lifecycle wrappers are gone; pages
  send offeringCall(record.offering, ...) and buys take an OfferingRecord.
- CLI: reads.ts deleted, buys go through planBuy, assertFactoryChild
  returns the record, serializeOffering moves to format.ts.
- Tests follow their modules; one shared fake ChainClient with encoded
  logs. Docs name the new paths; contracts.md mermaid batchRefund ->
  refundAll; contracts/README.md joins the pinned-address check.
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