Skip to content

refactor(io): extract a shared Input/Processing/Output manager tier#11

Merged
burrows99 merged 1 commit into
masterfrom
feat/io-manager-tier
Jun 19, 2026
Merged

refactor(io): extract a shared Input/Processing/Output manager tier#11
burrows99 merged 1 commit into
masterfrom
feat/io-manager-tier

Conversation

@burrows99

Copy link
Copy Markdown
Owner

Why

The use-case commands (DynamicCommand, GraphCommand, …) were already transport-neutral, but everything around them — accepting/validating input, orchestrating the run (collector + streaming + abort handling), and shaping output — lived welded inside the 398-line Cli, coupled to process.stdout / process.exit / writeFileSync. No second frontend (an MCP server, an HTTP "run" endpoint, …) could reuse it without copy-pasting that orchestration.

What

A new src/io/ middle tier the CLI — and any future frontend — sits on:

raw → InputManager.accept* → ProcessingManager.run* → OutputManager.emit
Class Owns
InputManager normalization: pickTarget, --urlgoto: step assembly, secret redaction, meta.args shaping
InputValidator the input rules: the run guards + strict DTO/step/graph validation — throws InputError (never process.exit)
ProcessingManager collector resolve + serialized emit-chain + onProgress + abort flush (EngineAbortError) + the static explicit-only forward; owns emitFailureMessage
OutputManager condense + render-vs-JSON + --json/--html file contents + exit code; returns a descriptor, writes nothing
OutputValidator the envelope schema gate (trace.validate()E_SCHEMA → recompute ok)

Cli is now a thin adapter: argv → managers → stdout/files/exit, mapping InputErrorusage()/exit 2 and EngineAbortError→exit 1. build()/run() and the command surface are unchanged; condense/emitFailureMessage are re-exported from Cli.ts so existing import points keep resolving. Adds Code.INPUT for the structured input error.

Behavior-preserving

The entire pre-existing suite passes with no test edits — that's the regression gate. Verified by smoke tests that input-error wording + exit codes, dynamic success/abort paths, and --html output are byte-identical.

Tests

New unit tests cover the tier directly: input guards + the security-critical step redaction (a typed password / eval body never reaches meta.args), output descriptors + the schema gate, processing abort/emit-folding/forwarding, and both validators at their own boundary.

77 tests · 76 pass · 1 skip (the Postgres round-trip needs a live DB) · typecheck clean.

Not in this PR (intentionally)

The MCP and HTTP-run adapters are deferred — the point of this PR is the seam that makes each a thin add later.

🤖 Generated with Claude Code

The use-case commands were already transport-neutral, but everything around
them — accepting/validating input, orchestrating the run (collector +
streaming + abort handling), and shaping output — lived welded inside the
398-line Cli, coupled to process.stdout/process.exit/writeFileSync. No second
frontend (MCP, HTTP, …) could reuse it.

Introduce src/io/, a middle tier the CLI (and any future frontend) sits on:

  raw → InputManager.accept* → ProcessingManager.run* → OutputManager.emit

- InputManager: pickTarget, --url→goto step assembly, secret redaction,
  meta.args shaping. Delegates the RULES to InputValidator (the guards, the
  strict DTO/step/graph checks), which throws InputError instead of exiting.
- ProcessingManager: collector resolve + serialized emit chain + onProgress +
  the abort flush (EngineAbortError) + the static explicit-only forward. Owns
  emitFailureMessage. No stdout/exit.
- OutputManager: condense + render-vs-JSON + --json/--html file CONTENTS +
  exit code; the schema gate is delegated to OutputValidator. Returns a
  descriptor; writes nothing.
- Cli is now a thin adapter: argv → managers → stdout/files/exit, mapping
  InputError→usage()/exit 2 and EngineAbortError→exit 1. build()/run() and the
  command surface are unchanged; condense/emitFailureMessage are re-exported so
  existing import points keep resolving.

Behavior-preserving: the full pre-existing suite passes with no test edits.
Adds Code.INPUT for the structured input error. New unit tests cover the tier
directly (input guards + the security-critical step redaction, output
descriptors + schema gate, processing abort/emit-folding/forward, and both
validators). 77 tests, 76 pass, 1 skip (Postgres needs a live DB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 19, 2026 10:43
@burrows99 burrows99 merged commit 6929de3 into master Jun 19, 2026
2 checks passed
@burrows99 burrows99 deleted the feat/io-manager-tier branch June 19, 2026 10:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the CLI’s IO/orchestration layer into a reusable, transport-neutral middle tier (src/io/) so future frontends (e.g. MCP server / HTTP endpoint) can share input acceptance/validation, run orchestration, and output shaping without duplicating CLI-specific stdout/exit/filesystem logic.

Changes:

  • Introduces a new IO tier (InputManager/InputValidator, ProcessingManager, OutputManager/OutputValidator) with structured InputError / EngineAbortError control flow.
  • Updates Cli to become a thin adapter (argv → managers → stdout/files/exit), re-exporting condense and emitFailureMessage to preserve existing import paths.
  • Adds new unit tests covering the validators and IO managers directly (including step redaction, schema gating, abort/emit behavior).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/io-validators.test.js Adds isolated unit tests for InputValidator and OutputValidator boundary behavior.
test/io-processing.test.js Adds unit tests for ProcessingManager orchestration, collector emit folding, and abort handling.
test/io-output.test.js Adds unit tests for OutputManager descriptors (stdout/files/logs/exitCode) and schema gate effects.
test/io-input.test.js Adds unit tests for InputManager normalization, guard mapping, and security-critical step redaction.
src/shared/codes.ts Adds Code.INPUT (E_INPUT) for structured input-validation failures.
src/io/ProcessingManager.ts New orchestration manager for dynamic/static runs plus EngineAbortError and emitFailureMessage.
src/io/OutputValidator.ts New schema-gate validator that turns structural violations into E_SCHEMA diagnostics and recomputes ok.
src/io/OutputManager.ts New output manager that produces output descriptors (no I/O) and applies --concise / --json / --html policy.
src/io/InputValidator.ts New validator split from normalization: guards + strict DTO/step/graph validation, throws InputError.
src/io/InputManager.ts New input manager that normalizes parsed inputs into typed requests and redacts secrets in meta.args.
src/io/InputError.ts New structured input error type (code, problems) for non-CLI frontends and CLI usage mapping.
src/io/index.ts Public IO-tier exports for reuse by future frontends.
src/io/descriptors.ts Transport-neutral raw/normalized/request/result descriptor types shared across the IO tier.
src/cli/Cli.ts Refactors CLI to delegate to IO managers; retains behavior via adapter responsibilities and re-exports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants