refactor(io): extract a shared Input/Processing/Output manager tier#11
Merged
Conversation
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>
There was a problem hiding this comment.
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 structuredInputError/EngineAbortErrorcontrol flow. - Updates
Clito become a thin adapter (argv → managers → stdout/files/exit), re-exportingcondenseandemitFailureMessageto 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-lineCli, coupled toprocess.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:InputManagerpickTarget,--url→goto:step assembly, secret redaction,meta.argsshapingInputValidatorInputError(neverprocess.exit)ProcessingManageronProgress+ abort flush (EngineAbortError) + the static explicit-only forward; ownsemitFailureMessageOutputManagercondense+ render-vs-JSON +--json/--htmlfile contents + exit code; returns a descriptor, writes nothingOutputValidatortrace.validate()→E_SCHEMA→ recomputeok)Cliis now a thin adapter:argv → managers → stdout/files/exit, mappingInputError→usage()/exit 2 andEngineAbortError→exit 1.build()/run()and the command surface are unchanged;condense/emitFailureMessageare re-exported fromCli.tsso existing import points keep resolving. AddsCode.INPUTfor 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
--htmloutput are byte-identical.Tests
New unit tests cover the tier directly: input guards + the security-critical step redaction (a typed password /
evalbody never reachesmeta.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