Skip to content

refactor(blast,cli,serve): Zod-Core-Out architecture — vertical slice for blast #109

@stackbilt-admin

Description

@stackbilt-admin

Context

Charter's primitives are exposed through three surfaces: the programmatic API (@stackbilt/blast, @stackbilt/validate, etc.), the CLI (charter <cmd>), and the MCP server (charter serve). Today these surfaces drift:

  • API contracts are TypeScript interfaces — compile-time only, no runtime validation, no machine-readable schema.
  • CLI commands parse argv and format output against those types manually.
  • charter serve exposes ADF-curated context as MCP resources but registers zero server.tool(...) calls — so agents can read governance but can't invoke primitives.

Result: adding a parameter to a core function means touching the API, the CLI adapter, any MCP tool wrapper, and the docs independently. The surfaces can silently disagree.

Proposal: Zod-Core-Out pattern

Make the Zod schema the single source of truth for each primitive's input/output. CLI and MCP become thin adapters that translate their respective input formats (argv, JSON-RPC) into the same schema, call the same pure core function, and format the same structured result for their respective consumers.

┌─────────────────────────┐
│  @stackbilt/blast       │   Pure core + Zod schemas
│  - BlastInputSchema     │   - z.describe() for LLMs
│  - BlastOutputSchema    │   - z.infer<> → TS types
│  - blastRadius()        │   - No I/O, no chalk, no prompts
└──────────┬──────────────┘
           │
     ┌─────┴─────┐
     ▼           ▼
┌─────────┐ ┌─────────────┐
│  CLI    │ │  MCP tool   │
│  argv → │ │  JSON-RPC → │
│  schema │ │  schema     │
│  → core │ │  → core     │
│  → ANSI │ │  → JSON     │
└─────────┘ └─────────────┘

Vertical slice: blast

Scope this PR to @stackbilt/blast end-to-end. Every other package follows the same template afterward.

Additive only (per OSS update policy):

  • Add BlastInputSchema and BlastOutputSchema to packages/blast/src/index.ts.
  • Keep existing BlastOptions / BlastRadiusResult interfaces — re-export as z.infer<> aliases so no consumer breaks.
  • Add .describe() strings on every schema field — this is the LLM-facing documentation that MCP clients surface.
  • Register charter_blast as an MCP tool in packages/cli/src/commands/serve.ts via server.tool(...) — it validates with BlastInputSchema.parse, calls blastRadius, returns structured JSON.
  • Refactor packages/cli/src/commands/blast.ts to parse argv → BlastInputSchema → core → formatter. Human output unchanged.

Acceptance criteria

  • @stackbilt/blast exports Zod schemas alongside existing types; BlastRadiusResult remains a valid type import.
  • charter blast <path> output is byte-identical to current behavior (snapshot test).
  • charter serve registers charter_blast as an MCP tool callable from Claude Code; tool schema is generated from Zod via zod-to-json-schema.
  • Unit tests cover the core function with schema-validated inputs only (no child-process tests).
  • No removal or rename of existing public API.

Why this first

blast is the right proof-of-concept because:

  • The package is already dependency-free and headless — minimal refactor cost.
  • Blast radius is an obvious agent-use-case ("what breaks if I change this file?").
  • Once the pattern is proven, validate, drift, classify, and surface each become a ~1-day port.

Out of scope

  • SSE transport for charter serve (separate issue).
  • MCP tool wrappers for other packages (follow-up issues once the pattern lands).
  • Breaking changes to existing types (policy: additive only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttype:refactorCode restructuring without behavior change

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions