# Tasks: add-import-sse-progress ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 1. OTel — extend instruments bundle - [ ] Add `sseConnected: Counter` (`import.sse.connected`) to `ImportInstruments`. - [ ] Add `sseEventsSent: Counter` (`import.sse.events_sent`) labelled by `kind`. - [ ] Add `sseDisconnected: Counter` (`import.sse.disconnected`) labelled by `reason`. - [ ] Update `src/otel/instruments.md` to describe the three new counters. - [ ] Extend `src/otel/instruments.test.ts` to assert the new counters round-trip through a fresh `MeterProvider`. ## 2. Route — `GET /:id/progress` - [ ] Create `src/routes/progress-sse/progress-sse.ts` exporting `registerProgressSseRoute(app, deps)`. - [ ] Wire the handler into `createImportRoutes` in `src/routes/build-routes.ts`. - [ ] Subscribe to `service.bus` (`import.progress`, `import.completed`, `import.failed`) — NO polling. - [ ] Forward the request `AbortSignal` to detach the listener on client disconnect. - [ ] Frame format: `id: -`, `event: progress|complete|failed`, JSON-stringified `ImportProgress` payload, double-newline terminator. - [ ] Initial frame: snapshot from `service.getJob(id, ownerId)` so the client sees current state before the first bus tick. - [ ] Terminal events (`complete` / `failed`) close the stream cleanly. ## 3. Errors - [ ] Reuse `IMPORT.NOT_FOUND` for foreign-owner / missing jobs (404 problem+json). - [ ] Reuse `IMPORT.UNAUTHENTICATED` (via `requireAuth()` middleware already in `build-routes.ts`) for the 401 path. - [ ] No new error codes added. ## 4. Tests - [ ] `src/routes/progress-sse/progress-sse.test.ts` covering eight scenarios listed in `proposal.md#source-mapping`. - [ ] Tests use Hono's `app.request()` returning a `Response` with a streamed body; assertions read via `body.getReader()` and decode UTF-8. - [ ] No mocks. Real `ImportService`, real Hono, real in-memory `MeterProvider`. ## 5. Docs - [ ] `src/routes/progress-sse/progress-sse.md` (≥200 words, Purpose / Features / Usage Basic / Usage Advanced / API Reference / Implementation Notes). - [ ] Update package `README.md` Features list and routes table to include the new SSE endpoint. ## 6. Subpath export - [ ] Add `./routes/progress-sse/progress-sse` to `package.json#exports`. ## 7. Gates (in order) - [ ] `bunx biome check src/` - [ ] `bunx turbo lint typecheck test --filter=@suaveplan/api-plugin-import` - [ ] `bunx turbo test:coverage --filter=@suaveplan/api-plugin-import` (100/100/100/100) - [ ] `bun scripts/openspec-conformance.ts @suaveplan/api-plugin-import` (0 errors) - [ ] `bunx turbo build --filter=@suaveplan/api-plugin-import` ## 8. Archive - [ ] Fold spec delta into `openspec/specs/api-plugin-import/spec.md`. - [ ] `mv openspec/changes/add-import-sse-progress openspec/archive/2026-05-20-add-import-sse-progress`.