Skip to content

SDK migration — baseline audit

This document captures the starting state for the autoloop → SDK split and tracks the 27 atomic commits that get us there. Keep it updated as each commit lands.

Migration plan

PhaseCommitsMilestoneTag
0 — baseline1This doc, clean check
1 — decouple in place10run() is embed-able (no workspaces yet)0.5.0-sdk.0
2 — workspaces + core/harness8@mobrienv/autoloop-{core,harness} published0.6.0
3 — specialty packages4presets, backends, dashboard extracted0.7.0
4 — docs + 1.03SDK docs, embed examples, frozen API1.0.0

Phase-1 checkpoints (the ones that do the heavy decoupling):

  • 1.1 split config.ts into pure schema + fs loader
  • 1.2 AbortSignal on RunOptions; CLI owns SIGINT/SIGTERM
  • 1.3 introduce LoopEvent + onEvent (duplicated w/ existing console.log)
  • 1.4 remove console.log from harness/iteration.ts + harness/stop.ts
  • 1.5 move harness/display.tscli/display.ts, event-printer shim
  • 1.6 move render* helpers out of harness/index.ts into cli/render.ts
  • 1.7 emit.ts returns a Result, no more process.exitCode
  • 1.8 run() becomes async
  • 1.9 public entry at src/index.ts + exports map
  • 1.10 embed smoke test asserting zero stdio leakage

Full plan prose: see commit-message body of 0.1 and the kickoff chat transcript.

Baseline state

Captured against commit 99819c9 (main, 10 unpushed).

Coverage

Full-repo baseline (npm run check at commit 99819c9 + pre-Phase-0 fixes):

Lines       57.13%   ( configured threshold: 50% )
Branches    81.33%   ( configured threshold: 75% )
Functions   69.40%   ( configured threshold: 60% )
Statements  57.13%
Test files  85 passed / 85
Tests       926 passed / 926

Hot spots for the migration (low line coverage worth watching as files move):

  • src/loops/watch.ts 46% — CLI-only; moves to cli/ in 1.5/1.6 area
  • src/harness/index.ts currently mixed — watch as we split render* in 1.6
  • src/registry/index.ts, src/worktree/index.ts 0% — barrel files; ignore

console.* calls outside CLI surface

67 calls in non-CLI source. Each one is a decoupling target (Phase 1).

FileCountHandled by
src/harness/display.ts261.5 (move to cli/)
src/memory.ts13library, keep but prefer return values; callers print
src/loops/watch.ts13CLI-only consumer; leave as-is (watch is a CLI mode)
src/harness/index.ts81.6 (move render* out)
src/topology.ts4library, replace with return values
src/harness/stop.ts11.4
src/harness/iteration.ts11.4
src/harness/emit.ts11.7

Everything under src/cli/, src/commands/, src/usage.ts, and src/dashboard/views/alpine-vendor.ts (vendored JS) is out of scope.

process.* side effects outside CLI surface

FileCallDecision
src/harness/index.tsprocess.on("SIGINT"/"SIGTERM"), process.kill(process.pid, signal)1.2 — replace with AbortSignal
src/harness/emit.tsprocess.exitCode = 0/1 (3×)1.7 — return Result
src/loops/watch.tsprocess.on("SIGINT")CLI-only (watch mode), keep
src/backend/acp-client.tsprocess.kill(-pid, ...) for child-process group teardownlegitimate, keep
src/backend/kiro-bridge.tsprocess.kill(-acpChildPid, "SIGTERM")legitimate, keep
src/loops/health.tsprocess.kill(pid, 0) (liveness probe)legitimate, keep

harness/ cross-dir imports

Hot spots (import targets, #-refs from src/harness/**):

9× ../utils.js            → packages/core (Phase 2.2)
7× ../json.js             → packages/core (2.2)
8× ../events/*            → packages/core (2.2)
4× ../topology.js         → packages/core (2.3)
4× ../markdown.js         → packages/core (2.2)
4× ../config.js           → split: schema→core (1.1), fs→core (2.4)
3× ../tasks.js            → packages/core (2.3)
3× ../registry/harness.js → packages/core (2.4)
3× ../agent-map.js        → packages/core (2.3)
2× ../memory.js           → packages/core (2.3)
2× ../backend/kiro-bridge → packages/backends (3.2)
1× ../worktree/*          → packages/core (2.4, registry-adjacent)
1× ../isolation/*         → packages/core (2.4)
1× ../profiles.js         → packages/core (2.3)
1× ../cli/color.js        → anomaly — journal-format.ts imports CLI; invert in 1.5

After Phase 2, harness/ should import only from @mobrienv/autoloop-core, @agentclientprotocol/sdk, and node builtins.

Pre-Phase-0 fixes folded into 0.1

Four fixes, all pre-existing flakes/leaks on a clean checkout. Rolled into the audit commit so the baseline is actually green:

  • test/commands/list.test.ts — leaked user-level presets via $XDG_CONFIG_HOME/autoloop/presets. Fix: isolate XDG_CONFIG_HOME to a temp dir in beforeAll.
  • presets/autopreset/README.md, presets/autodebug/README.md — description first lines violated the "Use when/after" convention enforced by the list test. Fix: reworded to comply.
  • test/harness/automerge-chain.test.tsvi.mock("src/worktree/create.js") was missing tryResolveGitRoot (added to source later). Fix: add it to the mock return.
  • vitest.config.ts — worktree + integration tests spawn git subprocesses and were timing out at the default 5s under parallel load (observed 4.2–4.6s isolated, intermittent fail in full run). Fix: bump testTimeout to 15s.

Coverage-threshold drift (resolved in Phase 2.8)

AGENTS.md declares a ≥90% line / ≥90% branch gate. vitest.config.ts is currently set to lines: 50, branches: 75, functions: 60. Phase 2.8 owns the ratchet: each extracted package gets its own vitest.config.ts with a gate sized to its surface (core/harness aim for 90/90; CLI-heavy packages ratchet upward each release). Root-level config stays loose until 2.6 empties the root src/.

Checkpoint log

#StatusCommit
0.1c442991 baseline audit
1.1d9342db split config.ts schema / fs
1.2eb69649 AbortSignal on RunOptions
1.31bf9c2e LoopEvent + onEvent
1.4d6a87b0 drop console.log from iteration.ts + stop.ts
1.51ace8fc route harness display calls via LoopEvent; add cli/event-printer.ts
1.6bb1e33d render*cli/render.ts; drop unused parentPort
1.78fc1d3f emit.ts returns EmitResult; process.* side effects moved to main.ts
1.842438d6 async run() + cascading awaits
1.9012979b public src/index.ts + exports map
1.10ad82906 SDK embed smoke test
9cafb25 autosde follow-up: cliPrintEvent coverage
version bump → 0.5.0-sdk.0
2.12244a26 enable npm workspaces
2.245bd208 core: events/journal/markdown/json/utils/topology/config-schema
2.3759b8f7 core: domain models (agent-map, tasks, memory, profiles)
2.4eaa1728 core: registry + config-fs + isolation
2.56630bed extract packages/harness
2.6c2f1a7a extract packages/cli (keeps @mobrienv/autoloop)
2.7cf81dbc resolveBundleRoot via require.resolve
2.8per-package coverage gate (core/harness → 90/90) → publish 0.6.0
3.1extract packages/presets (data-only; resolved via @mobrienv/autoloop-presets)
3.2extract packages/backends (+ simplify: drop dead run-kiro.ts, inline run-mock/run-pi, shared kiro-ipc.ts helpers, decouple BackendCommandContext from LoopContext)
3.2basync-native kiro path — iteration loop made async, kiro-bridge.ts/kiro-worker.ts/kiro-ipc.ts deleted (-251 LOC)
3.3extract packages/dashboard (+ move categorizeRuns/policyForPreset@mobrienv/autoloop-core/runs-health; inject listPresets via DashboardContext)
3.4bin/release script + multi-workspace publish workflow → ready to tag v0.7.0
4.1SDK docs
4.2embed examples
4.31.0.0