Skip to content

feat(cli): add ao migrate to port legacy projects + settings into the rewrite daemon#2127

Closed
harshitsinghbhandari wants to merge 3 commits into
mainfrom
feat/ao-migrate-projects
Closed

feat(cli): add ao migrate to port legacy projects + settings into the rewrite daemon#2127
harshitsinghbhandari wants to merge 3 commits into
mainfrom
feat/ao-migrate-projects

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

What

Adds ao migrate — the legacy side of the legacy → rewrite (Go/Electron) data migration. It ports the registered project registry and per-project settings into the new AO daemon's SQLite store.

It does not open ~/.ao/data/ao.db directly. The rewrite daemon is the sole writer of that DB (the CLI/doctor are forbidden from opening it). So ao migrate mirrors the rewrite's own ao project add flow over the daemon's loopback REST API:

POST /api/v1/projects             { path, projectId?, name? }
PUT  /api/v1/projects/{id}/config { config }
  • Reads the legacy effective config via loadConfig(getGlobalConfigPath()) (same source ao update already trusts) — global identity merged with each project's local agent-orchestrator.yaml behavioral config.
  • Maps project config per the spec mapping (see below): permission enum remap, harness remap, dropped-field accounting.
  • --dry-run prints the plan with no network calls. --daemon-url / AO_DAEMON_URL override the target (default http://127.0.0.1:3001).
  • Idempotent: a project already present comes back 409 and is reported as skipped.

Spec source

Implements the legacy-side, projects-only slice of aoagents/ReverbCode#247 (sections 1 + 3: project mapping and the config blob mapping, incl. the permission enum remap and the §4 dropped-field set).

Scope (deliberately narrow for this cut)

  • ✅ Projects + per-project settings.
  • ❌ Sessions, PRs, notifiers, and global config — not migrated here (sessions deferred per maintainer direction).

Why this shape (verification notes)

I cloned aoagents/ReverbCode and verified the contract before building. Key findings that shaped the implementation:

  • The rewrite-side importer described in fix: use JSONL-based activity detection in lifecycle manager #247 §5 (ao migrate import CLI / POST /api/v1/migrate/import endpoint / InsertSessionVerbatim) does not exist yet — so there is nothing to hand off to. The existing POST /api/v1/projects controller is the locked, contract-respecting write path, so this command uses that instead of an unbuilt endpoint.
  • The daemon binds loopback-only with no auth; its CORS gate lets origin-less (CLI) requests through, so the client must not send an Origin header (we don't).
  • repo_origin_url, registered_at, and kind are server-resolved and cannot be supplied in the create body — so we don't try to.
  • fix: use JSONL-based activity detection in lifecycle manager #247 §1 assumes everything lives in config.yaml projects.<id>; in reality the global config holds identity fields only and behavioral settings live in each project's local agent-orchestrator.yaml. loadConfig merges both, so we read the merged effective config.
  • fix: use JSONL-based activity detection in lifecycle manager #247 G11's "legacy parallel goose store at ~/.ao/data/ao.db" does not match this codebase — the legacy TS side never writes that path (its only SQLite store is ~/.agent-orchestrator/activity-events.db). That collision is purely rewrite-side, so this command does not touch ~/.ao.
  • Dropped the AO_PORT fallback for the daemon URL: AO_PORT is overloaded (it points at the legacy dashboard, 3000, in a legacy environment), so falling back to it risked POSTing project payloads at the wrong server. Targeting the rewrite daemon is explicit.

Tests

packages/cli/__tests__/lib/migrate.test.ts — 26 tests covering the pure mapping (permission/harness remap, config blob construction, lossy-suggest + dropped-field notes, id validation, daemon-url resolution) and runMigrate orchestration against an injected fetch + config (create, 409-skip, error envelope, config-write-failure-keeps-project, degraded/invalid-id skips, daemon-unreachable).

pnpm build, pnpm -r typecheck, and lint (0 errors on the new files) are green. Smoke-tested --dry-run and the unreachable-daemon path against a real legacy config.

Changeset: @aoagents/ao-cli minor.

harshitsinghbhandari and others added 3 commits June 16, 2026 18:10
… daemon

Implements the legacy half of the legacy->rewrite migration spec
(aoagents/ReverbCode#247 sections 1 + 3): a new `ao migrate` command that
ports the registered project registry and per-project settings into the
rewrite (Go/Electron) daemon over its loopback REST API, mirroring the
rewrite's own `ao project add` flow (POST /api/v1/projects then
PUT /api/v1/projects/{id}/config). Sessions are out of scope for this cut.

Respects the rewrite invariant that the daemon is the sole writer of
~/.ao/data/ao.db (the CLI never opens the DB).

Ref: aoagents/ReverbCode#247

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ref: aoagents/ReverbCode#247

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AO_PORT is overloaded — in a legacy environment it points at the legacy
Next.js dashboard (3000), not the rewrite daemon (3001). Falling back to it
could silently POST project-create payloads at the wrong server. Targeting
the rewrite daemon is now explicit: --daemon-url or AO_DAEMON_URL, else the
rewrite's 3001 default.

Ref: aoagents/ReverbCode#247

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test Coverage Report

Metric Value
Lines covered 350/452
Lines not covered 102/452
Overall coverage 77.4%
Per-file breakdown
File Coverage
packages/cli/src/commands/migrate.ts 15/114 (13.2%)
packages/cli/src/lib/migrate.ts 272/274 (99.3%)
packages/cli/src/program.ts 63/64 (98.4%)

Uncovered lines

  • packages/cli/src/commands/migrate.ts: L27-L28, L30-L36, L38-L53, L55-L68, L70, L72-L79, L81, L85-L89, L91-L92, L94-L97, L99-L103, L105-L110, L112-L119, L121-L125, L127-L141
  • packages/cli/src/lib/migrate.ts: L368-L369
  • packages/cli/src/program.ts: L65

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator Author

Closing in favor of a unified OFFLINE ao migrate (projects + sessions via direct-DB into ao.db) — see #2129. This PR's online POST /api/v1/projects approach is superseded: sessions have no online insert path on the rewrite, so one offline tool (daemon stopped) is cleaner than mixing daemon states. The project mappers here are reused by #2129.

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.

1 participant