Intent
The domain layer imports the HTTP layer. packages/cli/src/task-review.ts:34 reads:
import { createSession } from './serve.js'
createSession and the LiveSession type it returns live in serve.ts (:82, :102), the module that also owns the node:http server, the route table, the static file handler and the SSE writers. serve.ts imports task-server.ts in return (type-only), and task-server.ts imports task-review.ts.
LiveSession is not transport. It is an in-memory event hub: subscribe, publish, hold the last state so a late subscriber can catch up. SSE is one way to serialise it, the CLI renderers are another. Placing it in the HTTP module makes every domain module that wants to publish progress depend on the web server.
The practical cost is that serve.ts cannot be changed or replaced without touching the domain, and a headless run still pulls the HTTP module in. The rest of the task-*.ts split is genuinely respected by the imports; this one edge points the wrong way.
Not a defect in behaviour: nothing is broken today. Filed as an enhancement so the boundary is recorded rather than rediscovered at the next change to serve.ts.
Scope
In scope:
- Moving
LiveSession/createSession out of serve.ts into a module the domain can depend on.
- The resulting import direction between domain and transport.
Out of scope:
- The SSE wire format and the routes, which stay in
serve.ts.
- Splitting
task-server.ts or task-runner.ts, a separate discussion.
- Any behaviour change: this is a move, not a redesign.
Approach
Extract the session hub into its own module (alongside server-context.ts, which already holds shared server state), leaving serve.ts as a consumer that subscribes and serialises to SSE. Domain modules then import the hub, never the server.
MODIFIED Requirements
Requirement: The domain MUST NOT depend on the HTTP layer
The system SHALL keep progress publication independent of the HTTP server, so that domain modules depend on the session hub and the server depends on both.
Scenario: a headless run
- GIVEN a review executed with no web server started
- WHEN the domain publishes progress events
- THEN no module owning HTTP routes or SSE writers is imported
Scenario: the transport is changed
- GIVEN a change to the SSE frame format in
serve.ts
- WHEN the change is made
- THEN no file under the
task-* domain modules needs editing
Tasks
1. Extract
2. Verify the direction
3. Prove it
Intent
The domain layer imports the HTTP layer.
packages/cli/src/task-review.ts:34reads:createSessionand theLiveSessiontype it returns live inserve.ts(:82,:102), the module that also owns thenode:httpserver, the route table, the static file handler and the SSE writers.serve.tsimportstask-server.tsin return (type-only), andtask-server.tsimportstask-review.ts.LiveSessionis not transport. It is an in-memory event hub: subscribe, publish, hold the last state so a late subscriber can catch up. SSE is one way to serialise it, the CLI renderers are another. Placing it in the HTTP module makes every domain module that wants to publish progress depend on the web server.The practical cost is that
serve.tscannot be changed or replaced without touching the domain, and a headless run still pulls the HTTP module in. The rest of thetask-*.tssplit is genuinely respected by the imports; this one edge points the wrong way.Not a defect in behaviour: nothing is broken today. Filed as an enhancement so the boundary is recorded rather than rediscovered at the next change to
serve.ts.Scope
In scope:
LiveSession/createSessionout ofserve.tsinto a module the domain can depend on.Out of scope:
serve.ts.task-server.tsortask-runner.ts, a separate discussion.Approach
Extract the session hub into its own module (alongside
server-context.ts, which already holds shared server state), leavingserve.tsas a consumer that subscribes and serialises to SSE. Domain modules then import the hub, never the server.MODIFIED Requirements
Requirement: The domain MUST NOT depend on the HTTP layer
The system SHALL keep progress publication independent of the HTTP server, so that domain modules depend on the session hub and the server depends on both.
Scenario: a headless run
Scenario: the transport is changed
serve.tstask-*domain modules needs editingTasks
1. Extract
LiveSession,createSessionand their state out ofserve.tsinto a dedicated moduletask-review.tsand any other domain importer at the new module2. Verify the direction
task-*.tsmodule importsserve.jsany moreserve.tsstill owns the routes, the static handler and the SSE writers3. Prove it