diff --git a/.env.example b/.env.example index f8e9174..583a6d6 100644 --- a/.env.example +++ b/.env.example @@ -11,11 +11,11 @@ SUPABASE_SERVICE_ROLE_KEY= SUPABASE_JWT_SECRET= # Optional daemon polling interval for autonomous Supabase sync. -TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS=5000 -TEAMBRIDGE_RELAY_PRESENCE_INTERVAL_MS=15000 -TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS=50 -TEAMBRIDGE_CHECKPOINT_LEASE_MS=60000 +COORD_RELAY_SYNC_INTERVAL_MS=5000 +COORD_RELAY_PRESENCE_INTERVAL_MS=15000 +COORD_CHECKPOINT_INTERVAL_EVENTS=50 +COORD_CHECKPOINT_LEASE_MS=60000 # Optional local CLI convenience only; prefer passing login flags in shared shells. -TEAMBRIDGE_EMAIL= -TEAMBRIDGE_PASSWORD= +COORD_EMAIL= +COORD_PASSWORD= diff --git a/.gitignore b/.gitignore index ceb4014..c8a63bd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ self/ node_modules/ dist/ *.tsbuildinfo -.teambridge/ +.coord/ .env .env.* !.env.example diff --git a/CLAUDE.md b/CLAUDE.md index a4e2414..8a8b6fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ first — this file covers how to build, test, and navigate the code. ## What this is -**Condominium** (npm name `teambridge`) — a shared workspace for teams building +**Coord** (npm name `coord`) — a shared workspace for teams building with AI coding agents. Multiple developers work on the same task in separate git branches while sharing context through a vault, a local daemon, a dashboard, and (eventually) MCP tools. Current status lives in [`PROGRESS.md`](./PROGRESS.md). @@ -17,7 +17,7 @@ branches while sharing context through a vault, a local daemon, a dashboard, and packages/core Shared types + contracts (src/contracts/) — start here packages/daemon Local HTTP API on :9473 (state, vault, relay) packages/vault Vault materialization from events.jsonl -packages/cli `teambridge` CLI (commander) +packages/cli `coord` CLI (commander) packages/mcp MCP resource/tool stubs (server not yet wired) apps/dashboard React + Vite + TanStack Query UI on :5173 supabase/ Phase 2 relay migration (tc_ tables) @@ -26,8 +26,8 @@ docs/ Concepts, daemon API, worktrees, design choices, plans report/ Team build plan + Phase 2 relay plan ``` -Local runtime state lives in `.teambridge/` at the git root (SQLite at -`.teambridge/state.sqlite`, worktrees, vault, avatars). +Local runtime state lives in `.coord/` at the git root (SQLite at +`.coord/state.sqlite`, worktrees, vault, avatars). ## Commands @@ -40,7 +40,7 @@ pnpm daemon # HTTP API on http://127.0.0.1:9473 pnpm seed # optional demo projects (Beacon, Silo, Forge) pnpm dashboard # dev UI on http://127.0.0.1:5173 pnpm dashboard:preview # production build + preview -pnpm teambridge # run the built CLI (node packages/cli/dist/index.js) +pnpm coord # run the built CLI (node packages/cli/dist/index.js) pnpm test # per-package unit tests (node --test) pnpm test:integration # builds, then runs tests/integration/*.test.mjs ``` @@ -60,7 +60,7 @@ daemon code before exercising a flow. integration tests are `tests/integration/*.test.mjs`, run serially. - **Naming hybrid:** UI and docs say **track**; several APIs and types still say **workspace** (`/workspaces/*` mutations vs. `/projects` + `/tracks` - reads, `.teambridge/workspaces/{sessionName}/` on disk). This is intentional + reads, `.coord/workspaces/{sessionName}/` on disk). This is intentional until a future `/tracks/*` rename — see [`docs/CONCEPTS.md`](./docs/CONCEPTS.md). - **Vault is a projection, not the truth.** `events.jsonl` is canonical (ordered by per-workspace `seq`, not timestamps); markdown vault files are diff --git a/PROGRESS.md b/PROGRESS.md index cb2a01b..f516e33 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,4 +1,4 @@ -# Condominium — Progress +# Coord — Progress Snapshot of what has been built so far and what remains. This is a status digest; the authoritative checklist stays in [`todo.md`](./todo.md), and the @@ -32,7 +32,7 @@ pnpm test:integration # full CLI + daemon flow, incl. vault-flow.test.mjs SQLite state, projects/tracks, participants, worktrees, publish events, vault materialization + rebuild, `vault context`, FTS5 vault search, avatars, repo context, and Supabase relay mirroring/sync. -- **CLI** (`packages/cli`, `pnpm teambridge`) — `init` (with `--relay +- **CLI** (`packages/cli`, `pnpm coord`) — `init` (with `--relay local|supabase`), `status`, `project create|list`, `start`, `join`, `enter`, `publish`, `vault read|context|search`, `context` (compact vault context + teammate deltas), `hook install|uninstall|status` (Claude Code @@ -56,7 +56,7 @@ pnpm test:integration # full CLI + daemon flow, incl. vault-flow.test.mjs (`team_publish`, `vault_search`, `vault_read`, `workspace_status`, `team_ask`, `team_reply`) — all calling the daemon. Workspace resolution from explicit params, local `state.sqlite` worktree mapping, or - `.teambridge/.active` fallback. Start with `teambridge mcp`. Integration tests + `.coord/.active` fallback. Start with `coord mcp`. Integration tests spawn the server over stdio and verify JSON-RPC handshake, resource/tool lists, live resource reads, and full ask/reply/conflict flows. @@ -82,7 +82,7 @@ All Phase 1 steps and the pass example in `todo.md` are checked off: `dedupeKey`. - Daemon relay identity/device registration; mirrors projects/sessions/ participants after login; queues failed publishes in `pending_remote_events`. -- Push/pull sync (autonomous polling + manual `teambridge sync`); remote +- Push/pull sync (autonomous polling + manual `coord sync`); remote events rebuild the local vault. - CLI: `login`, `sessions`, `list`, `sync`, `status relay`; `join` can import a remote session before creating the worktree. @@ -113,14 +113,14 @@ All Phase 1 steps and the pass example in `todo.md` are checked off: - MCP server over stdio transport using `@modelcontextprotocol/sdk` with `StdioServerTransport` (Claude Code compatible). Start with - `teambridge mcp`. -- Five MCP resources registered: `teambridge://workspace` (with relay status), - `teambridge://participants`, `teambridge://vault/context`, - `teambridge://inbox`, `teambridge://conflicts` — all backed by daemon calls. + `coord mcp`. +- Five MCP resources registered: `coord://workspace` (with relay status), + `coord://participants`, `coord://vault/context`, + `coord://inbox`, `coord://conflicts` — all backed by daemon calls. - Six MCP tools registered: `team_publish`, `vault_search`, `vault_read`, `workspace_status`, `team_ask`, `team_reply` — all calling the daemon. - Workspace resolution from explicit params, local `state.sqlite` worktree - mapping, or `.teambridge/.active` fallback. + mapping, or `.coord/.active` fallback. - Integration tests spawning the server over stdio, verifying initialize handshake, resources/list, tools/list, and workspace-resolution errors. - MCP resource contracts include relay-backed workspace state with graceful @@ -139,9 +139,9 @@ All Phase 1 steps and the pass example in `todo.md` are checked off: conflict endpoints (`GET /workspaces/:id/conflicts`, `POST /workspaces/:id/conflicts/:id/resolve`) with participant-level actor validation. -- `teambridge ask|inbox|reply` CLI commands, live MCP ask/reply, and end-to-end +- `coord ask|inbox|reply` CLI commands, live MCP ask/reply, and end-to-end local verification in `tests/integration/inbox-conflicts-flow.test.mjs`. -- Conflict-marker parser, `conflicts.md` materialization, and `teambridge +- Conflict-marker parser, `conflicts.md` materialization, and `coord conflicts` CLI command. - Daemon hook/delta context endpoints (`/context/hook`, `/context/deltas`) for IDE hooks and dashboard callers, covered by @@ -149,10 +149,10 @@ All Phase 1 steps and the pass example in `todo.md` are checked off: - Mock-relay end-to-end verification for offline/reconnect retry and late joiner checkpoint bootstrap in `tests/integration/relay-reconnect-bootstrap.test.mjs`. -- Claude Code hook auto-injection: `teambridge hook install` writes a - SessionStart hook into `.claude/settings.json` that runs `teambridge +- Claude Code hook auto-injection: `coord hook install` writes a + SessionStart hook into `.claude/settings.json` that runs `coord context`, so an agent opening a worktree gets shared context with no - per-session flags. `teambridge context` emits smarter compact vault context + per-session flags. `coord context` emits smarter compact vault context (empty files dropped, per-file titles stripped, bullets deduped) plus a delta of what changed since a per-participant last-seen `seq` (`--peek`/`--deltas-only`/`--json`). Covered by diff --git a/README.md b/README.md index 5433c3b..deaf8b6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Condominium +# Coord Shared workspace for teams building with AI agents — projects, tracks, a shared vault, and a dashboard to stay aligned. @@ -17,9 +17,9 @@ pnpm dashboard # UI on http://127.0.0.1:5173 **Your own data (no seed):** ```bash -pnpm teambridge init -pnpm teambridge project create --name "My App" -pnpm teambridge start my-track +pnpm coord init +pnpm coord project create --name "My App" +pnpm coord start my-track ``` Optional: add `PEXELS_API_KEY` to `.env` for flower profile photos. @@ -33,9 +33,9 @@ Optional: add `PEXELS_API_KEY` to `.env` for flower profile photos. | `packages/vault` | Vault materialization | | `packages/mcp` | MCP server (in progress) | | `apps/dashboard` | Web UI (`pnpm dashboard`) | -| `packages/cli` | CLI (`pnpm teambridge`) | +| `packages/cli` | CLI (`pnpm coord`) | -Local state lives in `.teambridge/` at the git root. +Local state lives in `.coord/` at the git root. ## Docs diff --git a/agent.md b/agent.md index 2512637..4f769f2 100644 --- a/agent.md +++ b/agent.md @@ -1,27 +1,27 @@ -# Condominium Agent Guide +# Coord Agent Guide -This repository is **Condominium** — a shared workspace for teams building with AI coding agents. +This repository is **Coord** — a shared workspace for teams building with AI coding agents. -Condominium lets multiple developers work on the same task with separate branches while sharing context through a vault, dashboard, and (eventually) agent tools. +Coord lets multiple developers work on the same task with separate branches while sharing context through a vault, dashboard, and (eventually) agent tools. For team execution details, keep and use `report/team-implementation-plan.md`. This file is the product vision; the report file is the build plan for Nihal, Kushagra, and Ronish. ## Product Vision -Condominium is for teams where each developer has their own AI agent, but everyone needs the same picture of what's going on. +Coord is for teams where each developer has their own AI agent, but everyone needs the same picture of what's going on. Example (target CLI — **not shipped yet**; today use `pnpm daemon`, `pnpm seed`, and `pnpm dashboard`): ```bash -teambridge start billing-v2 main -teambridge join billing-v2 -cd "$(teambridge enter billing-v2)" && claude +coord start billing-v2 main +coord join billing-v2 +cd "$(coord enter billing-v2)" && claude ``` Each teammate gets: - A separate git worktree and branch from the same recorded `base_commit` -- A local materialized **track vault** at `.teambridge/workspaces/{session_name}/vault/` +- A local materialized **track vault** at `.coord/workspaces/{session_name}/vault/` - Shared context updates from teammate events - MCP tools for agents to read, publish, ask, and inspect workspace state (planned) - Daemon HTTP API + **dashboard** for humans; CLI visibility (planned) @@ -71,22 +71,22 @@ Events notify everyone, then materialize locally, then all agents can use the up Primary user-facing commands (north-star UX): ```bash -teambridge init -teambridge start [base_ref] -teambridge join -teambridge enter -teambridge status -teambridge ws show -teambridge ws who -teambridge ws branches -teambridge publish -teambridge ask "question" -teambridge inbox -teambridge reply "answer" -teambridge vault search -teambridge vault read -teambridge vault debug-snapshot -teambridge dashboard +coord init +coord start [base_ref] +coord join +coord enter +coord status +coord ws show +coord ws who +coord ws branches +coord publish +coord ask "question" +coord inbox +coord reply "answer" +coord vault search +coord vault read +coord vault debug-snapshot +coord dashboard ``` ### Available today (local dogfood) @@ -107,12 +107,12 @@ For local simulation and dogfooding, `start` and `join` may accept a display-nam ```text Developer machine - -> teambridge daemon (:9473) + -> coord daemon (:9473) -> local HTTP API (projects, tracks/workspaces, vault, avatars) -> HTTP MCP server :9474 (planned) -> workspace vault materializer -> checkpoint builder / relay client (Phase 2) - -> local SQLite state (.teambridge/state.sqlite) + -> local SQLite state (.coord/state.sqlite) -> CLI (planned) -> dashboard (React + Vite — shipped) -> agent hooks (planned) @@ -132,12 +132,12 @@ Supabase ## Track vault -There is only one Teambridge vault type in the first product: the **track vault** (still stored under `workspaces/` on disk). +There is only one Coord vault type in the first product: the **track vault** (still stored under `workspaces/` on disk). Phase 1 keeps the vault flat and easy to change: ```text -.teambridge/workspaces/{session_name}/vault/ +.coord/workspaces/{session_name}/vault/ ├── README.md ├── decisions.md ├── observations.md @@ -163,7 +163,7 @@ Persisted via `POST /workspaces/:trackId/vault/annotate`. Not stored in `events. ## Event Flow ```text -agent/team_publish or teambridge publish +agent/team_publish or coord publish -> local daemon -> local events.jsonl -> local vault materialization @@ -177,10 +177,10 @@ agent/team_publish or teambridge publish User-published context uses one event type, `publish`. The vault-relative `targetFile` decides where the markdown projection goes. Examples: ```bash -teambridge publish decisions.md "Backend is the source of truth for invoice state." -teambridge publish observations.md "Frontend reads derived totals from the invoice API." -teambridge publish blockers.md "Need refresh-token behavior decided before UI retry logic." -teambridge publish test-results.md "pnpm test passed for billing package." +coord publish decisions.md "Backend is the source of truth for invoice state." +coord publish observations.md "Frontend reads derived totals from the invoice API." +coord publish blockers.md "Need refresh-token behavior decided before UI retry logic." +coord publish test-results.md "pnpm test passed for billing package." ``` Phase 1 uses these simple target files: @@ -213,7 +213,7 @@ Supabase stores latest vault checkpoints for fast bootstrap. New joiner flow: ```text -teambridge join billing-v2 +coord join billing-v2 -> fetch workspace manifest -> fetch latest checkpoint -> fetch events after checkpoint `seq` diff --git a/apps/dashboard/index.html b/apps/dashboard/index.html index 4506916..bff17fb 100644 --- a/apps/dashboard/index.html +++ b/apps/dashboard/index.html @@ -4,7 +4,7 @@ - Condominium + Coord
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 7872443..e772086 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,5 +1,5 @@ { - "name": "@teambridge/dashboard", + "name": "@coord/dashboard", "version": "0.0.0", "private": true, "type": "module", @@ -15,7 +15,7 @@ "@hugeicons/core-free-icons": "^4.2.2", "@hugeicons/react": "^1.1.9", "@tabler/icons-react": "^3.44.0", - "@teambridge/core": "workspace:*", + "@coord/core": "workspace:*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "motion": "^12.42.0", diff --git a/apps/dashboard/src/App.test.tsx b/apps/dashboard/src/App.test.tsx index 4bc795c..e21a507 100644 --- a/apps/dashboard/src/App.test.tsx +++ b/apps/dashboard/src/App.test.tsx @@ -28,7 +28,7 @@ const api = vi.hoisted(() => ({ resolveConflict: vi.fn() })); -vi.mock('./api/teambridgeClient', () => api); +vi.mock('./api/coordClient', () => api); function renderAtRoute(path: string, element: React.ReactElement, withSidebar = false) { const routes = ( @@ -72,11 +72,11 @@ describe('ProjectSelectionPage', () => { }); it('shows an error message when the API is unreachable', async () => { - api.listProjects.mockRejectedValue(new Error('Unable to reach local Condominium daemon.')); + api.listProjects.mockRejectedValue(new Error('Unable to reach local Coord daemon.')); renderAtRoute('/projects', ); - expect(await screen.findByText('Unable to reach local Condominium daemon.')).toBeTruthy(); + expect(await screen.findByText('Unable to reach local Coord daemon.')).toBeTruthy(); }); it('shows empty state when no projects exist', async () => { @@ -92,7 +92,7 @@ describe('ProjectSelectionPage', () => { api.listKnownRepos.mockResolvedValue({ repos: [ { - repoRoot: '/tmp/teambridge-dummy-repo', + repoRoot: '/tmp/coord-dummy-repo', lastSeenAt: '2026-01-01T00:00:00.000Z', projects: [ { id: 'proj_dummy', name: 'Dummy Repo', description: 'Local test repo', status: 'active', createdAt: '2026-01-01T00:00:00.000Z' } @@ -116,7 +116,7 @@ describe('ProjectSelectionPage', () => { api.listKnownRepos.mockResolvedValue({ repos: [ { - repoRoot: '/tmp/teambridge-dummy-repo', + repoRoot: '/tmp/coord-dummy-repo', lastSeenAt: '2026-01-01T00:00:00.000Z', projects: [ { id: 'proj_dummy', name: 'Dummy Repo', description: 'Local test repo', status: 'active', createdAt: '2026-01-01T00:00:00.000Z' } @@ -129,7 +129,7 @@ describe('ProjectSelectionPage', () => { expect(await screen.findByText('Current Repo')).toBeTruthy(); expect(screen.getByText('Dummy Repo')).toBeTruthy(); - expect(screen.getByText('/tmp/teambridge-dummy-repo')).toBeTruthy(); + expect(screen.getByText('/tmp/coord-dummy-repo')).toBeTruthy(); }); }); diff --git a/apps/dashboard/src/api/teambridgeClient.test.ts b/apps/dashboard/src/api/coordClient.test.ts similarity index 95% rename from apps/dashboard/src/api/teambridgeClient.test.ts rename to apps/dashboard/src/api/coordClient.test.ts index 8c65020..d2f7b68 100644 --- a/apps/dashboard/src/api/teambridgeClient.test.ts +++ b/apps/dashboard/src/api/coordClient.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { askInbox, - buildTeambridgeUrl, + buildCoordUrl, getContextPointer, getDefaultClientConfig, listConflicts, @@ -14,7 +14,7 @@ import { searchVault, setContextPointer, unwrapApiResult -} from './teambridgeClient'; +} from './coordClient'; function jsonResponse(body: unknown): Response { return new Response(JSON.stringify(body), { @@ -24,7 +24,7 @@ function jsonResponse(body: unknown): Response { }); } -describe('teambridgeClient', () => { +describe('coordClient', () => { afterEach(() => { vi.restoreAllMocks(); vi.unstubAllEnvs(); @@ -33,7 +33,7 @@ describe('teambridgeClient', () => { it('builds daemon URLs with repoRoot and encoded query params', () => { expect( - buildTeambridgeUrl('/workspaces/ws_123/vault/read', { repoRoot: '/tmp/team channel' }, { path: 'decisions.md' }) + buildCoordUrl('/workspaces/ws_123/vault/read', { repoRoot: '/tmp/team channel' }, { path: 'decisions.md' }) ).toBe( 'http://127.0.0.1:9473/workspaces/ws_123/vault/read?repoRoot=%2Ftmp%2Fteam+channel&path=decisions.md' ); @@ -81,8 +81,8 @@ describe('teambridgeClient', () => { }); it('uses query params before env values for daemon config', () => { - vi.stubEnv('VITE_TEAMBRIDGE_DAEMON_URL', 'http://env-host:9473'); - vi.stubEnv('VITE_TEAMBRIDGE_REPO_ROOT', '/env/repo'); + vi.stubEnv('VITE_COORD_DAEMON_URL', 'http://env-host:9473'); + vi.stubEnv('VITE_COORD_REPO_ROOT', '/env/repo'); window.history.pushState({}, '', '/?daemonBaseUrl=http%3A%2F%2Fquery-host%3A9473&repoRoot=%2Fquery%2Frepo'); expect(getDefaultClientConfig()).toEqual({ @@ -98,7 +98,7 @@ describe('teambridgeClient', () => { }); it('falls back to env and default daemon URL', () => { - vi.stubEnv('VITE_TEAMBRIDGE_REPO_ROOT', ''); + vi.stubEnv('VITE_COORD_REPO_ROOT', ''); window.history.pushState({}, '', '/'); expect(getDefaultClientConfig()).toEqual({ @@ -106,7 +106,7 @@ describe('teambridgeClient', () => { repoRoot: '' }); - vi.stubEnv('VITE_TEAMBRIDGE_REPO_ROOT', '/env/repo'); + vi.stubEnv('VITE_COORD_REPO_ROOT', '/env/repo'); expect(getDefaultClientConfig()).toEqual({ daemonBaseUrl: 'http://127.0.0.1:9473', diff --git a/apps/dashboard/src/api/teambridgeClient.ts b/apps/dashboard/src/api/coordClient.ts similarity index 78% rename from apps/dashboard/src/api/teambridgeClient.ts rename to apps/dashboard/src/api/coordClient.ts index b32e423..a29a8e1 100644 --- a/apps/dashboard/src/api/teambridgeClient.ts +++ b/apps/dashboard/src/api/coordClient.ts @@ -24,16 +24,16 @@ import type { VaultItemAnnotation, WorkspaceListResponse, WorkspaceStatusResponse -} from '@teambridge/core'; +} from '@coord/core'; import { avatarNameSlug } from '@/lib/avatar-identity'; export const DEFAULT_DAEMON_BASE_URL = 'http://127.0.0.1:9473'; const DEFAULT_TIMEOUT_MS = 10_000; -export type TeambridgeQueryParams = Record; +export type CoordQueryParams = Record; -export type TeambridgeClientConfig = { +export type CoordClientConfig = { daemonBaseUrl?: string; repoRoot?: string; }; @@ -65,10 +65,10 @@ function normalizeDaemonBaseUrl(value: string | null | undefined): string { } } -export function buildTeambridgeUrl( +export function buildCoordUrl( path: string, - config: TeambridgeClientConfig = {}, - params: TeambridgeQueryParams = {} + config: CoordClientConfig = {}, + params: CoordQueryParams = {} ): string { const url = new URL(path, config.daemonBaseUrl ?? DEFAULT_DAEMON_BASE_URL); @@ -106,29 +106,29 @@ function withDefaultTimeout(signal?: AbortSignal): AbortSignal | undefined { async function getJson( path: string, - config: TeambridgeClientConfig, - params?: TeambridgeQueryParams, + config: CoordClientConfig, + params?: CoordQueryParams, signal?: AbortSignal ): Promise { - const response = await fetch(buildTeambridgeUrl(path, config, params), { signal: withDefaultTimeout(signal) }); + const response = await fetch(buildCoordUrl(path, config, params), { signal: withDefaultTimeout(signal) }); return unwrapApiResult(response); } -export function getDefaultClientConfig(): TeambridgeClientConfig { +export function getDefaultClientConfig(): CoordClientConfig { const env = import.meta.env; const query = new URLSearchParams(window.location.search); return { - daemonBaseUrl: normalizeDaemonBaseUrl(query.get('daemonBaseUrl') ?? env.VITE_TEAMBRIDGE_DAEMON_URL), - repoRoot: query.get('repoRoot') ?? env.VITE_TEAMBRIDGE_REPO_ROOT + daemonBaseUrl: normalizeDaemonBaseUrl(query.get('daemonBaseUrl') ?? env.VITE_COORD_DAEMON_URL), + repoRoot: query.get('repoRoot') ?? env.VITE_COORD_REPO_ROOT }; } -export function listKnownRepos(config: TeambridgeClientConfig, signal?: AbortSignal): Promise<{ repos: KnownRepo[] }> { +export function listKnownRepos(config: CoordClientConfig, signal?: AbortSignal): Promise<{ repos: KnownRepo[] }> { return getJson<{ repos: KnownRepo[] }>('/repos', { daemonBaseUrl: config.daemonBaseUrl }, undefined, signal); } -export async function registerRepo(config: TeambridgeClientConfig, repoRoot: string, signal?: AbortSignal): Promise<{ repoRoot: string }> { - const response = await fetch(buildTeambridgeUrl('/repos/register', { daemonBaseUrl: config.daemonBaseUrl }), { +export async function registerRepo(config: CoordClientConfig, repoRoot: string, signal?: AbortSignal): Promise<{ repoRoot: string }> { + const response = await fetch(buildCoordUrl('/repos/register', { daemonBaseUrl: config.daemonBaseUrl }), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ repoRoot }), @@ -137,16 +137,16 @@ export async function registerRepo(config: TeambridgeClientConfig, repoRoot: str return unwrapApiResult<{ repoRoot: string }>(response); } -export function listWorkspaces(config: TeambridgeClientConfig, signal?: AbortSignal): Promise { +export function listWorkspaces(config: CoordClientConfig, signal?: AbortSignal): Promise { return getJson('/workspaces', config, undefined, signal); } -export function listProjects(config: TeambridgeClientConfig, signal?: AbortSignal): Promise { +export function listProjects(config: CoordClientConfig, signal?: AbortSignal): Promise { return getJson('/projects', config, undefined, signal); } export function getUserProfile( - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson('/user/profile', config, undefined, signal); @@ -154,7 +154,7 @@ export function getUserProfile( export function getProjectMembers( projectId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson(`/projects/${encodeURIComponent(projectId)}/members`, config, undefined, signal); @@ -162,21 +162,21 @@ export function getProjectMembers( export function getProjectTracks( projectId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson(`/projects/${encodeURIComponent(projectId)}/tracks`, config, undefined, signal); } export function listRelaySessions( - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise<{ sessions: TrackListResponse['tracks'] }> { return getJson<{ sessions: TrackListResponse['tracks'] }>('/relay/sessions', config, undefined, signal); } export function getRelayStatus( - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson('/relay/status', config, undefined, signal); @@ -184,7 +184,7 @@ export function getRelayStatus( export function getWorkspaceEvents( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -197,7 +197,7 @@ export function getWorkspaceEvents( export function getContextDeltas( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, params: { sinceSeq?: number; limit?: number; excludeActorId?: string } = {}, signal?: AbortSignal ): Promise { @@ -210,7 +210,7 @@ export function getContextDeltas( } export function getRepoContext( - config: TeambridgeClientConfig, + config: CoordClientConfig, workspaceId?: string, signal?: AbortSignal ): Promise { @@ -223,11 +223,11 @@ export function getRepoContext( } export async function openRepoPath( - config: TeambridgeClientConfig, + config: CoordClientConfig, path: string, signal?: AbortSignal ): Promise<{ opened: string }> { - const response = await fetch(buildTeambridgeUrl('/repo/open-path', config), { + const response = await fetch(buildCoordUrl('/repo/open-path', config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ path, repoRoot: config.repoRoot }), @@ -238,7 +238,7 @@ export async function openRepoPath( export function getWorkspaceStatus( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson(`/workspaces/${encodeURIComponent(workspaceId)}/status`, config, undefined, signal); @@ -246,7 +246,7 @@ export function getWorkspaceStatus( export function getVaultContext( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -260,7 +260,7 @@ export function getVaultContext( export function readVaultFile( workspaceId: string, path: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -274,7 +274,7 @@ export function readVaultFile( export function searchVault( workspaceId: string, query: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -287,7 +287,7 @@ export function searchVault( export function listInbox( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -300,11 +300,11 @@ export function listInbox( export async function askInbox( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, body: { to: string; text: string; actorId?: string }, signal?: AbortSignal ): Promise { - const response = await fetch(buildTeambridgeUrl(`/workspaces/${encodeURIComponent(workspaceId)}/inbox/ask`, config), { + const response = await fetch(buildCoordUrl(`/workspaces/${encodeURIComponent(workspaceId)}/inbox/ask`, config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, repoRoot: config.repoRoot }), @@ -316,11 +316,11 @@ export async function askInbox( export async function replyInbox( workspaceId: string, messageId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, body: { text: string; actorId?: string }, signal?: AbortSignal ): Promise { - const response = await fetch(buildTeambridgeUrl(`/workspaces/${encodeURIComponent(workspaceId)}/inbox/${encodeURIComponent(messageId)}/reply`, config), { + const response = await fetch(buildCoordUrl(`/workspaces/${encodeURIComponent(workspaceId)}/inbox/${encodeURIComponent(messageId)}/reply`, config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, repoRoot: config.repoRoot }), @@ -331,7 +331,7 @@ export async function replyInbox( export function listConflicts( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -345,11 +345,11 @@ export function listConflicts( export async function resolveConflict( workspaceId: string, conflictId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, body: { resolutionText: string; actorId?: string }, signal?: AbortSignal ): Promise { - const response = await fetch(buildTeambridgeUrl(`/workspaces/${encodeURIComponent(workspaceId)}/conflicts/${encodeURIComponent(conflictId)}/resolve`, config), { + const response = await fetch(buildCoordUrl(`/workspaces/${encodeURIComponent(workspaceId)}/conflicts/${encodeURIComponent(conflictId)}/resolve`, config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, repoRoot: config.repoRoot }), @@ -360,11 +360,11 @@ export async function resolveConflict( export function annotateVaultItem( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, annotation: VaultItemAnnotation, signal?: AbortSignal ): Promise { - return fetch(buildTeambridgeUrl(`/workspaces/${encodeURIComponent(workspaceId)}/vault/annotate`, config), { + return fetch(buildCoordUrl(`/workspaces/${encodeURIComponent(workspaceId)}/vault/annotate`, config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...annotation, repoRoot: config.repoRoot }), @@ -374,10 +374,10 @@ export function annotateVaultItem( export function buildDisplayNameAvatarUrl( displayName: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, rev?: number | string ): string { - return buildTeambridgeUrl( + return buildCoordUrl( `/avatars/by-name/${encodeURIComponent(avatarNameSlug(displayName))}`, config, rev !== undefined ? { v: rev } : {} @@ -403,11 +403,11 @@ export type PfpPreviewResult = { }; export async function previewPfp( - config: TeambridgeClientConfig, + config: CoordClientConfig, options: PfpPreviewOptions, signal?: AbortSignal ): Promise { - const response = await fetch(buildTeambridgeUrl('/dev/pfp/preview', config), { + const response = await fetch(buildCoordUrl('/dev/pfp/preview', config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(options), @@ -427,11 +427,11 @@ export async function previewPfp( } export async function regeneratePfp( - config: TeambridgeClientConfig, + config: CoordClientConfig, participantId: string, options: PfpPreviewOptions ): Promise<{ participantId: string; meta: unknown }> { - const response = await fetch(buildTeambridgeUrl('/dev/pfp/regenerate', config), { + const response = await fetch(buildCoordUrl('/dev/pfp/regenerate', config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ participantId, ...options }) @@ -441,7 +441,7 @@ export async function regeneratePfp( export function getContextPointer( workspaceId: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { return getJson( @@ -455,11 +455,11 @@ export function getContextPointer( export function setContextPointer( workspaceId: string, body: SaveContextPointerRequest, - config: TeambridgeClientConfig, + config: CoordClientConfig, signal?: AbortSignal ): Promise { const bodyWithRepo = config.repoRoot ? { ...body, repoRoot: config.repoRoot } : body; - return fetch(buildTeambridgeUrl(`/workspaces/${encodeURIComponent(workspaceId)}/context-pointer`, config), { + return fetch(buildCoordUrl(`/workspaces/${encodeURIComponent(workspaceId)}/context-pointer`, config), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(bodyWithRepo), diff --git a/apps/dashboard/src/components/AppLayout.tsx b/apps/dashboard/src/components/AppLayout.tsx index 1cd2fd3..f8295e2 100644 --- a/apps/dashboard/src/components/AppLayout.tsx +++ b/apps/dashboard/src/components/AppLayout.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react'; import { Outlet } from 'react-router-dom'; -import { getDefaultClientConfig } from '@/api/teambridgeClient'; +import { getDefaultClientConfig } from '@/api/coordClient'; import { AppShellProvider, useAppShell } from '@/components/app-shell-context'; import { SiteHeader } from '@/components/site-header'; import { SettingsDialog } from '@/components/settings-dialog'; diff --git a/apps/dashboard/src/components/CheckpointState.test.tsx b/apps/dashboard/src/components/CheckpointState.test.tsx index 278ffa1..c72f58b 100644 --- a/apps/dashboard/src/components/CheckpointState.test.tsx +++ b/apps/dashboard/src/components/CheckpointState.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { VaultCheckpoint } from '@teambridge/core'; +import type { VaultCheckpoint } from '@coord/core'; import { CheckpointState, truncateHash } from './CheckpointState'; import { formatRelativeTime } from '@/lib/relative-time'; @@ -11,7 +11,7 @@ function makeCheckpoint(overrides: Partial = {}): VaultCheckpoi id: 'ckpt_001', workspaceId: 'ws_123', seq: 42, - storagePath: '/tmp/teambridge/.teambridge/checkpoints/ckpt_001.tar', + storagePath: '/tmp/coord/.coord/checkpoints/ckpt_001.tar', hash: 'abcdef1234567890fedcba', createdByDeviceId: 'device_ronish', createdAt: '2026-07-06T11:00:00.000Z', diff --git a/apps/dashboard/src/components/CheckpointState.tsx b/apps/dashboard/src/components/CheckpointState.tsx index 455b955..783c985 100644 --- a/apps/dashboard/src/components/CheckpointState.tsx +++ b/apps/dashboard/src/components/CheckpointState.tsx @@ -1,5 +1,5 @@ import { motion } from 'motion/react'; -import type { VaultCheckpoint } from '@teambridge/core'; +import type { VaultCheckpoint } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { cn } from '@/lib/utils'; import { formatRelativeTime } from '@/lib/relative-time'; diff --git a/apps/dashboard/src/components/ConflictsPanel.tsx b/apps/dashboard/src/components/ConflictsPanel.tsx index 0552f6b..95d2b39 100644 --- a/apps/dashboard/src/components/ConflictsPanel.tsx +++ b/apps/dashboard/src/components/ConflictsPanel.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { motion } from 'motion/react'; -import type { Conflict } from '@teambridge/core'; +import type { Conflict } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; diff --git a/apps/dashboard/src/components/EventFeed.test.tsx b/apps/dashboard/src/components/EventFeed.test.tsx index 01d57f7..2d18386 100644 --- a/apps/dashboard/src/components/EventFeed.test.tsx +++ b/apps/dashboard/src/components/EventFeed.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { render, screen } from '@testing-library/react'; import { EventFeed } from './EventFeed'; import { makeWorkspaceEvent } from '@/test/factories'; -import type { Participant } from '@teambridge/core'; +import type { Participant } from '@coord/core'; const participants: Participant[] = [ { id: 'user_ronish', displayName: 'ronish', workspaceId: 'ws_123', branch: 'main', agent: 'cursor', status: 'active', lastSeenAt: '2026-07-06T12:00:00.000Z' }, diff --git a/apps/dashboard/src/components/EventFeed.tsx b/apps/dashboard/src/components/EventFeed.tsx index 0d3aa4a..bec6fe0 100644 --- a/apps/dashboard/src/components/EventFeed.tsx +++ b/apps/dashboard/src/components/EventFeed.tsx @@ -1,19 +1,19 @@ import { useState } from 'react'; import { motion } from 'motion/react'; -import type { Participant, WorkspaceEvent, WorkspaceEventType } from '@teambridge/core'; +import type { Participant, WorkspaceEvent, WorkspaceEventType } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { cn } from '@/lib/utils'; import { formatRelativeTime } from '@/lib/relative-time'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; import { participantFirstName } from './participantDisplay'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; export type EventFeedProps = { events?: WorkspaceEvent[]; error?: string; participants?: Participant[]; - config?: TeambridgeClientConfig; + config?: CoordClientConfig; avatarRev?: number; maxItems?: number; }; diff --git a/apps/dashboard/src/components/InboxPanel.tsx b/apps/dashboard/src/components/InboxPanel.tsx index b242812..cd08068 100644 --- a/apps/dashboard/src/components/InboxPanel.tsx +++ b/apps/dashboard/src/components/InboxPanel.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { motion } from 'motion/react'; -import type { InboxMessage, LocalUserProfile, Participant } from '@teambridge/core'; +import type { InboxMessage, LocalUserProfile, Participant } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -10,13 +10,13 @@ import { formatRelativeTime } from '@/lib/relative-time'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; import { participantFirstName } from './participantDisplay'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; export type InboxPanelProps = { messages?: InboxMessage[]; localUser?: LocalUserProfile | null; participants?: Participant[]; - config?: TeambridgeClientConfig; + config?: CoordClientConfig; error?: string; avatarRev?: number; onReply?: (messageId: string, text: string) => Promise; diff --git a/apps/dashboard/src/components/ProjectMemberSidebar.tsx b/apps/dashboard/src/components/ProjectMemberSidebar.tsx index 4422c7d..b0af86d 100644 --- a/apps/dashboard/src/components/ProjectMemberSidebar.tsx +++ b/apps/dashboard/src/components/ProjectMemberSidebar.tsx @@ -1,7 +1,7 @@ import { memo } from 'react'; import { motion } from 'motion/react'; -import type { ProjectMember } from '@teambridge/core'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { ProjectMember } from '@coord/core'; +import type { CoordClientConfig } from '@/api/coordClient'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; @@ -51,7 +51,7 @@ const MemberRow = memo(function MemberRow({ columnIndex }: { row: MemberRowData; - config: TeambridgeClientConfig; + config: CoordClientConfig; avatarRev?: number | string; index: number; columnIndex: number; @@ -151,7 +151,7 @@ export function ProjectMemberSidebar({ } const { online, offline } = buildMemberRows(members, localUser, localAvatarVersion); - const config: TeambridgeClientConfig = { daemonBaseUrl, repoRoot }; + const config: CoordClientConfig = { daemonBaseUrl, repoRoot }; const offlineStartIndex = online.length + 1; return ( diff --git a/apps/dashboard/src/components/RecentDeltasPanel.tsx b/apps/dashboard/src/components/RecentDeltasPanel.tsx index e61db1e..eb6dfe1 100644 --- a/apps/dashboard/src/components/RecentDeltasPanel.tsx +++ b/apps/dashboard/src/components/RecentDeltasPanel.tsx @@ -1,4 +1,4 @@ -import type { ContextDelta } from '@teambridge/core'; +import type { ContextDelta } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { formatRelativeTime } from '@/lib/relative-time'; diff --git a/apps/dashboard/src/components/RelaySyncHealth.tsx b/apps/dashboard/src/components/RelaySyncHealth.tsx index eddd8ec..93ec888 100644 --- a/apps/dashboard/src/components/RelaySyncHealth.tsx +++ b/apps/dashboard/src/components/RelaySyncHealth.tsx @@ -1,5 +1,5 @@ import { motion } from 'motion/react'; -import type { RelayStatusResponse } from '@teambridge/core'; +import type { RelayStatusResponse } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { cn } from '@/lib/utils'; import { formatRelativeTime } from '@/lib/relative-time'; diff --git a/apps/dashboard/src/components/TeammateDeltaPanel.tsx b/apps/dashboard/src/components/TeammateDeltaPanel.tsx index e0ed5a4..7667fc4 100644 --- a/apps/dashboard/src/components/TeammateDeltaPanel.tsx +++ b/apps/dashboard/src/components/TeammateDeltaPanel.tsx @@ -1,18 +1,18 @@ import { useMemo } from 'react'; import { motion } from 'motion/react'; -import type { ContextPointerResponse, Participant, WorkspaceEvent } from '@teambridge/core'; +import type { ContextPointerResponse, Participant, WorkspaceEvent } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { Button } from '@/components/ui/button'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; import { participantFirstName } from './participantDisplay'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; export type TeammateDeltaPanelProps = { events?: WorkspaceEvent[]; participants?: Participant[]; pointer?: ContextPointerResponse | null; - config?: TeambridgeClientConfig; + config?: CoordClientConfig; error?: string; avatarRev?: number; onMarkSeen?: () => void; diff --git a/apps/dashboard/src/components/TrackList.tsx b/apps/dashboard/src/components/TrackList.tsx index 8dd14ba..47365db 100644 --- a/apps/dashboard/src/components/TrackList.tsx +++ b/apps/dashboard/src/components/TrackList.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; import { motion } from 'motion/react'; -import type { Workspace } from '@teambridge/core'; +import type { Workspace } from '@coord/core'; import { IconDeviceDesktop } from '@tabler/icons-react'; import { SidebarGroup, diff --git a/apps/dashboard/src/components/TrackParticipantsPanel.test.tsx b/apps/dashboard/src/components/TrackParticipantsPanel.test.tsx index f46ce49..09cffcd 100644 --- a/apps/dashboard/src/components/TrackParticipantsPanel.test.tsx +++ b/apps/dashboard/src/components/TrackParticipantsPanel.test.tsx @@ -16,7 +16,7 @@ describe('TrackParticipantsPanel', () => { expect(screen.getByText('Ronish')).toBeTruthy(); expect(screen.getByText('Active')).toBeTruthy(); expect(screen.getByText(/cursor/)).toBeTruthy(); - expect(screen.getByText(/teambridge\/billing-refactor\/ronish/)).toBeTruthy(); + expect(screen.getByText(/coord\/billing-refactor\/ronish/)).toBeTruthy(); expect(screen.getByRole('button', { name: 'Enter' })).toBeTruthy(); }); diff --git a/apps/dashboard/src/components/TrackParticipantsPanel.tsx b/apps/dashboard/src/components/TrackParticipantsPanel.tsx index e94bb64..5fbd1b3 100644 --- a/apps/dashboard/src/components/TrackParticipantsPanel.tsx +++ b/apps/dashboard/src/components/TrackParticipantsPanel.tsx @@ -1,9 +1,9 @@ import { motion } from 'motion/react'; -import type { Participant, WorktreeInfo, WorkspaceStatusResponse } from '@teambridge/core'; +import type { Participant, WorktreeInfo, WorkspaceStatusResponse } from '@coord/core'; import { SidebarGroup, SidebarGroupLabel } from '@/components/ui/sidebar'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; -import { openRepoPath, type TeambridgeClientConfig } from '@/api/teambridgeClient'; +import { openRepoPath, type CoordClientConfig } from '@/api/coordClient'; import { prettyParticipantName, displayNamesMatch, type PinnedLocalUser } from './participantDisplay'; import { columnEnterTransition, COLUMN_ENTER, COLUMN_HIDE } from '@/lib/motion'; import { formatRelativeTime } from '@/lib/relative-time'; @@ -57,7 +57,7 @@ function MemberRow({ avatarUrl?: string; index: number; columnIndex: number; - config: TeambridgeClientConfig; + config: CoordClientConfig; }) { const showDot = row.status !== 'offline'; const meta = [row.agent, row.branch].filter(Boolean).join(' · '); diff --git a/apps/dashboard/src/components/VaultHighlights.tsx b/apps/dashboard/src/components/VaultHighlights.tsx index c2da75f..a3db4c3 100644 --- a/apps/dashboard/src/components/VaultHighlights.tsx +++ b/apps/dashboard/src/components/VaultHighlights.tsx @@ -1,9 +1,9 @@ import { useState, useMemo, useCallback, useEffect, type ReactNode } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { Participant, VaultContext, VaultItemAnnotation } from '@teambridge/core'; +import type { Participant, VaultContext, VaultItemAnnotation } from '@coord/core'; import { Badge } from '@/components/ui/badge'; import { IconSearch } from '@tabler/icons-react'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; import { ParticipantAvatar } from '@/components/participant-avatar'; import { avatarUrlForDisplayName } from '@/components/member-avatar'; import { participantFirstName, prettyParticipantName } from './participantDisplay'; @@ -114,7 +114,7 @@ function HighlightedText({ text, query }: { text: string; query: string }) { } function VaultParticipantAvatar({ participant, config, avatarRev, size = 20 }: { - participant: Participant; config: TeambridgeClientConfig; avatarRev?: number; size?: number; + participant: Participant; config: CoordClientConfig; avatarRev?: number; size?: number; }) { const avatarUrl = avatarUrlForDisplayName(participant.displayName, config, avatarRev); return ( @@ -127,7 +127,7 @@ function VaultParticipantAvatar({ participant, config, avatarRev, size = 20 }: { } function AuthorChip({ participant, config, avatarRev }: { - participant: Participant; config: TeambridgeClientConfig; avatarRev?: number; + participant: Participant; config: CoordClientConfig; avatarRev?: number; }) { const firstName = participantFirstName(participant.displayName); return ( @@ -139,7 +139,7 @@ function AuthorChip({ participant, config, avatarRev }: { } function AssignedChip({ participant, config, avatarRev }: { - participant: Participant; config: TeambridgeClientConfig; avatarRev?: number; + participant: Participant; config: CoordClientConfig; avatarRev?: number; }) { const firstName = participantFirstName(participant.displayName); return ( @@ -167,7 +167,7 @@ const ICON_CHECK = ( function EntryRow({ item, participant, rowState, onColor, onAssign, onCopy, participants, config, avatarRev, openMenus, onToggleMenu, highlightQuery }: { item: VaultEntry; participant?: Participant; rowState: RowState; onColor: (c: string | undefined) => void; onAssign: (slug: string | undefined) => void; onCopy: () => void; - participants: Participant[]; config: TeambridgeClientConfig; avatarRev?: number; + participants: Participant[]; config: CoordClientConfig; avatarRev?: number; openMenus: OpenMenus | null; onToggleMenu: (t: 'color' | 'assign') => void; onCloseAll: () => void; highlightQuery: string; }) { @@ -360,7 +360,7 @@ export function VaultHighlights({ }: VaultHighlightsProps) { const sections = useMemo(() => (context ? parseVaultSections(context.content) : []), [context?.content]); const persistedStates = useMemo(() => buildRowStates(sections), [sections]); - const config = useMemo(() => ({ daemonBaseUrl, repoRoot }), [daemonBaseUrl, repoRoot]); + const config = useMemo(() => ({ daemonBaseUrl, repoRoot }), [daemonBaseUrl, repoRoot]); const [rowStates, setRowStates] = useState>({}); const [openMenus, setOpenMenus] = useState(null); diff --git a/apps/dashboard/src/components/WorkspaceList.tsx b/apps/dashboard/src/components/WorkspaceList.tsx index cafa167..84b2d27 100644 --- a/apps/dashboard/src/components/WorkspaceList.tsx +++ b/apps/dashboard/src/components/WorkspaceList.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; import { motion } from 'motion/react'; -import type { Workspace } from '@teambridge/core'; +import type { Workspace } from '@coord/core'; import { IconDeviceDesktop } from '@tabler/icons-react'; import { SidebarGroup, diff --git a/apps/dashboard/src/components/app-shell-context.tsx b/apps/dashboard/src/components/app-shell-context.tsx index 46c6175..3bd6fe8 100644 --- a/apps/dashboard/src/components/app-shell-context.tsx +++ b/apps/dashboard/src/components/app-shell-context.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; -import type { Project, RelayStatusResponse, Workspace } from '@teambridge/core'; +import type { Project, RelayStatusResponse, Workspace } from '@coord/core'; export type HeaderVariant = 'projects' | 'dashboard'; diff --git a/apps/dashboard/src/components/app-sidebar.tsx b/apps/dashboard/src/components/app-sidebar.tsx index bd4f918..6477fa4 100644 --- a/apps/dashboard/src/components/app-sidebar.tsx +++ b/apps/dashboard/src/components/app-sidebar.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import type { Workspace } from '@teambridge/core'; +import type { Workspace } from '@coord/core'; import { TrackList } from '@/components/TrackList'; import { RepoContextPanel } from '@/components/repo-context-panel'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; import { Sidebar, SidebarContent @@ -12,7 +12,7 @@ import { export type AppSidebarProps = React.ComponentProps & { tracks: Workspace[]; selectedTrackId?: string; - clientConfig: TeambridgeClientConfig; + clientConfig: CoordClientConfig; error?: string; onSelectTrack: (trackId: string) => void; columnIndex?: number; diff --git a/apps/dashboard/src/components/dev-pfp-panel.tsx b/apps/dashboard/src/components/dev-pfp-panel.tsx index c732219..ffc25ea 100644 --- a/apps/dashboard/src/components/dev-pfp-panel.tsx +++ b/apps/dashboard/src/components/dev-pfp-panel.tsx @@ -1,11 +1,11 @@ import { useState } from 'react'; import { IconRefresh, IconSparkles } from '@tabler/icons-react'; import { - buildTeambridgeUrl, + buildCoordUrl, previewPfp, type PfpPreviewOptions, - type TeambridgeClientConfig -} from '@/api/teambridgeClient'; + type CoordClientConfig +} from '@/api/coordClient'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -50,7 +50,7 @@ function randomHexColor(): string { return rgbToHex({ r: Math.round((r1 + m) * 255), g: Math.round((g1 + m) * 255), b: Math.round((b1 + m) * 255) }); } -export function DevPfpPanel({ config }: { config: TeambridgeClientConfig }) { +export function DevPfpPanel({ config }: { config: CoordClientConfig }) { const [query, setQuery] = useState('flower close up'); const [size, setSize] = useState(220); const [algorithm, setAlgorithm] = useState('bayer'); @@ -92,7 +92,7 @@ export function DevPfpPanel({ config }: { config: TeambridgeClientConfig }) { } } - const directPreviewUrl = buildTeambridgeUrl('/dev/pfp/preview', config); + const directPreviewUrl = buildCoordUrl('/dev/pfp/preview', config); return (
diff --git a/apps/dashboard/src/components/member-avatar.ts b/apps/dashboard/src/components/member-avatar.ts index 12f604e..87eb3ac 100644 --- a/apps/dashboard/src/components/member-avatar.ts +++ b/apps/dashboard/src/components/member-avatar.ts @@ -1,8 +1,8 @@ -import { buildDisplayNameAvatarUrl, type TeambridgeClientConfig } from '@/api/teambridgeClient'; +import { buildDisplayNameAvatarUrl, type CoordClientConfig } from '@/api/coordClient'; export function avatarUrlForDisplayName( displayName: string, - config: TeambridgeClientConfig, + config: CoordClientConfig, rev?: number | string ): string | undefined { if (!config.daemonBaseUrl) return undefined; diff --git a/apps/dashboard/src/components/participantDisplay.ts b/apps/dashboard/src/components/participantDisplay.ts index 7652387..5dc106a 100644 --- a/apps/dashboard/src/components/participantDisplay.ts +++ b/apps/dashboard/src/components/participantDisplay.ts @@ -1,4 +1,4 @@ -import type { Participant, ParticipantStatus } from '@teambridge/core'; +import type { Participant, ParticipantStatus } from '@coord/core'; import { normalizeDisplayName } from '@/lib/avatar-identity'; export type PinnedLocalUser = { diff --git a/apps/dashboard/src/components/repo-context-panel.tsx b/apps/dashboard/src/components/repo-context-panel.tsx index 7975b22..5f98d37 100644 --- a/apps/dashboard/src/components/repo-context-panel.tsx +++ b/apps/dashboard/src/components/repo-context-panel.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState, type ReactNode } from 'react'; -import type { RepoContext } from '@teambridge/core'; +import type { RepoContext } from '@coord/core'; import { IconBrandGithub, IconCloudUpload, @@ -8,13 +8,13 @@ import { IconGitFork } from '@tabler/icons-react'; import { motion } from 'motion/react'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; -import { getRepoContext, openRepoPath } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; +import { getRepoContext, openRepoPath } from '@/api/coordClient'; import { cn } from '@/lib/utils'; export type RepoContextPanelProps = { workspaceId?: string; - clientConfig: TeambridgeClientConfig; + clientConfig: CoordClientConfig; }; const panelSpring = { type: 'spring' as const, duration: 0.32, bounce: 0 }; diff --git a/apps/dashboard/src/components/settings-dialog.tsx b/apps/dashboard/src/components/settings-dialog.tsx index 88493a3..6335eac 100644 --- a/apps/dashboard/src/components/settings-dialog.tsx +++ b/apps/dashboard/src/components/settings-dialog.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { IconBug } from '@tabler/icons-react'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; import { Dialog, DialogContent, @@ -25,7 +25,7 @@ import { DevPfpPanel } from './dev-pfp-panel'; export type SettingsDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; - config: TeambridgeClientConfig; + config: CoordClientConfig; }; type SettingsPage = 'dev'; diff --git a/apps/dashboard/src/components/site-header.tsx b/apps/dashboard/src/components/site-header.tsx index bb2cca2..8144cc6 100644 --- a/apps/dashboard/src/components/site-header.tsx +++ b/apps/dashboard/src/components/site-header.tsx @@ -1,7 +1,7 @@ import { Link, useLocation } from 'react-router-dom'; import { motion } from 'motion/react'; import { IconSettings, IconUsers } from '@tabler/icons-react'; -import type { Project, RelayStatusResponse, Workspace } from '@teambridge/core'; +import type { Project, RelayStatusResponse, Workspace } from '@coord/core'; import { Breadcrumb, diff --git a/apps/dashboard/src/components/team-sidebar.tsx b/apps/dashboard/src/components/team-sidebar.tsx index 66b8b6d..6143b23 100644 --- a/apps/dashboard/src/components/team-sidebar.tsx +++ b/apps/dashboard/src/components/team-sidebar.tsx @@ -9,8 +9,8 @@ import type { VaultCheckpoint, WorkspaceEvent, WorkspaceStatusResponse -} from '@teambridge/core'; -import type { ProjectMember } from '@teambridge/core'; +} from '@coord/core'; +import type { ProjectMember } from '@coord/core'; import { motion } from 'motion/react'; import { SidebarContent } from '@/components/ui/sidebar'; import { cn } from '@/lib/utils'; @@ -24,7 +24,7 @@ import { ConflictsPanel } from './ConflictsPanel'; import { TeammateDeltaPanel } from './TeammateDeltaPanel'; import { RecentDeltasPanel } from './RecentDeltasPanel'; import { displayNamesMatch, type PinnedLocalUser } from './participantDisplay'; -import type { TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { CoordClientConfig } from '@/api/coordClient'; export type TeamSidebarTab = 'team' | 'inbox' | 'conflicts' | 'updates'; export type MembersView = 'all' | 'track' | 'relay'; @@ -210,7 +210,7 @@ export function TeamSidebar({ }, [localUser, membersView, members, trackStatus?.participants]); const config = useMemo( - () => ({ daemonBaseUrl, repoRoot }) as TeambridgeClientConfig, + () => ({ daemonBaseUrl, repoRoot }) as CoordClientConfig, [daemonBaseUrl, repoRoot] ); diff --git a/apps/dashboard/src/components/workspaceDisplay.ts b/apps/dashboard/src/components/workspaceDisplay.ts index b32e3dc..ea73d78 100644 --- a/apps/dashboard/src/components/workspaceDisplay.ts +++ b/apps/dashboard/src/components/workspaceDisplay.ts @@ -1,4 +1,4 @@ -import type { Workspace } from '@teambridge/core'; +import type { Workspace } from '@coord/core'; type WorkspaceName = Pick; diff --git a/apps/dashboard/src/hooks/useContextPointer.ts b/apps/dashboard/src/hooks/useContextPointer.ts index 425da40..8e0b068 100644 --- a/apps/dashboard/src/hooks/useContextPointer.ts +++ b/apps/dashboard/src/hooks/useContextPointer.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; -import type { ContextPointerResponse } from '@teambridge/core'; -import { getContextPointer, setContextPointer, type TeambridgeClientConfig } from '@/api/teambridgeClient'; +import type { ContextPointerResponse } from '@coord/core'; +import { getContextPointer, setContextPointer, type CoordClientConfig } from '@/api/coordClient'; const REFRESH_MS = 5000; @@ -13,7 +13,7 @@ export type UseContextPointerResult = { export function useContextPointer( workspaceId: string | undefined, - config: TeambridgeClientConfig + config: CoordClientConfig ): UseContextPointerResult { const [pointer, setPointer] = useState(null); const [error, setError] = useState(); diff --git a/apps/dashboard/src/lib/avatar-identity.ts b/apps/dashboard/src/lib/avatar-identity.ts index a819c7d..e40ef52 100644 --- a/apps/dashboard/src/lib/avatar-identity.ts +++ b/apps/dashboard/src/lib/avatar-identity.ts @@ -1,4 +1,4 @@ -/** Mirror of @teambridge/core/avatar — kept local for Vite ESM (core ships CJS). */ +/** Mirror of @coord/core/avatar — kept local for Vite ESM (core ships CJS). */ export function normalizeDisplayName(name: string): string { return name diff --git a/apps/dashboard/src/lib/branding.ts b/apps/dashboard/src/lib/branding.ts index eca4242..5cabf84 100644 --- a/apps/dashboard/src/lib/branding.ts +++ b/apps/dashboard/src/lib/branding.ts @@ -1 +1 @@ -export const APP_NAME = 'Condominium'; +export const APP_NAME = 'Coord'; diff --git a/apps/dashboard/src/lib/cache.ts b/apps/dashboard/src/lib/cache.ts index 4c87e96..3fba940 100644 --- a/apps/dashboard/src/lib/cache.ts +++ b/apps/dashboard/src/lib/cache.ts @@ -1,4 +1,4 @@ -import type { Project, ProjectMember, VaultContext, Workspace, WorkspaceStatusResponse } from '@teambridge/core'; +import type { Project, ProjectMember, VaultContext, Workspace, WorkspaceStatusResponse } from '@coord/core'; type CacheShape = { workspaces: Workspace[]; diff --git a/apps/dashboard/src/lib/local-profile-cache.ts b/apps/dashboard/src/lib/local-profile-cache.ts index e448663..016d1cb 100644 --- a/apps/dashboard/src/lib/local-profile-cache.ts +++ b/apps/dashboard/src/lib/local-profile-cache.ts @@ -1,4 +1,4 @@ -import type { LocalUserProfile } from '@teambridge/core'; +import type { LocalUserProfile } from '@coord/core'; type CachedLocalIdentity = { profile: LocalUserProfile; diff --git a/apps/dashboard/src/lib/vault-item-meta.ts b/apps/dashboard/src/lib/vault-item-meta.ts index 08708b8..eab72c7 100644 --- a/apps/dashboard/src/lib/vault-item-meta.ts +++ b/apps/dashboard/src/lib/vault-item-meta.ts @@ -1,4 +1,4 @@ -/** Mirror of @teambridge/core/vault-annotations for Vite ESM. */ +/** Mirror of @coord/core/vault-annotations for Vite ESM. */ export type VaultItemMeta = { color?: string; diff --git a/apps/dashboard/src/pages/DashboardPage.tsx b/apps/dashboard/src/pages/DashboardPage.tsx index ba452f4..8983de8 100644 --- a/apps/dashboard/src/pages/DashboardPage.tsx +++ b/apps/dashboard/src/pages/DashboardPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import type { Conflict, ContextDelta, InboxMessage, Project, ProjectMember, LocalUserProfile, RelayStatusResponse, VaultContext, VaultItemAnnotation, Workspace, WorkspaceEvent, WorkspaceStatusResponse } from '@teambridge/core'; +import type { Conflict, ContextDelta, InboxMessage, Project, ProjectMember, LocalUserProfile, RelayStatusResponse, VaultContext, VaultItemAnnotation, Workspace, WorkspaceEvent, WorkspaceStatusResponse } from '@coord/core'; import { annotateVaultItem, getContextDeltas, @@ -18,8 +18,8 @@ import { replyInbox, resolveConflict, DEFAULT_DAEMON_BASE_URL, - type TeambridgeClientConfig -} from '@/api/teambridgeClient'; + type CoordClientConfig +} from '@/api/coordClient'; import { useContextPointer } from '@/hooks/useContextPointer'; import { useAppShell } from '@/components/app-shell-context'; import { createCache } from '@/lib/cache'; @@ -27,7 +27,7 @@ import { AppSidebar } from '@/components/app-sidebar'; import { TeamSidebar } from '@/components/team-sidebar'; import { SidebarInset } from '@/components/ui/sidebar'; import { VaultHighlights } from '@/components/VaultHighlights'; -import { buildDisplayNameAvatarUrl } from '@/api/teambridgeClient'; +import { buildDisplayNameAvatarUrl } from '@/api/coordClient'; import { preloadAvatars } from '@/lib/avatar-cache'; import { readCachedLocalIdentity, writeCachedLocalIdentity } from '@/lib/local-profile-cache'; @@ -43,7 +43,7 @@ export function DashboardPage() { const { projectId } = useParams<{ projectId: string }>(); const navigate = useNavigate(); const { setHeader, resetHeader } = useAppShell(); - const clientConfig = useMemo(() => getDefaultClientConfig(), []); + const clientConfig = useMemo(() => getDefaultClientConfig(), []); const cache = useMemo( () => createCache(clientConfig.daemonBaseUrl ?? DEFAULT_DAEMON_BASE_URL), diff --git a/apps/dashboard/src/pages/ProjectSelectionPage.tsx b/apps/dashboard/src/pages/ProjectSelectionPage.tsx index a1d40b8..b87283d 100644 --- a/apps/dashboard/src/pages/ProjectSelectionPage.tsx +++ b/apps/dashboard/src/pages/ProjectSelectionPage.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import type { Project } from '@teambridge/core'; -import { listKnownRepos, listProjects, getDefaultClientConfig, DEFAULT_DAEMON_BASE_URL } from '@/api/teambridgeClient'; +import type { Project } from '@coord/core'; +import { listKnownRepos, listProjects, getDefaultClientConfig, DEFAULT_DAEMON_BASE_URL } from '@/api/coordClient'; import { useAppShell } from '@/components/app-shell-context'; import { createCache } from '@/lib/cache'; @@ -64,7 +64,7 @@ export function ProjectSelectionPage() { setError(undefined); } catch (err) { if (!controller.signal.aborted) { - setError(err instanceof Error ? err.message : 'Unable to reach local Teambridge daemon.'); + setError(err instanceof Error ? err.message : 'Unable to reach local Coord daemon.'); } } }; @@ -108,7 +108,7 @@ export function ProjectSelectionPage() {
No projects found for the current repo yet.
- Run teambridge init and project create in a repo. + Run coord init and project create in a repo.
) : (
diff --git a/apps/dashboard/src/test/factories.ts b/apps/dashboard/src/test/factories.ts index f36ae31..c0cfcdf 100644 --- a/apps/dashboard/src/test/factories.ts +++ b/apps/dashboard/src/test/factories.ts @@ -1,4 +1,4 @@ -import type { Conflict, ContextPointerResponse, InboxMessage, Participant, RelayStatusResponse, SyncStateEntry, VaultContext, Workspace, WorkspaceEvent, WorkspaceStatusResponse } from '@teambridge/core'; +import type { Conflict, ContextPointerResponse, InboxMessage, Participant, RelayStatusResponse, SyncStateEntry, VaultContext, Workspace, WorkspaceEvent, WorkspaceStatusResponse } from '@coord/core'; export const createdAt = '2026-06-22T00:00:00.000Z'; @@ -25,7 +25,7 @@ export function makeParticipant(overrides: Partial = {}): Participa id: 'user_ronish', displayName: 'ronish', workspaceId: 'ws_123', - branch: 'teambridge/billing-refactor/ronish', + branch: 'coord/billing-refactor/ronish', agent: 'cursor', status: 'active', lastSeenAt: createdAt, @@ -41,8 +41,8 @@ export function makeWorkspaceStatus(overrides: Partial { workspaceId: 'ws_123', userId: 'user_ronish', - path: '/tmp/teambridge/.teambridge/worktrees/billing-refactor/ronish', - branch: 'teambridge/billing-refactor/ronish', + path: '/tmp/coord/.coord/worktrees/billing-refactor/ronish', + branch: 'coord/billing-refactor/ronish', baseCommit: 'abc123', currentCommit: 'abc123', dirty: false diff --git a/apps/dashboard/vite.config.ts b/apps/dashboard/vite.config.ts index 10a0330..a45189c 100644 --- a/apps/dashboard/vite.config.ts +++ b/apps/dashboard/vite.config.ts @@ -8,7 +8,7 @@ const monorepoRoot = fileURLToPath(new URL('../..', import.meta.url)); export default defineConfig({ plugins: [react(), tailwindcss()], define: { - 'import.meta.env.VITE_TEAMBRIDGE_REPO_ROOT': JSON.stringify(monorepoRoot) + 'import.meta.env.VITE_COORD_REPO_ROOT': JSON.stringify(monorepoRoot) }, resolve: { alias: { diff --git a/docs/CONCEPTS.md b/docs/CONCEPTS.md index 2bbe93c..873b165 100644 --- a/docs/CONCEPTS.md +++ b/docs/CONCEPTS.md @@ -1,6 +1,6 @@ -# Condominium Concepts +# Coord Concepts -Canonical vocabulary for the Condominium codebase and dashboard. Use these terms in UI copy and new docs. +Canonical vocabulary for the Coord codebase and dashboard. Use these terms in UI copy and new docs. ## Hierarchy @@ -12,7 +12,7 @@ Project - **Project** — Organizational container for a product or initiative. Has a roster of **project members** (names, status, avatars). Example seed projects: Beacon, Silo, Forge (see `scripts/seed-demo.mjs`). - **Track** — A scoped piece of work within a project. Maps to a git session with its own vault, event log, and **participants** (people/agents active on that track). User-facing UI says "track"; several APIs and types still say **workspace** for backward compatibility. -- **Vault** — Materialized markdown under `.teambridge/workspaces/{sessionName}/vault/`. Built from `events.jsonl`; not the cross-device source of truth in Phase 1 (events are). +- **Vault** — Materialized markdown under `.coord/workspaces/{sessionName}/vault/`. Built from `events.jsonl`; not the cross-device source of truth in Phase 1 (events are). ## Two identity layers @@ -27,8 +27,8 @@ Do not collapse these: a person is a **project member** once per project and a * | Concept | UI / docs | SQLite | HTTP (today) | On disk | |---------|-----------|--------|--------------|---------| -| Track | track | `tracks` table | `/workspaces/*` mutations; `GET /tracks` list | `.teambridge/workspaces/{sessionName}/` | -| Workspace type | — | — | `Workspace` in `@teambridge/core` | same as track dir | +| Track | track | `tracks` table | `/workspaces/*` mutations; `GET /tracks` list | `.coord/workspaces/{sessionName}/` | +| Workspace type | — | — | `Workspace` in `@coord/core` | same as track dir | | Project | project | `projects`, `project_members` | `GET /projects`, … | rows in `state.sqlite` | **Intentional hybrid:** reads expose `/projects` and `/tracks`; start/join/events/vault still use `/workspaces/*`. A future breaking change may rename those paths to `/tracks/*`. @@ -36,7 +36,7 @@ Do not collapse these: a person is a **project member** once per project and a * ## Avatars - Display names map to a URL-safe **slug** (`avatarNameSlug`) — apostrophes become hyphens (`Flynn O'Brien` → `flynn-o-brien`). -- On-disk id: `name_{slug}` under `.teambridge/avatars/`. +- On-disk id: `name_{slug}` under `.coord/avatars/`. - Primary dashboard URL: `GET /avatars/by-name/:slug`. - Flower images come from Pexels when `PEXELS_API_KEY` is set; otherwise procedural fallback. @@ -55,10 +55,10 @@ Written via `POST /workspaces/:id/vault/annotate`. Survives vault rebuild (extra ## Local user profile -- Stored at `.teambridge/user.json` after `teambridge init` (first name, last name, computed `displayName`). +- Stored at `.coord/user.json` after `coord init` (first name, last name, computed `displayName`). - Same `displayName` is used for project roster rows and track participants when using the CLI. - Avatar PNG is generated immediately via the daemon (`name_{slug}`); dashboard loads it through `/avatars/by-name/:slug`. -- Optional `defaultProjectId` — CLI sets this when you create a project so `teambridge start` links sessions to the right project sidebar. +- Optional `defaultProjectId` — CLI sets this when you create a project so `coord start` links sessions to the right project sidebar. ## Phase 1 vs later diff --git a/docs/cli-worktrees.md b/docs/cli-worktrees.md index 690da3b..650f6d7 100644 --- a/docs/cli-worktrees.md +++ b/docs/cli-worktrees.md @@ -1,6 +1,6 @@ # CLI Worktrees (`start` / `join`) -How the `@teambridge/cli` creates and manages git worktrees. Owner: Kushagra. +How the `@coord/cli` creates and manages git worktrees. Owner: Kushagra. ## Model @@ -8,21 +8,21 @@ How the `@teambridge/cli` creates and manages git worktrees. Owner: Kushagra. - **Starter and joiners get isolated worktrees** cut from the session's frozen `base_commit`: ``` -/.teambridge/worktrees/// +/.coord/worktrees/// ``` -- **Branch:** `teambridge//` — byte-for-byte the daemon's `branchForParticipant` (index.ts:778). The session name is used **raw** (validated `^[A-Za-z0-9._-]+$`); the path segment is sanitized. `` mirrors the daemon's `safeDisplayName`. +- **Branch:** `coord//` — byte-for-byte the daemon's `branchForParticipant` (index.ts:778). The session name is used **raw** (validated `^[A-Za-z0-9._-]+$`); the path segment is sanitized. `` mirrors the daemon's `safeDisplayName`. -## `teambridge join [NAME] [--as DISPLAY_NAME]` +## `coord join [NAME] [--as DISPLAY_NAME]` Flow (git-first, then daemon, so the daemon never records a row for a worktree that failed to materialize): -1. Resolve `repoRoot` (`git rev-parse --show-toplevel`) and the local user profile (run `teambridge init` first). +1. Resolve `repoRoot` (`git rev-parse --show-toplevel`) and the local user profile (run `coord init` first). 2. Resolve the track authoritatively via `GET /tracks` → its `id` + frozen `baseCommit`. (Refuses if missing or archived.) -3. Preflight (read-only): ensure `.teambridge/worktrees` is gitignored; check for an existing registered worktree (idempotent), branch checked out elsewhere, base commit present locally. -4. `git worktree add -b teambridge// ` (reuses the branch if it already exists). +3. Preflight (read-only): ensure `.coord/worktrees` is gitignored; check for an existing registered worktree (idempotent), branch checked out elsewhere, base commit present locally. +4. `git worktree add -b coord// ` (reuses the branch if it already exists). 5. `POST /workspaces/join` with `{ repoRoot, sessionName, displayName, agent, worktreePath }`. -6. Write a local pointer `.teambridge/workspaces//.worktree..json` (so a future `enter` resolves the path without reading SQLite). +6. Write a local pointer `.coord/workspaces//.worktree..json` (so a future `enter` resolves the path without reading SQLite). **Enter the worktree:** `cd "$(... printed path)" && claude` (printed by the command). @@ -35,7 +35,7 @@ Flow (git-first, then daemon, so the daemon never records a row for a worktree t | Branch checked out elsewhere | Aborts with the conflicting path. | | Base commit missing locally | Aborts with `git fetch origin ` hint. | | Invalid session name | Rejected before any daemon call. | -| `.teambridge/` not gitignored | Writes a self-contained `.teambridge/.gitignore` (`*`) rather than editing the repo's root ignore. | +| `.coord/` not gitignored | Writes a self-contained `.coord/.gitignore` (`*`) rather than editing the repo's root ignore. | | Daemon down / non-JSON | `daemon-client.request()` returns a clear `ApiFail` instead of throwing. | ## Code @@ -55,4 +55,4 @@ Tests: `packages/cli/test/worktree.test.cjs` (naming parity + worktree orchestra - **`enter`/`status`** reconstruct the worktree path from the local pointer / deterministically, because the daemon doesn't expose worktree rows yet (ask #2). - **`leave`/`clean`** will leave a stale daemon worktrees row until ask #3 (`DELETE …/worktree`) lands. -- **`safeDisplayName`** is replicated in the CLI; ask #6 lifts it into `@teambridge/core` to remove the drift risk. +- **`safeDisplayName`** is replicated in the CLI; ask #6 lifts it into `@coord/core` to remove the drift risk. diff --git a/docs/daemon-api.md b/docs/daemon-api.md index 2df6bf3..43f08c2 100644 --- a/docs/daemon-api.md +++ b/docs/daemon-api.md @@ -1,13 +1,13 @@ # Daemon HTTP API -Local backend at `http://127.0.0.1:9473` (default). All JSON responses use `ApiResult` from `@teambridge/core`. +Local backend at `http://127.0.0.1:9473` (default). All JSON responses use `ApiResult` from `@coord/core`. ## Common query parameters | Param | Used on | Description | |-------|---------|-------------| | `repoRoot` | Most routes | Git repo root; daemon resolves to toplevel via `git rev-parse` | -| `maxBytes` | `GET .../vault/context` | Context size limit (default from `.teambridge/config.json`) | +| `maxBytes` | `GET .../vault/context` | Context size limit (default from `.coord/config.json`) | | `path` | `GET .../vault/read` | Vault file name, e.g. `decisions.md` | POST bodies may include `repoRoot` when the route accepts a JSON body. @@ -18,7 +18,7 @@ POST bodies may include `repoRoot` when the route accepts a JSON body. |--------|------|-------------| | GET | `/health` | Liveness | | GET | `/config?repoRoot=` | Read repo config | -| POST | `/config/init` | Create `.teambridge/config.json` (body `{ repoRoot?, relayMode? }`; `relayMode` sets/updates `defaultRelayMode` — `local` or `supabase`) | +| POST | `/config/init` | Create `.coord/config.json` (body `{ repoRoot?, relayMode? }`; `relayMode` sets/updates `defaultRelayMode` — `local` or `supabase`) | ## Repo context (dashboard sidebar) @@ -29,7 +29,7 @@ POST bodies may include `repoRoot` when the route accepts a JSON body. ## Local user profile (CLI + dashboard) -Written by `teambridge init`; stored at `.teambridge/user.json`. +Written by `coord init`; stored at `.coord/user.json`. | Method | Path | Description | |--------|------|-------------| diff --git a/docs/dashboard.md b/docs/dashboard.md index 16f2373..19caa0c 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -18,9 +18,9 @@ pnpm dashboard # Vite dev server (default host 127.0.0.1) **Your own data (CLI, same backend the dashboard reads):** ```bash -pnpm teambridge init -pnpm teambridge project create --name "My App" --description "Optional" -pnpm teambridge start my-track +pnpm coord init +pnpm coord project create --name "My App" --description "Optional" +pnpm coord start my-track ``` `project create` sets your default project so `start` links the session to the correct project sidebar without passing `--project`. @@ -33,8 +33,8 @@ Open the URL Vite prints (typically `http://127.0.0.1:5173`). | Variable / param | Purpose | |------------------|---------| -| `VITE_TEAMBRIDGE_DAEMON_URL` | Daemon base URL (default `http://127.0.0.1:9473`) | -| `VITE_TEAMBRIDGE_REPO_ROOT` | Git repo root sent as `repoRoot` on API calls | +| `VITE_COORD_DAEMON_URL` | Daemon base URL (default `http://127.0.0.1:9473`) | +| `VITE_COORD_REPO_ROOT` | Git repo root sent as `repoRoot` on API calls | | `?daemonBaseUrl=` query | Override daemon URL | | `?repoRoot=` query | Override repo root | @@ -79,6 +79,6 @@ Browser cache key: `tb_cache_v2_${daemonUrl}` (tracks, status, vault context). H ## Tests ```bash -pnpm --filter @teambridge/dashboard test +pnpm --filter @coord/dashboard test pnpm test:integration # repo root — CLI init → project → session against live daemon ``` diff --git a/docs/kushagra-handoff.md b/docs/kushagra-handoff.md index 324f2e6..218409a 100644 --- a/docs/kushagra-handoff.md +++ b/docs/kushagra-handoff.md @@ -12,13 +12,13 @@ The repo is a pnpm workspace: packages/core shared contracts, types, and zod schemas packages/vault vault helpers, materializer, context, and rebuild logic packages/daemon local backend HTTP server -packages/cli teambridge CLI (init, project create, start, join) +packages/cli coord CLI (init, project create, start, join) packages/mcp MCP resource/tool name stubs (HTTP server pending) apps/dashboard React dashboard (project picker, tracks, vault highlights) scripts/seed-demo.mjs demo data — run via pnpm seed ``` -`core` is not the CLI. It is the shared contract layer. CLI should import public shapes from `@teambridge/core`, not from daemon internals. +`core` is not the CLI. It is the shared contract layer. CLI should import public shapes from `@coord/core`, not from daemon internals. `vault` is not the CLI. It owns reusable vault behavior that the daemon uses. @@ -26,7 +26,7 @@ scripts/seed-demo.mjs demo data — run via pnpm seed ## How To Run Backend -From the Teambridge repo: +From the Coord repo: ```bash pnpm install @@ -44,9 +44,9 @@ Right now we test with `curl` or the dashboard. The CLI will hide those HTTP cal ## Terminology -- **Track** = workspace session (same row in `tracks` table, directory under `.teambridge/workspaces/`). -- **Project** groups tracks and project members; CLI sets `projectId` on tracks via `teambridge start --project`. -- **Local user** — `.teambridge/user.json` from `teambridge init`; dashboard avatars/roster use the same `displayName`. +- **Track** = workspace session (same row in `tracks` table, directory under `.coord/workspaces/`). +- **Project** groups tracks and project members; CLI sets `projectId` on tracks via `coord start --project`. +- **Local user** — `.coord/user.json` from `coord init`; dashboard avatars/roster use the same `displayName`. ## Repo Config @@ -61,7 +61,7 @@ POST /config/init creates: ```text -repo/.teambridge/config.json +repo/.coord/config.json ``` Default config: @@ -83,7 +83,7 @@ Default config: CLI mapping later: ```bash -teambridge init +coord init ``` should call: @@ -101,10 +101,10 @@ The daemon should run generally. Users should not normally start it with a repo Good final UX: ```bash -teambridge daemon +coord daemon cd /some/repo -teambridge init -teambridge start billing-refactor main +coord init +coord start billing-refactor main ``` The CLI should resolve the current working directory to the git repo root and send that as `repoRoot` in daemon requests. @@ -167,7 +167,7 @@ POST /workspaces/:workspaceId/vault/rebuild ## CLI Command Mapping -### `teambridge init` +### `coord init` Call: @@ -183,7 +183,7 @@ Body: } ``` -### `teambridge start billing-refactor main` +### `coord start billing-refactor main` Call: @@ -205,10 +205,10 @@ Body: The daemon creates: ```text -.teambridge/state.sqlite -.teambridge/workspaces/billing-refactor/manifest.json -.teambridge/workspaces/billing-refactor/events.jsonl -.teambridge/workspaces/billing-refactor/vault/ +.coord/state.sqlite +.coord/workspaces/billing-refactor/manifest.json +.coord/workspaces/billing-refactor/events.jsonl +.coord/workspaces/billing-refactor/vault/ ``` It also stores: @@ -220,7 +220,7 @@ worktree row local_sequences row with last_seq = 0 ``` -### `teambridge join billing-refactor --as kushagra` +### `coord join billing-refactor --as kushagra` Call: @@ -242,7 +242,7 @@ The daemon loads the existing workspace, keeps the recorded `baseCommit`, create Actual git worktree creation is still CLI-side work. -### `teambridge publish decisions.md "Backend is source of truth."` +### `coord publish decisions.md "Backend is source of truth."` Call: @@ -311,7 +311,7 @@ attempts.md ## Runtime Validation -Nihal added `zod` schemas in `@teambridge/core`. +Nihal added `zod` schemas in `@coord/core`. The daemon now validates request bodies and returns: @@ -350,7 +350,7 @@ Participant PublishEvent VaultContext ApiResult -TeambridgeConfig +CoordConfig ``` ## What Kushagra Should Build Next @@ -370,14 +370,14 @@ print friendly output Main commands to wire first: ```bash -teambridge init -teambridge start [base_ref] -teambridge join --as -teambridge publish -teambridge vault read -teambridge vault context -teambridge vault rebuild -teambridge status +coord init +coord start [base_ref] +coord join --as +coord publish +coord vault read +coord vault context +coord vault rebuild +coord status ``` The backend pieces for these are now mostly ready. The biggest remaining CLI-side responsibility is making the user experience clean and creating/managing actual git worktrees. diff --git a/docs/nihal-daemon-requests.md b/docs/nihal-daemon-requests.md index a0994e6..f9cbcd3 100644 --- a/docs/nihal-daemon-requests.md +++ b/docs/nihal-daemon-requests.md @@ -28,7 +28,7 @@ All references use function names in `packages/daemon/src/index.ts` (line number | 3 | `DELETE /workspaces/:id/worktree` | Open | No | MEDIUM | | 4 | `/status` resolves by session name (not id-only) | **Done** (`getWorkspaceByIdentifier`) | No | MEDIUM | | 5 | Transaction-wrap + idempotent `join` inserts | Open | No | MEDIUM | -| 6 | Shared `safeDisplayName` / `sanitizeSessionName` in `@teambridge/core` | Open | No | HIGH (low effort) | +| 6 | Shared `safeDisplayName` / `sanitizeSessionName` in `@coord/core` | Open | No | HIGH (low effort) | | 7 | Compute `current_commit` from the worktree, not repoRoot HEAD | Open | No | LOW | --- @@ -46,10 +46,10 @@ row — with no CLI-side DB writes. (mirror `JoinRequestBodySchema`). - In `startWorkspace`: when `worktreePath` is present, set the `worktrees` insert `path = resolve(body.worktreePath)` and - `persistedBranch = teambridge//`. When absent → behavior byte-identical to today. + `persistedBranch = coord//`. When absent → behavior byte-identical to today. **Acceptance.** `POST /workspaces/start` with `worktreePath` persists a `worktrees` row at that -path on the teambridge branch; without it, unchanged. +path on the coord branch; without it, unchanged. **CLI use.** Flip the creator to the same git-first sequence as a joiner — the product's "separate hands" isolation for everyone, with the daemon owning the DB. @@ -100,14 +100,14 @@ a raw 500. **Acceptance.** Re-join is idempotent and returns a clean result; no orphan rows. **CLI use.** Lets the CLI drop its "translate 500 → already joined" workaround. -## #6 — Shared sanitizer in `@teambridge/core` · HIGH (low effort) +## #6 — Shared sanitizer in `@coord/core` · HIGH (low effort) -**Why.** The branch string `teambridge//` must be **byte-identical** on both +**Why.** The branch string `coord//` must be **byte-identical** on both sides, or `UNIQUE(branch)` reconciliation breaks. Today `safeDisplayName` lives only in the daemon and the CLI has to hand-replicate it — a drift risk that turns into a silent join failure. -**Change.** Lift `safeDisplayName` into `@teambridge/core` and add `assertValidSessionName` / +**Change.** Lift `safeDisplayName` into `@coord/core` and add `assertValidSessionName` / `sanitizeSessionName`; the daemon imports from there. Also worth deciding: should the daemon **validate** `sessionName` against `^[A-Za-z0-9._-]+$` server-side? It currently stores the name raw into a git ref (index.ts:371, 378), so a name with spaces / `..` / leading `-` persists an diff --git a/docs/phase-1-design-choices.md b/docs/phase-1-design-choices.md index ebb1c91..2f68074 100644 --- a/docs/phase-1-design-choices.md +++ b/docs/phase-1-design-choices.md @@ -92,9 +92,9 @@ Why: Example CLI: ```bash -teambridge publish decisions.md "Backend is the source of truth for invoice state." -teambridge publish observations.md "Frontend reads derived totals from the invoice API." -teambridge publish blockers.md "Need refresh-token behavior decided before UI retry logic." +coord publish decisions.md "Backend is the source of truth for invoice state." +coord publish observations.md "Frontend reads derived totals from the invoice API." +coord publish blockers.md "Need refresh-token behavior decided before UI retry logic." ``` Example MCP tool input: @@ -162,9 +162,9 @@ vault/*.md The materializer should be able to delete and rebuild the vault from ordered events: ```bash -rm -rf .teambridge/workspaces/billing-refactor/vault -teambridge vault rebuild billing-refactor -teambridge vault read decisions.md +rm -rf .coord/workspaces/billing-refactor/vault +coord vault rebuild billing-refactor +coord vault read decisions.md ``` If rebuild does not work, the architecture is drifting in the wrong direction. @@ -236,24 +236,24 @@ Why: - `core` is not the CLI. It is the shared contract layer that daemon, CLI, dashboard, hooks, and MCP can all import. - `vault` is not the CLI. It owns reusable vault behavior so the daemon does not become the only place where vault rules exist. -- Splitting early lets Kushagra and Ronish build CLI/dashboard later against `@teambridge/core` without pulling daemon internals. +- Splitting early lets Kushagra and Ronish build CLI/dashboard later against `@coord/core` without pulling daemon internals. - Each package can have its own dependencies and build output while the root workspace still runs one `pnpm build`. -Do not make CLI code import directly from `packages/daemon`. Public shapes should come from `@teambridge/core`, and reusable vault behavior should come from `@teambridge/vault`. +Do not make CLI code import directly from `packages/daemon`. Public shapes should come from `@coord/core`, and reusable vault behavior should come from `@coord/vault`. ## 9. Phase 1 Success Criteria The first version passes when this local flow works (via **CLI when shipped**, or equivalent daemon HTTP + dashboard today): ```bash -teambridge init -teambridge start billing-refactor main -teambridge join billing-refactor --as kushagra -teambridge join billing-refactor --as ronish -teambridge publish decisions.md "Backend is the source of truth for invoice state." -teambridge vault read decisions.md -teambridge vault context -teambridge vault search "invoice state" +coord init +coord start billing-refactor main +coord join billing-refactor --as kushagra +coord join billing-refactor --as ronish +coord publish decisions.md "Backend is the source of truth for invoice state." +coord vault read decisions.md +coord vault context +coord vault search "invoice state" ``` **Alternate local proof (shipped):** `pnpm seed` + `pnpm dashboard` — pick a project, select a track, view vault highlights from `GET .../vault/context`. diff --git a/docs/plans/2026-06-21-001-feat-project-hierarchy-and-track-rename-plan.md b/docs/plans/2026-06-21-001-feat-project-hierarchy-and-track-rename-plan.md index ed20de7..baf70c4 100644 --- a/docs/plans/2026-06-21-001-feat-project-hierarchy-and-track-rename-plan.md +++ b/docs/plans/2026-06-21-001-feat-project-hierarchy-and-track-rename-plan.md @@ -33,10 +33,10 @@ Members live at the **Project** level. A member's status, PFP, and activity are ## Requirements -- R1: `Project` type exists in `@teambridge/core` with id, name, description, createdAt, status. +- R1: `Project` type exists in `@coord/core` with id, name, description, createdAt, status. - R2: `Workspace` type is renamed to `Track` throughout core contracts, daemon, vault, and dashboard. - R3: Each Track has a `projectId` foreign key. -- R4: `ProjectMember` type exists in `@teambridge/core` — participant with project-level status (active/idle/offline), PFP, displayName. +- R4: `ProjectMember` type exists in `@coord/core` — participant with project-level status (active/idle/offline), PFP, displayName. - R5: Daemon exposes project CRUD endpoints and project-member endpoints. - R6: Dashboard has a project-selection page at `/` that navigates to `/projects/:projectId`. - R7: Dashboard's topbar shows the project name, not the track name. @@ -168,7 +168,7 @@ flowchart LR ## Implementation Units -### U1. Update `@teambridge/core` — add Project and ProjectMember types, rename Workspace → Track +### U1. Update `@coord/core` — add Project and ProjectMember types, rename Workspace → Track **Goal:** Establish the canonical type layer. All downstream work derives from these contracts. @@ -194,12 +194,12 @@ flowchart LR **Patterns to follow:** Existing contract style in `packages/core/src/contracts/workspace.ts` **Test scenarios:** -- TypeScript compilation passes with zero errors after rename: `pnpm -F @teambridge/core build` +- TypeScript compilation passes with zero errors after rename: `pnpm -F @coord/core build` - `Track` has required `projectId` field; a Track without it fails type-check - `ProjectMember` has `projectId`, `displayName`, `status`; compiles cleanly - `StartTrackRequest` and `JoinTrackRequest` satisfy existing schema validators (Zod) -**Verification:** `pnpm -F @teambridge/core build` exits 0; no type errors in any package consuming core. +**Verification:** `pnpm -F @coord/core build` exits 0; no type errors in any package consuming core. --- @@ -248,7 +248,7 @@ Update all `querySql` strings: `from workspaces` → `from tracks`, `workspace_i - Daemon starts against a repo with old `workspaces` table; migration runs without error, data is preserved in `tracks` - `tracks.project_id` accepts null for legacy rows (migration adds column nullable) -**Verification:** Daemon process starts, `sqlite3 .teambridge/state.sqlite ".tables"` shows `projects`, `tracks`, `participants`, `project_members`, `checkpoints`, `local_sequences`. +**Verification:** Daemon process starts, `sqlite3 .coord/state.sqlite ".tables"` shows `projects`, `tracks`, `participants`, `project_members`, `checkpoints`, `local_sequences`. --- @@ -319,8 +319,8 @@ GET /tracks/:id/participants/:pid/avatar ... **Approach:** Mechanical find-and-replace of `workspaceId` → `trackId`, `workspace_id` → `track_id`, `Workspace` → `Track` where used as type imports. The vault files themselves (decisions.md, etc.) are untouched — only the TypeScript code referencing workspace IDs changes. **Test scenarios:** -- `pnpm -F @teambridge/vault build` exits 0 -- `pnpm -F @teambridge/mcp build` exits 0 +- `pnpm -F @coord/vault build` exits 0 +- `pnpm -F @coord/mcp build` exits 0 - Vault context creation with a `trackId` produces valid `VaultContext` object **Verification:** All packages build without errors: `pnpm build`. @@ -360,7 +360,7 @@ createBrowserRouter([ - Browser back/forward works between `/` and `/projects/:id` - `Test expectation: none` for visual appearance at this unit -**Verification:** `pnpm -F @teambridge/dashboard dev` starts; visiting `/` shows project selection stub; visiting `/projects/anything` shows dashboard stub. +**Verification:** `pnpm -F @coord/dashboard dev` starts; visiting `/` shows project selection stub; visiting `/projects/anything` shows dashboard stub. --- @@ -374,7 +374,7 @@ createBrowserRouter([ **Files:** - `apps/dashboard/src/pages/ProjectSelectionPage.tsx` (new) -- `apps/dashboard/src/api/teambridgeClient.ts` — add `listProjects()` function +- `apps/dashboard/src/api/coordClient.ts` — add `listProjects()` function - `apps/dashboard/src/pages/ProjectSelectionPage.test.tsx` (new) **Approach:** @@ -418,7 +418,7 @@ createBrowserRouter([ - `apps/dashboard/src/components/site-header.tsx` — replace workspace/track name chip with project name - `apps/dashboard/src/components/WorkspaceList.tsx` → `TrackList.tsx` — rename file and component; filter by projectId - `apps/dashboard/src/components/WorkspaceDetails.tsx` → `ProjectMemberSidebar.tsx` — rename; now lists `ProjectMember[]` from project endpoint instead of workspace participants -- `apps/dashboard/src/api/teambridgeClient.ts` — rename workspace API calls; add project-member API calls +- `apps/dashboard/src/api/coordClient.ts` — rename workspace API calls; add project-member API calls - `apps/dashboard/src/lib/cache.ts` — add `projectId` to cache key; add `projectMembers` shape **Approach:** @@ -493,7 +493,7 @@ createBrowserRouter([ **Seed approach:** - Script calls daemon HTTP API to create projects, members, and tracks via the new endpoints -- Vault content is written directly to filesystem at `.teambridge/tracks//vault/` via `initializePhaseOneVault` + `writeFile` +- Vault content is written directly to filesystem at `.coord/tracks//vault/` via `initializePhaseOneVault` + `writeFile` - Script first deletes all rows from `projects`, `project_members`, `tracks`, `participants` tables via `sqlite3` CLI (same pattern as previous seed scripts in the repo) - Idempotent: always wipes before re-seeding @@ -534,7 +534,7 @@ createBrowserRouter([ - `TrackList.test.tsx`: renders tracks for a given projectId; stagger animation not tested - `ProjectMemberSidebar.test.tsx`: renders project members with correct names; presence dots on non-offline members - `VaultHighlights.test.tsx`: vault fixture updated to match seed data shape -- All tests pass: `pnpm -F @teambridge/dashboard test` +- All tests pass: `pnpm -F @coord/dashboard test` **Verification:** `pnpm test` across all packages exits 0 with no failures. @@ -591,8 +591,8 @@ Summary: Project → Track hierarchy, dashboard routing, seed data, and avatars | Unit | Status | Notes | |------|--------|-------| -| **U1** Core types | **Done** | `Project`, `ProjectMember`, `Track` contracts in `@teambridge/core`. `Workspace` aliases retained for API compat. | -| **U2** SQLite schema | **Done** | `tracks`, `projects`, `project_members` tables. DB file is `.teambridge/state.sqlite` (not `teambridge.db`). | +| **U1** Core types | **Done** | `Project`, `ProjectMember`, `Track` contracts in `@coord/core`. `Workspace` aliases retained for API compat. | +| **U2** SQLite schema | **Done** | `tracks`, `projects`, `project_members` tables. DB file is `.coord/state.sqlite` (not `coord.db`). | | **U3** Daemon API | **Partial** | Added `/projects`, `/tracks`, avatar routes. **Deviation:** mutations/events/vault stay on `/workspaces/*`, not renamed to `/tracks/*`. | | **U4** Vault + MCP rename | **Partial** | Vault uses track terminology internally. MCP package still largely stub; mechanical rename incomplete. | | **U5** React Router | **Done** | `createBrowserRouter`; `/` → `ProjectSelectionPage`, `/projects/:projectId` → `DashboardPage`. | diff --git a/docs/plans/2026-06-27-001-docs-architecture-sync-plan.md b/docs/plans/2026-06-27-001-docs-architecture-sync-plan.md index f29a607..39252d8 100644 --- a/docs/plans/2026-06-27-001-docs-architecture-sync-plan.md +++ b/docs/plans/2026-06-27-001-docs-architecture-sync-plan.md @@ -67,7 +67,7 @@ This section is the explicit preview of edits. Implementation should follow it c | Today | Will become | |-------|-------------| -| Single line `# team-channel` | Product intro (Teambridge), prerequisites, quick start: `pnpm install`, `pnpm build`, `pnpm daemon`, `pnpm seed`, `pnpm dashboard` | +| Single line `# team-channel` | Product intro (Coord), prerequisites, quick start: `pnpm install`, `pnpm build`, `pnpm daemon`, `pnpm seed`, `pnpm dashboard` | | No links | Documentation index linking to `agent.md`, `docs/CONCEPTS.md`, `docs/daemon-api.md`, `docs/dashboard.md`, `docs/phase-1-design-choices.md` | | No monorepo map | Short package table: `core`, `daemon`, `vault`, `mcp`, `apps/dashboard` | @@ -80,7 +80,7 @@ This section is the explicit preview of edits. Implementation should follow it c | **Product Vision / example commands** | Add note: CLI examples are **target UX**; today use daemon + dashboard + seed. Keep commands as north-star. | | **Core Mental Model** | Add hierarchy diagram: `Project → Track (workspace session) → Vault`. Clarify Supabase/relay is Phase 2 optional. | | **Commands** | Split into “Planned CLI” vs “Available today” (daemon HTTP, dashboard). | -| **Workspace Vault** | Rename heading to **Track vault** with footnote: on-disk path remains `.teambridge/workspaces/{session_name}/vault/`. Add vault annotation syntax and annotate endpoint. | +| **Workspace Vault** | Rename heading to **Track vault** with footnote: on-disk path remains `.coord/workspaces/{session_name}/vault/`. Add vault annotation syntax and annotate endpoint. | | **Architecture** | Add dashboard (React Router), avatar generation, vault annotate flow. | | **Team Responsibilities — Ronish** | Mark dashboard shell, project picker, track sidebar, team sidebar, vault highlights + annotations as **shipped**. Keep inbox/MCP/conflicts as pending. | | **Contracts** | Mention `project.ts`, `avatar.ts`, `vault-annotations.ts`. | @@ -139,7 +139,7 @@ This section is the explicit preview of edits. Implementation should follow it c | **New “Completion log”** | Per U1–U9: Done / Partial / Deviation (hybrid API paths, seed names Beacon/Silo/Forge, cache not keyed by projectId, dead components remain). | | **Open Questions — Avatars** | Update: partially implemented via `/avatars/by-name/:slug` and member avatar routes. | | **Features beyond plan** | List vault annotations, team sidebar collapse, Pexels avatar migration. | -| **U2 verification** | Fix `teambridge.db` → `state.sqlite`. | +| **U2 verification** | Fix `coord.db` → `state.sqlite`. | --- @@ -160,7 +160,7 @@ Define: - **Project** — organizational container; has **ProjectMembers** (team roster). - **Track** — a coordinated work session (alias: `Workspace` in types/API); has **Participants** (per-track agents/branches). -- **Vault** — flat markdown projection per track under `.teambridge/workspaces/{sessionName}/vault/`. +- **Vault** — flat markdown projection per track under `.coord/workspaces/{sessionName}/vault/`. - **Naming map** — UI “track” vs HTTP `/workspaces/*` vs DB table `tracks` vs disk `workspaces/`. - **Avatar identity** — display name → slug → `name_{slug}` storage; assign tags use slug. - **Vault annotations** — optional row metadata in markdown, not events. @@ -188,7 +188,7 @@ Cover: - Scripts and ports (daemon 9473, Vite dev server) - Routes: `/` → `/projects`, `/projects/:projectId` -- Env: `VITE_TEAMBRIDGE_DAEMON_URL`, `VITE_TEAMBRIDGE_REPO_ROOT`; URL query overrides +- Env: `VITE_COORD_DAEMON_URL`, `VITE_COORD_REPO_ROOT`; URL query overrides - Prerequisites: run seed for demo data - UI map: project picker, track sidebar, vault highlights (color/assign), team sidebar - Link to vault annotation syntax in CONCEPTS @@ -223,7 +223,7 @@ Cover: **Files:** `README.md`, `docs/dashboard.md` -**Approach:** Quick start commands verified against root `package.json` scripts. Dashboard doc matches `apps/dashboard/src/App.tsx` routes and `teambridgeClient.ts` config. +**Approach:** Quick start commands verified against root `package.json` scripts. Dashboard doc matches `apps/dashboard/src/App.tsx` routes and `coordClient.ts` config. **Test expectation:** none — documentation only diff --git a/docs/plans/2026-07-02-001-feat-kushagra-cli-phase1-completion-plan.md b/docs/plans/2026-07-02-001-feat-kushagra-cli-phase1-completion-plan.md index 664879e..0d995be 100644 --- a/docs/plans/2026-07-02-001-feat-kushagra-cli-phase1-completion-plan.md +++ b/docs/plans/2026-07-02-001-feat-kushagra-cli-phase1-completion-plan.md @@ -11,24 +11,24 @@ deepened: null ## Summary -`todo.md` Steps 4/6/8 still list unchecked Kushagra CLI commands: `start`, `enter`, `publish`, `vault read/context/search`, and `ws show/who/branches`. Research into `packages/daemon` and `packages/core/src/contracts` shows the daemon already exposes working endpoints for publish, vault read, vault context, and workspace status (including `participant.branch`) — most of this is thin CLI plumbing. The one exception is `vault search`, which has no backing endpoint anywhere and needs a real indexed-search subsystem. Along the way, research surfaced a load-bearing gap: the CLI's repo-root resolution breaks when run from inside a Teambridge-managed worktree, which every command in this plan except `start` is designed to run from. +`todo.md` Steps 4/6/8 still list unchecked Kushagra CLI commands: `start`, `enter`, `publish`, `vault read/context/search`, and `ws show/who/branches`. Research into `packages/daemon` and `packages/core/src/contracts` shows the daemon already exposes working endpoints for publish, vault read, vault context, and workspace status (including `participant.branch`) — most of this is thin CLI plumbing. The one exception is `vault search`, which has no backing endpoint anywhere and needs a real indexed-search subsystem. Along the way, research surfaced a load-bearing gap: the CLI's repo-root resolution breaks when run from inside a Coord-managed worktree, which every command in this plan except `start` is designed to run from. ## Problem Frame Kushagra's Phase 1 CLI scope is otherwise complete (`init`, `status`, `project create/list`, `track start`, `track join --as`). The remaining commands are needed to prove the Phase 1 pass example end-to-end: a participant starts or joins a track, publishes into the flat vault, and reads/searches it back — all from inside their own git worktree. Two structural gaps block that flow today: -1. `packages/cli/src/repo.ts#resolveRepoRoot` shells out to `git rev-parse --show-toplevel`, which returns a **linked worktree's own path** when invoked from inside one — not the main repo root where `.teambridge/state.sqlite` and the vault actually live. Every command meant to run post-`enter` (`publish`, `vault read/context/search`) would silently target the wrong (nonexistent) workspace scope. +1. `packages/cli/src/repo.ts#resolveRepoRoot` shells out to `git rev-parse --show-toplevel`, which returns a **linked worktree's own path** when invoked from inside one — not the main repo root where `.coord/state.sqlite` and the vault actually live. Every command meant to run post-`enter` (`publish`, `vault read/context/search`) would silently target the wrong (nonexistent) workspace scope. 2. `vault search` has no daemon route, no query schema, and no storage. `PhaseOneVaultFile` content is flat markdown files with no full-text index today. ## Requirements -- R1. `teambridge start [base_ref]` performs the same daemon registration as `track start` and additionally creates a real, isolated git worktree + branch for the starter — symmetric with `track join`. -- R2. `teambridge enter ` prints the absolute worktree path for the current user's participation in that track, suitable for `cd "$(teambridge enter NAME)"`. -- R3. `teambridge publish ` appends a publish event to the track the current working directory belongs to. -- R4. `teambridge vault read ` and `teambridge vault context` return the corresponding vault content for the current track. -- R5. `teambridge vault search ` returns ranked matches from a real index, not a full-file scan, and the index stays consistent through `vault rebuild`. -- R6. `teambridge ws show|who|branches ` print workspace summary, participant list, and participant branches respectively. -- R7. CLI commands invoked from inside a Teambridge-managed worktree resolve to the correct (main) repo root, not the worktree's own toplevel. +- R1. `coord start [base_ref]` performs the same daemon registration as `track start` and additionally creates a real, isolated git worktree + branch for the starter — symmetric with `track join`. +- R2. `coord enter ` prints the absolute worktree path for the current user's participation in that track, suitable for `cd "$(coord enter NAME)"`. +- R3. `coord publish ` appends a publish event to the track the current working directory belongs to. +- R4. `coord vault read ` and `coord vault context` return the corresponding vault content for the current track. +- R5. `coord vault search ` returns ranked matches from a real index, not a full-file scan, and the index stays consistent through `vault rebuild`. +- R6. `coord ws show|who|branches ` print workspace summary, participant list, and participant branches respectively. +- R7. CLI commands invoked from inside a Coord-managed worktree resolve to the correct (main) repo root, not the worktree's own toplevel. - R8. `todo.md` reflects every checkbox this plan closes. ## Key Technical Decisions @@ -36,14 +36,14 @@ Kushagra's Phase 1 CLI scope is otherwise complete (`init`, `status`, `project c - **KTD1 — Vault search index storage: SQLite FTS5, not a hand-rolled inverted index.** The daemon already shells out to the `sqlite3` CLI for all state (`runSql`/`querySql`/`sqlValue` in `packages/daemon/src/index.ts`). FTS5 gives tokenizing, indexing, and `bm25`-style ranking for the cost of one virtual-table `CREATE VIRTUAL TABLE ... USING fts5(...)` and standard `INSERT`/`DELETE`/`MATCH` statements through those same helpers — no new SQLite driver, no hand-rolled tokenizer or TF scorer to maintain and rebuild-test. (Confirmed with user.) - **KTD2 — `start` creates a real worktree for the starter, matching `join`'s symmetry.** Today `track start` only registers the track with the daemon; the starter has no worktree of their own (only `join` calls `prepareJoinerWorktree`). `start` is the todo.md north-star alias described as the "full worktree+branch flow" — it should not be a behavior-identical rename. (Confirmed with user.) - **KTD3 — Repo-root resolution must detect linked worktrees.** `git rev-parse --show-toplevel` returns the immediate worktree's path, not the main repo's. `enter`, `publish`, and `vault *` are only meaningful when run from inside a participant's worktree (per the Phase 1 pass example), so `resolveRepoRoot` needs to detect a linked worktree and resolve back to the main repo root before sending `repoRoot` to the daemon. This is a prerequisite for R2-R6, not an optional hardening pass. -- **KTD4 — Current-track resolution comes from the git branch name, not a new flag.** `publish` and `vault *` have no `` argument in their todo.md signatures — they operate on "the current track." Branches created by `start`/`join` already encode the session (`teambridge//`, per `lib/naming.ts#branchForParticipant`). Reading `git rev-parse --abbrev-ref HEAD` from the literal invocation cwd (before KTD3's repo-root normalization) and parsing that pattern avoids inventing a second workspace-identity mechanism. +- **KTD4 — Current-track resolution comes from the git branch name, not a new flag.** `publish` and `vault *` have no `` argument in their todo.md signatures — they operate on "the current track." Branches created by `start`/`join` already encode the session (`coord//`, per `lib/naming.ts#branchForParticipant`). Reading `git rev-parse --abbrev-ref HEAD` from the literal invocation cwd (before KTD3's repo-root normalization) and parsing that pattern avoids inventing a second workspace-identity mechanism. ## Scope Boundaries **In scope:** `start`, `enter`, `publish`, `vault read/context/search`, `ws show/who/branches`, the repo-root worktree-detection fix, and the FTS5 search index (daemon + vault package). **Deferred to Follow-Up Work:** -- `teambridge ask`/`inbox`/`reply`, MCP HTTP server, Claude Code hook auto-injection (Phase 3 scope). +- `coord ask`/`inbox`/`reply`, MCP HTTP server, Claude Code hook auto-injection (Phase 3 scope). - Supabase relay, cross-device sync, checkpoint upload/download (Phase 2 scope). - Daemon background-service / auto-start packaging (noted in todo.md Step 4 as separate infra work). - Ranking beyond FTS5's built-in `bm25`-style ordering (e.g., recency weighting, per-file boosting) — out of scope unless FTS5's default ordering proves inadequate during implementation. @@ -58,7 +58,7 @@ Kushagra's Phase 1 CLI scope is otherwise complete (`init`, `status`, `project c ```mermaid sequenceDiagram - participant CLI as teambridge publish/vault search + participant CLI as coord publish/vault search participant Daemon participant Vault as packages/vault participant DB as state.sqlite (FTS5) @@ -100,7 +100,7 @@ flowchart LR ### U1. Fix repoRoot resolution for in-worktree CLI invocations -**Goal:** `resolveRepoRoot()` returns the main repo root even when invoked from inside a Teambridge-managed linked worktree, since `enter`, `publish`, and `vault *` are designed to run from there. +**Goal:** `resolveRepoRoot()` returns the main repo root even when invoked from inside a Coord-managed linked worktree, since `enter`, `publish`, and `vault *` are designed to run from there. **Requirements:** R7 (see KTD3) @@ -110,7 +110,7 @@ flowchart LR - `packages/cli/src/repo.ts` (modify) - `packages/cli/test/repo.test.cjs` (new) -**Approach:** Detect a linked worktree via `git rev-parse --path-format=absolute --git-common-dir` vs `git rev-parse --show-toplevel`. When the common-dir's parent differs from the toplevel, cwd is inside a linked worktree — use `git worktree list --porcelain` (mirrors the parsing already in `packages/cli/src/lib/git.ts#listWorktrees`) to find the main worktree entry and return its path. Otherwise return `show-toplevel` unchanged (today's behavior for the main repo root and any non-Teambridge worktree use). +**Approach:** Detect a linked worktree via `git rev-parse --path-format=absolute --git-common-dir` vs `git rev-parse --show-toplevel`. When the common-dir's parent differs from the toplevel, cwd is inside a linked worktree — use `git worktree list --porcelain` (mirrors the parsing already in `packages/cli/src/lib/git.ts#listWorktrees`) to find the main worktree entry and return its path. Otherwise return `show-toplevel` unchanged (today's behavior for the main repo root and any non-Coord worktree use). **Patterns to follow:** `packages/cli/src/lib/git.ts#listWorktrees` porcelain-output parsing. @@ -120,11 +120,11 @@ flowchart LR - Edge case: cwd is a subdirectory nested inside a linked worktree → still resolves to the main repo root. - Error path: cwd not inside any git repository → throws the existing "Not inside a git repository" error unchanged. -**Verification:** Running `teambridge status` from inside a freshly created worktree lists the same projects/tracks as running it from the main repo root. +**Verification:** Running `coord status` from inside a freshly created worktree lists the same projects/tracks as running it from the main repo root. --- -### U2. `teambridge start [base_ref]` +### U2. `coord start [base_ref]` **Goal:** North-star alias over `track start` that also creates a real worktree/branch for the starter (KTD2). @@ -145,18 +145,18 @@ flowchart LR **Patterns to follow:** `packages/cli/src/commands/track.ts#runTrackJoin` (git-first creation, scoped rollback, pointer write, idempotent-reuse messaging). **Test scenarios:** -- Happy path: `start` registers the track via the daemon and creates a real worktree/branch under `.teambridge/worktrees//`, writing a pointer with `role: 'creator'`. +- Happy path: `start` registers the track via the daemon and creates a real worktree/branch under `.coord/worktrees//`, writing a pointer with `role: 'creator'`. - Edge case: re-running `start` for a session/display name that already has a registered worktree → reused, not recreated (mirrors join's `reused` case). - Error path: worktree creation fails after the daemon track already exists → local worktree/branch rolled back, the daemon-registered track is left in place, and a clear error is surfaced (not a silent partial state). -- Integration: after `start`, `teambridge status` and `ws who` (U8) both show the starter as a participant with the created branch. +- Integration: after `start`, `coord status` and `ws who` (U8) both show the starter as a participant with the created branch. -**Verification:** `teambridge start` produces the same worktree/branch shape as `teambridge track join`, and status/ws commands reflect the new participant immediately. +**Verification:** `coord start` produces the same worktree/branch shape as `coord track join`, and status/ws commands reflect the new participant immediately. --- -### U3. `teambridge enter ` +### U3. `coord enter ` -**Goal:** Resolve and print the current user's worktree path for a track, composing with `cd "$(teambridge enter NAME)"`. +**Goal:** Resolve and print the current user's worktree path for a track, composing with `cd "$(coord enter NAME)"`. **Requirements:** R2 @@ -173,14 +173,14 @@ flowchart LR **Test scenarios:** - Happy path: a pointer exists for this session + display name → prints exactly the worktree path to stdout, nothing else. -- Error path: no pointer found (never started/joined this track) → non-zero exit, stderr message naming `teambridge start`/`teambridge track join` as the next step. +- Error path: no pointer found (never started/joined this track) → non-zero exit, stderr message naming `coord start`/`coord track join` as the next step. - Error path: the pointer exists but the worktree directory no longer exists on disk (manually removed) → clear error rather than silently returning a dead path. -**Verification:** `cd "$(teambridge enter )"` lands in the correct worktree directory for both a `start`-created and a `join`-created participant. +**Verification:** `cd "$(coord enter )"` lands in the correct worktree directory for both a `start`-created and a `join`-created participant. --- -### U4. `teambridge publish ` +### U4. `coord publish ` **Goal:** Thin wrapper appending a publish event to whichever track the current working directory belongs to (KTD4). @@ -196,7 +196,7 @@ flowchart LR - `packages/cli/test/publish.test.cjs` (new) - `packages/cli/test/current-track.test.cjs` (new) -**Approach:** Read `git rev-parse --abbrev-ref HEAD` from the literal invocation cwd (before U1's repo-root normalization changes cwd handling), match it against `teambridge//` (the same shape `lib/naming.ts#branchForParticipant` produces), extract `sessionName`, then resolve the workspace via `listTracks` + session-name match (same lookup pattern `track join` already uses). POST the publish event through the existing `/workspaces/:id/events` route. +**Approach:** Read `git rev-parse --abbrev-ref HEAD` from the literal invocation cwd (before U1's repo-root normalization changes cwd handling), match it against `coord//` (the same shape `lib/naming.ts#branchForParticipant` produces), extract `sessionName`, then resolve the workspace via `listTracks` + session-name match (same lookup pattern `track join` already uses). POST the publish event through the existing `/workspaces/:id/events` route. **Patterns to follow:** `packages/cli/src/commands/track.ts#runTrackJoin`'s track-lookup-by-sessionName block; `packages/vault/src/index.ts#formatPublishText`'s trim/empty validation (mirror client-side to fail fast). @@ -204,13 +204,13 @@ flowchart LR - Happy path: run from inside a track worktree, `publish decisions.md "text"` appends successfully; CLI prints confirmation including the resulting `seq`. - Edge case: text is empty or whitespace-only → rejected client-side with a clear error before any daemon call (mirrors the server-side trim requirement in `formatPublishText`). - Error path: `target_file` is not one of the Phase 1 flat vault files → the daemon's existing validation error message is passed through unchanged. -- Error path: current branch does not match the `teambridge//` convention (not inside a track worktree) → clear error telling the user to `cd` into a track worktree via `teambridge enter` first. +- Error path: current branch does not match the `coord//` convention (not inside a track worktree) → clear error telling the user to `cd` into a track worktree via `coord enter` first. -**Verification:** After `publish`, `teambridge vault read ` (U5) shows the appended line. +**Verification:** After `publish`, `coord vault read ` (U5) shows the appended line. --- -### U5. `teambridge vault read ` and `teambridge vault context` +### U5. `coord vault read ` and `coord vault context` **Goal:** Thin wrappers over the existing vault read/context daemon endpoints, using U4's current-track resolution. @@ -276,7 +276,7 @@ on rebuild(workspaceId): --- -### U7. `teambridge vault search ` CLI wrapper +### U7. `coord vault search ` CLI wrapper **Goal:** Thin CLI wrapper over U6's search endpoint, reusing U4/U5's current-track resolution. @@ -295,11 +295,11 @@ on rebuild(workspaceId): - Happy path: `vault search "invoice state"` after publishing that phrase returns the matching line formatted as `path:line: text`. - Edge case: no results → a clear "no matches" message on stdout, exit code 0 (not treated as an error). -**Verification:** `teambridge publish decisions.md "invoice state"` followed by `teambridge vault search "invoice state"` returns that line. +**Verification:** `coord publish decisions.md "invoice state"` followed by `coord vault search "invoice state"` returns that line. --- -### U8. `teambridge ws show|who|branches ` +### U8. `coord ws show|who|branches ` **Goal:** Thin CLI formatting over the existing track-lookup + workspace-status endpoints, which already return `participant.branch`. diff --git a/docs/plans/2026-07-06-001-feat-relay-dashboard-screens-plan.md b/docs/plans/2026-07-06-001-feat-relay-dashboard-screens-plan.md index e760160..2231094 100644 --- a/docs/plans/2026-07-06-001-feat-relay-dashboard-screens-plan.md +++ b/docs/plans/2026-07-06-001-feat-relay-dashboard-screens-plan.md @@ -87,7 +87,7 @@ flowchart TB end subgraph Dashboard["apps/dashboard/src"] - D[teambridgeClient.ts\n+ getRelayStatus\n+ getWorkspaceEvents] + D[coordClient.ts\n+ getRelayStatus\n+ getWorkspaceEvents] E[RelaySyncHealth.tsx] F[EventFeed.tsx] G[CheckpointState.tsx] @@ -147,7 +147,7 @@ flowchart TB - Validation: `RelayModeSchema` accepts both `'local'` and `'supabase'`; rejects `'remote'` or other strings. - Validation: `WorkspaceSchema` accepts a workspace with `relayMode: 'supabase'` (previously would fail). -**Verification:** `pnpm --filter @teambridge/core build && pnpm --filter @teambridge/core test` passes. New types are importable from `@teambridge/core`. +**Verification:** `pnpm --filter @coord/core build && pnpm --filter @coord/core test` passes. New types are importable from `@coord/core`. ### U2. Daemon: camelCase transform for relay status sync rows @@ -181,12 +181,12 @@ flowchart TB **Dependencies:** U1 **Files:** -- `apps/dashboard/src/api/teambridgeClient.ts` — add `getRelayStatus(config, signal)` calling `GET /relay/status`, add `getWorkspaceEvents(workspaceId, config, signal)` calling `GET /workspaces/:id/events` -- `apps/dashboard/src/api/teambridgeClient.test.ts` — add tests for both new functions +- `apps/dashboard/src/api/coordClient.ts` — add `getRelayStatus(config, signal)` calling `GET /relay/status`, add `getWorkspaceEvents(workspaceId, config, signal)` calling `GET /workspaces/:id/events` +- `apps/dashboard/src/api/coordClient.test.ts` — add tests for both new functions **Approach:** Both functions follow the existing `getJson` pattern. `getRelayStatus` returns `RelayStatusResponse`. `getWorkspaceEvents` returns `EventListResponse` (already defined in core contracts). -**Patterns to follow:** Existing `getWorkspaceStatus` and `getVaultContext` in `teambridgeClient.ts` — same `getJson` + `AbortSignal` pattern. +**Patterns to follow:** Existing `getWorkspaceStatus` and `getVaultContext` in `coordClient.ts` — same `getJson` + `AbortSignal` pattern. **Test scenarios:** - Happy path: `getRelayStatus` calls `GET /relay/status` with the correct URL and returns the parsed `RelayStatusResponse`. @@ -194,7 +194,7 @@ flowchart TB - Edge case: `getRelayStatus` propagates the error message when the daemon returns a non-OK response. - Integration: both functions pass `repoRoot` from `config` as a query parameter when set. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. Both functions are exported and callable. +**Verification:** `pnpm --filter @coord/dashboard test` passes. Both functions are exported and callable. ### U4. Relay sync health panel @@ -223,7 +223,7 @@ flowchart TB - Loading state: renders "Loading relay status…" placeholder when data is undefined and no error. - Error state: renders error message in destructive color when `error` prop is set. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. Component renders correctly for all relay states including error. +**Verification:** `pnpm --filter @coord/dashboard test` passes. Component renders correctly for all relay states including error. ### U5. Event feed panel @@ -251,7 +251,7 @@ flowchart TB - Event type rendering: renders `conflict_detected` and `checkpoint_created` type badges with distinct colors and text labels. - Error state: renders error message in destructive color when `error` prop is set. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. Component renders events correctly including error state. +**Verification:** `pnpm --filter @coord/dashboard test` passes. Component renders events correctly including error state. ### U6. Checkpoint state placeholder panel @@ -274,7 +274,7 @@ flowchart TB - Happy path: renders checkpoint seq, relative created at, truncated hash, and device ID when `latestCheckpoint` is present. - Edge case: renders gracefully when `latestCheckpoint` exists but `createdByDeviceId` is empty. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. Component renders both states correctly. +**Verification:** `pnpm --filter @coord/dashboard test` passes. Component renders both states correctly. ### U7. Presence enhancement: lastSeenAt in TrackParticipantsPanel @@ -297,7 +297,7 @@ flowchart TB - Edge case: does not render relative time when `lastSeenAt` is absent or empty. - Existing behavior: status dots, branch, agent, and worktree "Enter" button still render correctly. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. Existing `TrackParticipantsPanel` tests still pass. +**Verification:** `pnpm --filter @coord/dashboard test` passes. Existing `TrackParticipantsPanel` tests still pass. ### U8. Dashboard integration: relay tab + header sync badge @@ -333,7 +333,7 @@ flowchart TB - Tab switching: switching away from "Relay" tab and back preserves the relay data. - Accessibility: relay tab has `role="tab"` and `aria-controls`; header badge has `role="status"` and `aria-live="polite"`. -**Verification:** `pnpm build` succeeds. `pnpm --filter @teambridge/dashboard test` passes. `pnpm test:integration` passes. Dashboard renders relay tab with live data when daemon is running. +**Verification:** `pnpm build` succeeds. `pnpm --filter @coord/dashboard test` passes. `pnpm test:integration` passes. Dashboard renders relay tab with live data when daemon is running. --- @@ -341,9 +341,9 @@ flowchart TB | What | Command | Applies to | |------|---------|------------| -| Core contract tests | `pnpm --filter @teambridge/core test` | U1 | +| Core contract tests | `pnpm --filter @coord/core test` | U1 | | Daemon transform | `pnpm test:integration` | U2 | -| Dashboard unit tests | `pnpm --filter @teambridge/dashboard test` | U3, U4, U5, U6, U7, U8 | +| Dashboard unit tests | `pnpm --filter @coord/dashboard test` | U3, U4, U5, U6, U7, U8 | | Full build | `pnpm build` | All units | | Integration tests | `pnpm test:integration` | U2, U8 (no regressions) | @@ -351,7 +351,7 @@ flowchart TB - All 8 implementation units are complete with their test scenarios passing. - `pnpm build` succeeds with no TypeScript errors. -- `pnpm --filter @teambridge/core test`, `pnpm --filter @teambridge/dashboard test`, and `pnpm test:integration` all pass. +- `pnpm --filter @coord/core test`, `pnpm --filter @coord/dashboard test`, and `pnpm test:integration` all pass. - The dashboard "Relay" tab renders sync health, event feed, checkpoint state, and enhanced presence when the daemon is running. - The site header shows a relay sync badge with all states handled (Local, Not logged in, Error, Offline, Pending, Synced). - All relay panels define error states for API call failures. diff --git a/docs/plans/2026-07-06-002-feat-mcp-relay-contracts-plan.md b/docs/plans/2026-07-06-002-feat-mcp-relay-contracts-plan.md index 87f84df..d82f313 100644 --- a/docs/plans/2026-07-06-002-feat-mcp-relay-contracts-plan.md +++ b/docs/plans/2026-07-06-002-feat-mcp-relay-contracts-plan.md @@ -33,9 +33,9 @@ The MCP resource contracts in `packages/core/src/contracts/mcp.ts` and the stub ### Requirements -- R1. The MCP workspace resource (`teambridge://workspace`) includes optional relay sync state (`relayStatus?: RelayStatusResponse`) alongside the existing `WorkspaceStatusResponse`. -- R2. The MCP participants resource (`teambridge://participants`) type documents that presence data (status, lastSeenAt) is relay-backed when relay mode is active. -- R3. The MCP stub resolver (`resolveMcpResource` in `packages/mcp/src/resources.ts`) passes relay status through when resolving `teambridge://workspace` and relay is configured. +- R1. The MCP workspace resource (`coord://workspace`) includes optional relay sync state (`relayStatus?: RelayStatusResponse`) alongside the existing `WorkspaceStatusResponse`. +- R2. The MCP participants resource (`coord://participants`) type documents that presence data (status, lastSeenAt) is relay-backed when relay mode is active. +- R3. The MCP stub resolver (`resolveMcpResource` in `packages/mcp/src/resources.ts`) passes relay status through when resolving `coord://workspace` and relay is configured. - R4. The MCP daemon client (`packages/mcp/src/daemon-client.ts`) includes a `getRelayStatus` function that calls `GET /relay/status`. - R5. The stub resolver degrades gracefully when relay is not configured (returns workspace status without relay fields). @@ -57,13 +57,13 @@ The MCP resource contracts in `packages/core/src/contracts/mcp.ts` and the stub - **KTD1 — `McpResourceResponse` is updated in `packages/mcp/src/resources.ts`, not `packages/core/src/contracts/mcp.ts`.** The `McpResourceResponse` union type is defined in `packages/mcp/src/resources.ts`, not in the core contracts. The core `mcp.ts` file only contains resource names, tool names, and request context types. The plan adds a new `McpWorkspaceResourceResponse` type in `packages/core/src/contracts/mcp.ts` that extends `WorkspaceStatusResponse` with an optional `relayStatus` field, and the `McpResourceResponse` union in `resources.ts` uses it for the workspace resource variant. -- **KTD2 — Relay status is optional on the workspace resource, not a separate resource.** Rather than adding a `teambridge://relay/status` resource, relay sync state is an optional field on the existing `teambridge://workspace` resource. This keeps the resource count unchanged and lets the MCP server fetch relay status alongside workspace status in one resolution call. When relay is not configured, the field is absent and the resource degrades to the current behavior. +- **KTD2 — Relay status is optional on the workspace resource, not a separate resource.** Rather than adding a `coord://relay/status` resource, relay sync state is an optional field on the existing `coord://workspace` resource. This keeps the resource count unchanged and lets the MCP server fetch relay status alongside workspace status in one resolution call. When relay is not configured, the field is absent and the resource degrades to the current behavior. -- **KTD3 — Graceful degradation when relay is not configured.** The stub resolver attempts to fetch relay status when resolving `teambridge://workspace`. If the daemon returns an error (relay not configured, not logged in, or endpoint unavailable), the resolver returns workspace status without the `relayStatus` field rather than failing the entire resource resolution. This matches the dashboard's approach of showing "Local only" when relay is off. +- **KTD3 — Graceful degradation when relay is not configured.** The stub resolver attempts to fetch relay status when resolving `coord://workspace`. If the daemon returns an error (relay not configured, not logged in, or endpoint unavailable), the resolver returns workspace status without the `relayStatus` field rather than failing the entire resource resolution. This matches the dashboard's approach of showing "Local only" when relay is off. ### Assumptions -- The `RelayStatusResponse` and `SyncStateEntry` types from the dashboard plan (U1) are available in `@teambridge/core` before this plan's work begins. +- The `RelayStatusResponse` and `SyncStateEntry` types from the dashboard plan (U1) are available in `@coord/core` before this plan's work begins. - The daemon's `/relay/status` endpoint is stable in shape (camelCase sync rows after the dashboard plan's U2 transform). --- @@ -92,7 +92,7 @@ The MCP resource contracts in `packages/core/src/contracts/mcp.ts` and the stub - Edge case: a `McpWorkspaceResourceResponse` without `relayStatus` (undefined) is valid — the field is optional. - Type compatibility: a plain `WorkspaceStatusResponse` is assignable to `McpWorkspaceResourceResponse` (backward compatible). -**Verification:** `pnpm --filter @teambridge/core build && pnpm --filter @teambridge/core test` passes. New type is importable from `@teambridge/core`. +**Verification:** `pnpm --filter @coord/core build && pnpm --filter @coord/core test` passes. New type is importable from `@coord/core`. ### U2. MCP stub resolver: relay-backed workspace resource @@ -103,23 +103,23 @@ The MCP resource contracts in `packages/core/src/contracts/mcp.ts` and the stub **Dependencies:** U1 **Files:** -- `packages/mcp/src/resources.ts` — update `McpResourceResponse` union to use `McpWorkspaceResourceResponse` for the `teambridge://workspace` variant; update `resolveMcpResource` to fetch relay status alongside workspace status and attach it; add graceful degradation when relay fetch fails +- `packages/mcp/src/resources.ts` — update `McpResourceResponse` union to use `McpWorkspaceResourceResponse` for the `coord://workspace` variant; update `resolveMcpResource` to fetch relay status alongside workspace status and attach it; add graceful degradation when relay fetch fails - `packages/mcp/src/daemon-client.ts` — add `getRelayStatus` function that calls `GET /relay/status` and returns `ApiResult` - `packages/mcp/test/resources.test.cjs` — add tests for relay-backed resource resolution and graceful degradation -**Approach:** Add `getRelayStatus` to the MCP daemon client mirroring the existing `getWorkspaceStatus` pattern. Update `resolveMcpResource` for `teambridge://workspace` to call both `getWorkspaceStatus` and `getRelayStatus` in parallel. If `getRelayStatus` fails (relay not configured, not logged in, or error), return the workspace status without `relayStatus` — graceful degradation. If it succeeds, attach `relayStatus` to the response. Add `getRelayStatus` to the `McpDaemonReader` type. +**Approach:** Add `getRelayStatus` to the MCP daemon client mirroring the existing `getWorkspaceStatus` pattern. Update `resolveMcpResource` for `coord://workspace` to call both `getWorkspaceStatus` and `getRelayStatus` in parallel. If `getRelayStatus` fails (relay not configured, not logged in, or error), return the workspace status without `relayStatus` — graceful degradation. If it succeeds, attach `relayStatus` to the response. Add `getRelayStatus` to the `McpDaemonReader` type. **Patterns to follow:** Existing `resolveMcpResource` pattern in `resources.ts` (switch on resource name, call daemon reader, return `ApiResult`). Existing daemon-client pattern in `daemon-client.ts` (thin `getJson` wrapper returning `ApiResult`). **Test scenarios:** -- Happy path: `resolveMcpResource('teambridge://workspace', { workspaceId })` returns workspace status with `relayStatus` attached when both `getWorkspaceStatus` and `getRelayStatus` succeed. -- Edge case: `resolveMcpResource('teambridge://workspace', { workspaceId })` returns workspace status without `relayStatus` when `getRelayStatus` fails (relay not configured) — no error propagated. -- Happy path: `resolveMcpResource('teambridge://participants', { workspaceId })` returns participants array (unchanged behavior). -- Error path: `resolveMcpResource('teambridge://unknown')` returns `apiFail('NOT_FOUND', ...)` (unchanged). -- Error path: `resolveMcpResource('teambridge://workspace', { workspaceId })` returns error when `getWorkspaceStatus` fails (workspace not found) — relay status is not fetched. +- Happy path: `resolveMcpResource('coord://workspace', { workspaceId })` returns workspace status with `relayStatus` attached when both `getWorkspaceStatus` and `getRelayStatus` succeed. +- Edge case: `resolveMcpResource('coord://workspace', { workspaceId })` returns workspace status without `relayStatus` when `getRelayStatus` fails (relay not configured) — no error propagated. +- Happy path: `resolveMcpResource('coord://participants', { workspaceId })` returns participants array (unchanged behavior). +- Error path: `resolveMcpResource('coord://unknown')` returns `apiFail('NOT_FOUND', ...)` (unchanged). +- Error path: `resolveMcpResource('coord://workspace', { workspaceId })` returns error when `getWorkspaceStatus` fails (workspace not found) — relay status is not fetched. - Happy path: `getRelayStatus` calls `GET /relay/status` and returns `ApiResult`. -**Verification:** `pnpm --filter @teambridge/mcp build && pnpm --filter @teambridge/mcp test` passes. `resolveMcpResource` returns relay status on the workspace resource when available. +**Verification:** `pnpm --filter @coord/mcp build && pnpm --filter @coord/mcp test` passes. `resolveMcpResource` returns relay status on the workspace resource when available. --- @@ -127,15 +127,15 @@ The MCP resource contracts in `packages/core/src/contracts/mcp.ts` and the stub | What | Command | Applies to | |------|---------|------------| -| Core contract tests | `pnpm --filter @teambridge/core test` | U1 | -| MCP stub tests | `pnpm --filter @teambridge/mcp test` | U2 | +| Core contract tests | `pnpm --filter @coord/core test` | U1 | +| MCP stub tests | `pnpm --filter @coord/mcp test` | U2 | | Full build | `pnpm build` | All units | ## Definition of Done - All 2 implementation units are complete with their test scenarios passing. - `pnpm build` succeeds with no TypeScript errors. -- `pnpm --filter @teambridge/core test` and `pnpm --filter @teambridge/mcp test` pass. +- `pnpm --filter @coord/core test` and `pnpm --filter @coord/mcp test` pass. - MCP resource contracts in `packages/core` include relay-backed fields (`McpWorkspaceResourceResponse`). - The stub resolver in `packages/mcp/src/resources.ts` passes relay status through on the workspace resource. - The resolver degrades gracefully when relay is not configured. diff --git a/docs/plans/2026-07-07-001-feat-mcp-server-stdio-resources-tools-plan.md b/docs/plans/2026-07-07-001-feat-mcp-server-stdio-resources-tools-plan.md index dcd5bc9..924ff3b 100644 --- a/docs/plans/2026-07-07-001-feat-mcp-server-stdio-resources-tools-plan.md +++ b/docs/plans/2026-07-07-001-feat-mcp-server-stdio-resources-tools-plan.md @@ -13,9 +13,9 @@ execution: code ## Goal Capsule -- **Objective:** Build a local MCP server using `@modelcontextprotocol/sdk` with stdio transport that exposes 5 resources and 6 tools, enabling agents like Claude Code to read workspace state and publish vault entries through the Teambridge daemon. +- **Objective:** Build a local MCP server using `@modelcontextprotocol/sdk` with stdio transport that exposes 5 resources and 6 tools, enabling agents like Claude Code to read workspace state and publish vault entries through the Coord daemon. - **Authority:** Ronish's Phase 3 Step 2 + Step 3 scope from `todo.md`. -- **Stop conditions:** MCP server starts via `teambridge mcp`, registers all resources and tools, stdio transport works with Claude Code, 4 unblocked tools call the daemon correctly, 2 blocked tools return not-implemented, tests pass, `pnpm build` succeeds. +- **Stop conditions:** MCP server starts via `coord mcp`, registers all resources and tools, stdio transport works with Claude Code, 4 unblocked tools call the daemon correctly, 2 blocked tools return not-implemented, tests pass, `pnpm build` succeeds. - **Execution profile:** Server + contract work. New `packages/mcp/src/server.ts` entry point, workspace resolution logic, daemon client additions, CLI command wiring. - **Tail ownership:** Ronish. @@ -23,7 +23,7 @@ execution: code ### Summary -This plan builds the MCP HTTP server (Phase 3 Step 2) as a stdio server using the official `@modelcontextprotocol/sdk`. It registers 5 resources (`teambridge://workspace`, `teambridge://participants`, `teambridge://vault/context`, `teambridge://inbox`, `teambridge://conflicts`) and 6 tools (`team_publish`, `vault_search`, `vault_read`, `workspace_status`, `team_ask`, `team_reply`). The 4 unblocked tools call the daemon directly. The 2 blocked tools (`team_ask`, `team_reply`) and 2 blocked resources (`teambridge://inbox`, `teambridge://conflicts`) return not-implemented stubs until Nihal ships inbox/conflict daemon endpoints. +This plan builds the MCP HTTP server (Phase 3 Step 2) as a stdio server using the official `@modelcontextprotocol/sdk`. It registers 5 resources (`coord://workspace`, `coord://participants`, `coord://vault/context`, `coord://inbox`, `coord://conflicts`) and 6 tools (`team_publish`, `vault_search`, `vault_read`, `workspace_status`, `team_ask`, `team_reply`). The 4 unblocked tools call the daemon directly. The 2 blocked tools (`team_ask`, `team_reply`) and 2 blocked resources (`coord://inbox`, `coord://conflicts`) return not-implemented stubs until Nihal ships inbox/conflict daemon endpoints. ### Problem Frame @@ -32,10 +32,10 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar ### Requirements - R1. The MCP server starts over stdio transport using `@modelcontextprotocol/sdk`'s `McpServer` + `StdioServerTransport`. -- R2. The server resolves the active workspace from: explicit query params (workspaceId or sessionName) first, then CWD header, then `state.sqlite` worktree path mapping, then `.teambridge/.active` fallback. -- R3. The server registers 5 resources: `teambridge://workspace` (with relay status), `teambridge://participants`, `teambridge://vault/context`, `teambridge://inbox` (stub), `teambridge://conflicts` (stub). +- R2. The server resolves the active workspace from: explicit query params (workspaceId or sessionName) first, then CWD header, then `state.sqlite` worktree path mapping, then `.coord/.active` fallback. +- R3. The server registers 5 resources: `coord://workspace` (with relay status), `coord://participants`, `coord://vault/context`, `coord://inbox` (stub), `coord://conflicts` (stub). - R4. The server registers 6 tools: `team_publish`, `vault_search`, `vault_read`, `workspace_status` (all calling the daemon), `team_ask` and `team_reply` (stubs returning not-implemented). -- R5. The `teambridge mcp` CLI command spawns the MCP server process. +- R5. The `coord mcp` CLI command spawns the MCP server process. - R6. The daemon client gains `postJson`, `publishEvent`, and `searchVault` functions to support the publish and search tools. - R7. Blocked stubs return clear not-implemented errors so agents know the capability exists but is awaiting backend work. @@ -45,10 +45,10 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar **Deferred to Follow-Up Work:** - `team_ask` and `team_reply` real implementation (blocked on Nihal's inbox daemon endpoints, Phase 3 Step 1). -- `teambridge://inbox` and `teambridge://conflicts` real data (blocked on Nihal's inbox/conflict endpoints). +- `coord://inbox` and `coord://conflicts` real data (blocked on Nihal's inbox/conflict endpoints). - Claude Code hook auto-injection (Kushagra, Phase 3 Step 2). - Dashboard inbox/conflicts UI (blocked on Nihal, Phase 3 Step 4). -- MCP workspace resolution via client CWD headers (the `cwd` field in `McpRequestContext` is accepted but CWD-to-worktree mapping via `state.sqlite` is deferred; U2 implements `.teambridge/.active` and explicit params only, with CWD as a stretch goal). +- MCP workspace resolution via client CWD headers (the `cwd` field in `McpRequestContext` is accepted but CWD-to-worktree mapping via `state.sqlite` is deferred; U2 implements `.coord/.active` and explicit params only, with CWD as a stretch goal). --- @@ -58,11 +58,11 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - **KTD1 — Use `@modelcontextprotocol/sdk` with `McpServer` and `StdioServerTransport`.** The official SDK handles JSON-RPC framing, capability negotiation, and the protocol-level details. `StdioServerTransport` is the correct transport for Claude Code, which spawns the MCP server as a subprocess and communicates over stdin/stdout. No HTTP server is needed. -- **KTD2 — Workspace resolution is a layered fallback: explicit params > `.teambridge/.active` > error.** The MCP server receives `workspaceId` or `sessionName` as explicit parameters when available. When absent, it reads `.teambridge/.active` (a file containing the active session name) as a fallback. CWD-to-worktree mapping via `state.sqlite` is deferred to keep the initial implementation simple — the `.teambridge/.active` file covers the common case where a user is working inside a Teambridge worktree. +- **KTD2 — Workspace resolution is a layered fallback: explicit params > `.coord/.active` > error.** The MCP server receives `workspaceId` or `sessionName` as explicit parameters when available. When absent, it reads `.coord/.active` (a file containing the active session name) as a fallback. CWD-to-worktree mapping via `state.sqlite` is deferred to keep the initial implementation simple — the `.coord/.active` file covers the common case where a user is working inside a Coord worktree. -- **KTD3 — Blocked tools and resources return structured not-implemented errors, not silent empty data.** `team_ask` and `team_reply` return `isError: true` with a message explaining the inbox endpoint is not yet implemented. `teambridge://inbox` and `teambridge://conflicts` resources return empty arrays with a note. This signals to the agent that the capability exists but is awaiting backend work, rather than looking like there are no inbox messages or conflicts. +- **KTD3 — Blocked tools and resources return structured not-implemented errors, not silent empty data.** `team_ask` and `team_reply` return `isError: true` with a message explaining the inbox endpoint is not yet implemented. `coord://inbox` and `coord://conflicts` resources return empty arrays with a note. This signals to the agent that the capability exists but is awaiting backend work, rather than looking like there are no inbox messages or conflicts. -- **KTD4 — The `teambridge mcp` CLI command spawns the server as a Node subprocess.** The CLI command runs `node packages/mcp/dist/server.js` which starts the stdio transport. Claude Code's MCP configuration points to this command. The CLI is a thin wrapper — no argument parsing needed beyond optional `--repo` for repo root. +- **KTD4 — The `coord mcp` CLI command spawns the server as a Node subprocess.** The CLI command runs `node packages/mcp/dist/server.js` which starts the stdio transport. Claude Code's MCP configuration points to this command. The CLI is a thin wrapper — no argument parsing needed beyond optional `--repo` for repo root. - **KTD5 — Resources use the existing `resolveMcpResource` function, tools use a new `resolveMcpTool` function.** The existing `resolveMcpResource` in `resources.ts` already handles workspace, participants, and vault context. A parallel `resolveMcpTool` function in a new `tools.ts` (replacing the current stub) dispatches tool calls to the appropriate daemon client function. Both functions share the same `McpResourceContext` for workspace resolution. @@ -70,7 +70,7 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - `@modelcontextprotocol/sdk` v1.x is compatible with Node.js 22+ and the pnpm workspace. - The daemon is running on `http://127.0.0.1:9473` when the MCP server is invoked. -- `.teambridge/.active` contains the active session name (written by future hook injection or CLI `enter` command). +- `.coord/.active` contains the active session name (written by future hook injection or CLI `enter` command). - The existing `resolveMcpResource` function and `McpDaemonReader` type are stable and can be extended. --- @@ -98,9 +98,9 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - Happy path: `searchVault('ws_123', 'invoice', { repoRoot: '/tmp/repo' })` calls `GET /workspaces/ws_123/vault/search?query=invoice&repoRoot=...`. - Edge case: `searchVault` with `limit` parameter includes it in the query string. -**Verification:** `pnpm --filter @teambridge/mcp build && pnpm --filter @teambridge/mcp test` passes. +**Verification:** `pnpm --filter @coord/mcp build && pnpm --filter @coord/mcp test` passes. -### U2. Workspace resolution: explicit params and .teambridge/.active fallback +### U2. Workspace resolution: explicit params and .coord/.active fallback **Goal:** Create a workspace resolution function that determines which workspace an MCP request targets. @@ -112,19 +112,19 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - `packages/mcp/src/resolution.ts` (new) — `resolveWorkspaceContext` function that resolves `(workspaceId?, sessionName?, repoRoot?)` to an `McpResourceContext` with a valid `workspaceId` - `packages/mcp/test/resolution.test.cjs` (new) — tests for resolution paths -**Approach:** `resolveWorkspaceContext` checks: (1) explicit `workspaceId` or `sessionName` param — return immediately if present. (2) Read `.teambridge/.active` file at the repo root — if it contains a session name, use it. (3) If neither resolves, throw an error with a clear message. The function returns `McpResourceContext` (which includes `workspaceId` and daemon options). The repo root is determined from the `repoRoot` param or defaults to `process.cwd()` walked up to find `.git`. +**Approach:** `resolveWorkspaceContext` checks: (1) explicit `workspaceId` or `sessionName` param — return immediately if present. (2) Read `.coord/.active` file at the repo root — if it contains a session name, use it. (3) If neither resolves, throw an error with a clear message. The function returns `McpResourceContext` (which includes `workspaceId` and daemon options). The repo root is determined from the `repoRoot` param or defaults to `process.cwd()` walked up to find `.git`. **Patterns to follow:** `resolveWorkspaceId` in `resources.ts` (existing fallback logic). `getWorkspaceByIdentifier` in daemon (accepts both ID and session name). **Test scenarios:** - Happy path: explicit `workspaceId` param returns immediately with that ID. - Happy path: explicit `sessionName` param returns with that name as the workspace identifier. -- Happy path: `.teambridge/.active` file containing `billing-refactor` resolves to that session name. -- Edge case: `.teambridge/.active` file is empty or whitespace — falls through to error. -- Error path: no params and no `.teambridge/.active` file — throws with a clear message. -- Edge case: `.teambridge/.active` with trailing newline — trimmed correctly. +- Happy path: `.coord/.active` file containing `billing-refactor` resolves to that session name. +- Edge case: `.coord/.active` file is empty or whitespace — falls through to error. +- Error path: no params and no `.coord/.active` file — throws with a clear message. +- Edge case: `.coord/.active` with trailing newline — trimmed correctly. -**Verification:** `pnpm --filter @teambridge/mcp build && pnpm --filter @teambridge/mcp test` passes. +**Verification:** `pnpm --filter @coord/mcp build && pnpm --filter @coord/mcp test` passes. ### U3. MCP server with resources registration @@ -139,19 +139,19 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - `packages/mcp/package.json` — add `@modelcontextprotocol/sdk` dependency, add `start` script - `packages/mcp/test/server.test.cjs` (new) — tests for resource registration and resolution -**Approach:** Create `McpServer` with name `teambridge` and version from `package.json`. Register each resource using `server.registerResource`. For `teambridge://workspace`, `teambridge://participants`, and `teambridge://vault/context`, call `resolveMcpResource` with the resolved workspace context. For `teambridge://inbox`, return `{ messages: [] }`. For `teambridge://conflicts`, return `{ conflicts: [] }`. Connect with `StdioServerTransport`. The server reads `repoRoot` from `process.env.TEAMBRIDGE_REPO_ROOT` or `process.cwd()`. +**Approach:** Create `McpServer` with name `coord` and version from `package.json`. Register each resource using `server.registerResource`. For `coord://workspace`, `coord://participants`, and `coord://vault/context`, call `resolveMcpResource` with the resolved workspace context. For `coord://inbox`, return `{ messages: [] }`. For `coord://conflicts`, return `{ conflicts: [] }`. Connect with `StdioServerTransport`. The server reads `repoRoot` from `process.env.COORD_REPO_ROOT` or `process.cwd()`. **Patterns to follow:** MCP SDK's `server.registerResource` pattern from the official docs. Existing `resolveMcpResource` function in `resources.ts`. **Test scenarios:** -- Happy path: reading `teambridge://workspace` returns workspace status with relay status when daemon is available. -- Happy path: reading `teambridge://participants` returns participants array. -- Happy path: reading `teambridge://vault/context` returns vault context. -- Happy path: reading `teambridge://inbox` returns `{ messages: [] }` (stub). -- Happy path: reading `teambridge://conflicts` returns `{ conflicts: [] }` (stub). +- Happy path: reading `coord://workspace` returns workspace status with relay status when daemon is available. +- Happy path: reading `coord://participants` returns participants array. +- Happy path: reading `coord://vault/context` returns vault context. +- Happy path: reading `coord://inbox` returns `{ messages: [] }` (stub). +- Happy path: reading `coord://conflicts` returns `{ conflicts: [] }` (stub). - Error path: reading a resource without workspace context returns an error. -**Verification:** `pnpm --filter @teambridge/mcp build` succeeds. Server starts and responds to `resources/list` and `resources/read` MCP protocol messages. +**Verification:** `pnpm --filter @coord/mcp build` succeeds. Server starts and responds to `resources/list` and `resources/read` MCP protocol messages. ### U4. MCP tools registration @@ -179,11 +179,11 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - Error path: `team_reply` returns `isError: true` with not-implemented message. - Error path: `team_publish` without workspace context returns an error. -**Verification:** `pnpm --filter @teambridge/mcp build && pnpm --filter @teambridge/mcp test` passes. All 6 tools appear in `tools/list` MCP protocol response. +**Verification:** `pnpm --filter @coord/mcp build && pnpm --filter @coord/mcp test` passes. All 6 tools appear in `tools/list` MCP protocol response. ### U5. CLI entry point and package wiring -**Goal:** Add `teambridge mcp` CLI command that spawns the MCP server, and wire the package build. +**Goal:** Add `coord mcp` CLI command that spawns the MCP server, and wire the package build. **Requirements:** R5 @@ -194,14 +194,14 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar - `packages/cli/src/index.ts` — register the `mcp` command - `packages/mcp/package.json` — ensure `build` produces `dist/server.js` -**Approach:** The CLI command is a thin wrapper. `teambridge mcp` runs `node (packages/mcp/dist/server.js)` with `stdio: 'inherit'` so stdin/stdout flow directly to the MCP client (Claude Code). The command accepts optional `--repo ` to set `TEAMBRIDGE_REPO_ROOT` env var. The MCP package's `tsconfig.json` already includes `src/server.ts` in the build. +**Approach:** The CLI command is a thin wrapper. `coord mcp` runs `node (packages/mcp/dist/server.js)` with `stdio: 'inherit'` so stdin/stdout flow directly to the MCP client (Claude Code). The command accepts optional `--repo ` to set `COORD_REPO_ROOT` env var. The MCP package's `tsconfig.json` already includes `src/server.ts` in the build. **Patterns to follow:** Existing CLI command patterns in `packages/cli/src/commands/`. The daemon's `main()` function pattern. **Test scenarios:** - Test expectation: none — CLI command is a thin process spawn, verified by manual integration with Claude Code. -**Verification:** `pnpm build` succeeds. `pnpm teambridge mcp` starts the MCP server and speaks the protocol over stdio. +**Verification:** `pnpm build` succeeds. `pnpm coord mcp` starts the MCP server and speaks the protocol over stdio. ### U6. Integration tests @@ -214,17 +214,17 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar **Files:** - `packages/mcp/test/integration.test.cjs` (new) — integration tests that start the MCP server, send JSON-RPC messages, and verify responses -**Approach:** Spawn the MCP server as a child process, send JSON-RPC `initialize` → `resources/list` → `resources/read` → `tools/list` → `tools/call` messages over stdin, and verify the responses on stdout. Mock the daemon by setting `TEAMBRIDGE_DAEMON_URL` to a test HTTP server that returns canned responses. +**Approach:** Spawn the MCP server as a child process, send JSON-RPC `initialize` → `resources/list` → `resources/read` → `tools/list` → `tools/call` messages over stdin, and verify the responses on stdout. Mock the daemon by setting `COORD_DAEMON_URL` to a test HTTP server that returns canned responses. **Test scenarios:** -- Happy path: `initialize` handshake returns server info with `teambridge` name. +- Happy path: `initialize` handshake returns server info with `coord` name. - Happy path: `resources/list` returns all 5 resource URIs. - Happy path: `tools/list` returns all 6 tool names. - Happy path: `tools/call` with `team_publish` and valid args returns the created event from the daemon. - Error path: `tools/call` with `team_ask` returns an error response with not-implemented message. - Edge case: `resources/read` without workspace context returns an error. -**Verification:** `pnpm --filter @teambridge/mcp test` passes including integration tests. +**Verification:** `pnpm --filter @coord/mcp test` passes including integration tests. --- @@ -232,18 +232,18 @@ The MCP resource/tool stubs in `packages/mcp/src/resources.ts` and `tools.ts` ar | What | Command | Applies to | |------|---------|------------| -| MCP package tests | `pnpm --filter @teambridge/mcp test` | U1, U2, U3, U4, U6 | +| MCP package tests | `pnpm --filter @coord/mcp test` | U1, U2, U3, U4, U6 | | Full build | `pnpm build` | All units | -| CLI build | `pnpm --filter @teambridge/cli build` | U5 | -| Core contract tests | `pnpm --filter @teambridge/core test` | U1 (no changes expected) | +| CLI build | `pnpm --filter @coord/cli build` | U5 | +| Core contract tests | `pnpm --filter @coord/core test` | U1 (no changes expected) | ## Definition of Done - All 6 implementation units are complete with their test scenarios passing. - `pnpm build` succeeds with no TypeScript errors. -- `pnpm --filter @teambridge/mcp test` passes. -- The MCP server starts via `pnpm teambridge mcp` and speaks the MCP protocol over stdio. +- `pnpm --filter @coord/mcp test` passes. +- The MCP server starts via `pnpm coord mcp` and speaks the MCP protocol over stdio. - All 5 resources are registered and return data (3 real, 2 stubs). - All 6 tools are registered and respond (4 real, 2 not-implemented stubs). -- Workspace resolution works with explicit params and `.teambridge/.active` fallback. +- Workspace resolution works with explicit params and `.coord/.active` fallback. - No dead-end or experimental code left in the diff. diff --git a/docs/plans/2026-07-10-001-feat-inbox-conflicts-cross-surface-interlink-plan.md b/docs/plans/2026-07-10-001-feat-inbox-conflicts-cross-surface-interlink-plan.md index 66c4e63..0c07334 100644 --- a/docs/plans/2026-07-10-001-feat-inbox-conflicts-cross-surface-interlink-plan.md +++ b/docs/plans/2026-07-10-001-feat-inbox-conflicts-cross-surface-interlink-plan.md @@ -29,7 +29,7 @@ This plan closes the remaining Phase 3 gaps that keep MCP, dashboard, and CLI fr ### Problem Frame -Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` as empty stubs, and `team_ask`/`team_reply` return `isError: true`. The dashboard has no inbox or conflict UI. The daemon has no inbox or conflict HTTP endpoints. As a result, the agent can publish and search vault content, but it cannot ask a teammate, see questions, or resolve conflicts — and the dashboard cannot show those workflows either. Once this work lands, an agent can ask a targeted question from the IDE, a teammate can reply from the dashboard, and the team can detect and resolve content conflicts without leaving the shared workspace. +Today the MCP server exposes `coord://inbox` and `coord://conflicts` as empty stubs, and `team_ask`/`team_reply` return `isError: true`. The dashboard has no inbox or conflict UI. The daemon has no inbox or conflict HTTP endpoints. As a result, the agent can publish and search vault content, but it cannot ask a teammate, see questions, or resolve conflicts — and the dashboard cannot show those workflows either. Once this work lands, an agent can ask a targeted question from the IDE, a teammate can reply from the dashboard, and the team can detect and resolve content conflicts without leaving the shared workspace. ### Requirements @@ -49,8 +49,8 @@ Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` a **MCP** -- R9. The MCP resource `teambridge://inbox` returns real messages from the daemon. -- R10. The MCP resource `teambridge://conflicts` returns real conflicts from the daemon. +- R9. The MCP resource `coord://inbox` returns real messages from the daemon. +- R10. The MCP resource `coord://conflicts` returns real conflicts from the daemon. - R11. The MCP tool `team_ask` calls the daemon ask endpoint instead of returning a stub. - R12. The MCP tool `team_reply` calls the daemon reply endpoint instead of returning a stub. @@ -58,7 +58,7 @@ Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` a - R13. The dashboard shows an inbox panel with messages and a reply affordance for pending questions addressed to the local user. - R14. The dashboard shows a conflicts panel with open conflicts and a resolve affordance. -- R15. The dashboard surfaces recent teammate deltas (publishes since the local user's last-seen seq) using the same context-pointer mechanism as `teambridge context`. +- R15. The dashboard surfaces recent teammate deltas (publishes since the local user's last-seen seq) using the same context-pointer mechanism as `coord context`. - R16. Dashboard actions call the daemon endpoints and refresh the relevant panels. **Verification** @@ -72,7 +72,7 @@ Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` a **In scope:** Core contract schemas, daemon SQLite tables and HTTP endpoints, vault materialization for conflicts, MCP daemon client additions, MCP resource/tool real implementations, dashboard API client additions, dashboard inbox/conflicts/deltas UI, dashboard integration with the team sidebar, and per-package tests. **Deferred to Follow-Up Work:** -- CLI `teambridge ask|inbox|reply` commands (Kushagra's Phase 3 Step 3 scope). The daemon endpoints are shaped so the CLI can use them without changes. +- CLI `coord ask|inbox|reply` commands (Kushagra's Phase 3 Step 3 scope). The daemon endpoints are shaped so the CLI can use them without changes. - Supabase relay mirroring of `inbox_messages` and `conflicts` to remote `tc_` tables (Phase 2/3 follow-up once local behavior is solid). - Advanced conflict detection beyond same-targetFile collisions (e.g., semantic merge conflicts, branch-level conflicts). - Cross-surface end-to-end test that exercises MCP → CLI → dashboard in one flow (out of scope per the planning scope choice; kept as per-package tests). @@ -93,9 +93,9 @@ Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` a - **KTD4 — Conflicts materialize into `conflicts.md` alongside the Phase 1 flat vault files.** `packages/vault/src/index.ts` gains a `materializeConflictEvent` function that appends a line to `conflicts.md` (the same pattern used for `decisions.md`, `observations.md`, etc.). This keeps the vault the single readable artifact and lets `vault context` include conflict summaries automatically. -- **KTD5 — MCP resources and tools share daemon-client functions with the dashboard.** `packages/mcp/src/daemon-client.ts` adds `getInbox`, `askInbox`, `replyInbox`, `getConflicts`, and `resolveConflict`. The dashboard API client (`apps/dashboard/src/api/teambridgeClient.ts`) calls the same HTTP endpoints. This guarantees that all surfaces read the same state. +- **KTD5 — MCP resources and tools share daemon-client functions with the dashboard.** `packages/mcp/src/daemon-client.ts` adds `getInbox`, `askInbox`, `replyInbox`, `getConflicts`, and `resolveConflict`. The dashboard API client (`apps/dashboard/src/api/coordClient.ts`) calls the same HTTP endpoints. This guarantees that all surfaces read the same state. -- **KTD6 — Teammate deltas use the existing context-pointer file via a new daemon endpoint.** The dashboard cannot read the local filesystem directly, so the daemon exposes `GET /workspaces/:id/context-pointer` and `POST /workspaces/:id/context-pointer`. These read/write the same `.context.{displayName}.json` files that `teambridge context` uses, keeping the dashboard and CLI in sync on "what's new since I last looked." +- **KTD6 — Teammate deltas use the existing context-pointer file via a new daemon endpoint.** The dashboard cannot read the local filesystem directly, so the daemon exposes `GET /workspaces/:id/context-pointer` and `POST /workspaces/:id/context-pointer`. These read/write the same `.context.{displayName}.json` files that `coord context` uses, keeping the dashboard and CLI in sync on "what's new since I last looked." - **KTD7 — Dashboard reuses the relay-panel tab pattern for inbox and conflicts.** The team sidebar gains fourth and fifth tabs: "Inbox" and "Conflicts". Each panel follows the same polling, error-state, and motion-transition patterns as `RelaySyncHealth` and `EventFeed`. @@ -104,7 +104,7 @@ Today the MCP server exposes `teambridge://inbox` and `teambridge://conflicts` a - The local user profile and participant rows are stable enough to resolve display names to participant IDs for inbox addressing. - The existing `WorkspaceEventType` enum (`team_ask`, `team_reply`, `conflict_detected`, `conflict_resolved`) already reflects the intended event shapes. - The daemon's `runSql`/`querySql` sqlite3 shell-out helpers are sufficient for the new tables and queries. -- The MCP server is already wired to start via `teambridge mcp` and resolve the active workspace from `.teambridge/.active`. +- The MCP server is already wired to start via `coord mcp` and resolve the active workspace from `.coord/.active`. --- @@ -136,7 +136,7 @@ flowchart TB end subgraph Dashboard["apps/dashboard/src"] - L["teambridgeClient.ts: inbox + conflict functions"] + L["coordClient.ts: inbox + conflict functions"] M["InboxPanel.tsx + ConflictsPanel.tsx + TeammateDeltas.tsx"] N["team-sidebar.tsx: Inbox + Conflicts tabs"] O["DashboardPage.tsx: polling + error state"] @@ -190,7 +190,7 @@ flowchart TB - Error path: `TeamReplyEventSchema` rejects a `team_reply` event with a missing `replyToMessageId` payload field. - Type compatibility: a plain `InboxMessage` object is assignable to the inferred schema type. -**Verification:** `pnpm --filter @teambridge/core build && pnpm --filter @teambridge/core test` passes. +**Verification:** `pnpm --filter @coord/core build && pnpm --filter @coord/core test` passes. --- @@ -270,7 +270,7 @@ flowchart TB - Error path: replying to a message not addressed to the local user returns `FORBIDDEN`. - Integration: after ask + reply, `GET /inbox` returns both messages in correct order. -**Verification:** `pnpm build` and `pnpm --filter @teambridge/daemon test` pass. The existing `mcp-flow` integration test still passes (stubs are unchanged until U6). +**Verification:** `pnpm build` and `pnpm --filter @coord/daemon test` pass. The existing `mcp-flow` integration test still passes (stubs are unchanged until U6). --- @@ -310,7 +310,7 @@ flowchart TB ### U5. Context-pointer daemon endpoints -**Goal:** Let the dashboard read and write the same last-seen cursor that `teambridge context` uses, so teammate deltas are consistent across CLI and dashboard. +**Goal:** Let the dashboard read and write the same last-seen cursor that `coord context` uses, so teammate deltas are consistent across CLI and dashboard. **Requirements:** R15 @@ -321,7 +321,7 @@ flowchart TB - `packages/core/src/contracts/api.ts` — add `ContextPointerResponse` and `SaveContextPointerRequest` types if not already present. - `packages/daemon/test/inbox-conflict.test.cjs` — add pointer tests. -**Approach:** Reuse the existing `readContextPointer` / `writeContextPointer` logic from `packages/cli/src/lib/context-pointer.ts`. The daemon endpoints read/write the same JSON file under `.teambridge/workspaces/{sessionName}/.context.{displayName}.json`. The `GET` endpoint returns the pointer; the `POST` endpoint accepts `{ lastSeenSeq: number }` and updates the file. The display name used in the filename is derived from the authenticated local user profile for the workspace, so a participant cannot update another user's pointer. +**Approach:** Reuse the existing `readContextPointer` / `writeContextPointer` logic from `packages/cli/src/lib/context-pointer.ts`. The daemon endpoints read/write the same JSON file under `.coord/workspaces/{sessionName}/.context.{displayName}.json`. The `GET` endpoint returns the pointer; the `POST` endpoint accepts `{ lastSeenSeq: number }` and updates the file. The display name used in the filename is derived from the authenticated local user profile for the workspace, so a participant cannot update another user's pointer. **Patterns to follow:** The existing pointer file shape and the daemon's `repoRoot` resolution pattern. @@ -357,7 +357,7 @@ flowchart TB - Happy path: `resolveConflict(...)` calls `POST /workspaces/:id/conflicts/:id/resolve`. - Error path: functions propagate daemon errors as `ApiFail` results. -**Verification:** `pnpm --filter @teambridge/mcp build && pnpm --filter @teambridge/mcp test` passes. +**Verification:** `pnpm --filter @coord/mcp build && pnpm --filter @coord/mcp test` passes. --- @@ -370,7 +370,7 @@ flowchart TB **Dependencies:** U6 **Files:** -- `packages/mcp/src/resources.ts` — update `resolveMcpResource` for `teambridge://inbox` and `teambridge://conflicts` to call the daemon. +- `packages/mcp/src/resources.ts` — update `resolveMcpResource` for `coord://inbox` and `coord://conflicts` to call the daemon. - `packages/mcp/src/tools.ts` — replace stubs with real `resolveMcpTool` dispatch for `team_ask` and `team_reply`. - `packages/mcp/src/server.ts` — update tool descriptions and resource descriptions to remove "not yet implemented" notes. - `packages/mcp/test/resources.test.cjs` — add tests for real inbox/conflict resource resolution. @@ -378,8 +378,8 @@ flowchart TB - `tests/integration/mcp-flow.test.mjs` — update stub assertions to verify real ask/reply behavior. **Approach:** -- For `teambridge://inbox`, call `getInbox` and return `{ messages: result.data.messages }`. -- For `teambridge://conflicts`, call `getConflicts` and return `{ conflicts: result.data.conflicts }`. +- For `coord://inbox`, call `getInbox` and return `{ messages: result.data.messages }`. +- For `coord://conflicts`, call `getConflicts` and return `{ conflicts: result.data.conflicts }`. - For `team_ask`, call `askInbox` and return the created message. - For `team_reply`, call `replyInbox` and return the updated message. - If the daemon returns an error, propagate it as a thrown error so the MCP server returns an error response. @@ -387,13 +387,13 @@ flowchart TB **Patterns to follow:** Existing `resolveMcpResource` switch structure and `server.registerTool` pattern. **Test scenarios:** -- Happy path: reading `teambridge://inbox` returns messages from a mocked daemon reader. -- Happy path: reading `teambridge://conflicts` returns conflicts from a mocked daemon reader. +- Happy path: reading `coord://inbox` returns messages from a mocked daemon reader. +- Happy path: reading `coord://conflicts` returns conflicts from a mocked daemon reader. - Happy path: `team_ask` tool dispatches to `askInbox` and returns the created message. - Happy path: `team_reply` tool dispatches to `replyInbox` and returns the updated message. - Error path: daemon failure for any of the above returns an error response. -**Verification:** `pnpm --filter @teambridge/mcp test` passes, including updated integration tests. +**Verification:** `pnpm --filter @coord/mcp test` passes, including updated integration tests. --- @@ -406,12 +406,12 @@ flowchart TB **Dependencies:** U3, U4, U5 **Files:** -- `apps/dashboard/src/api/teambridgeClient.ts` — add `getInbox`, `askInbox`, `replyInbox`, `getConflicts`, `resolveConflict`, `getContextPointer`, `setContextPointer`. -- `apps/dashboard/src/api/teambridgeClient.test.ts` — add tests for the new functions. +- `apps/dashboard/src/api/coordClient.ts` — add `getInbox`, `askInbox`, `replyInbox`, `getConflicts`, `resolveConflict`, `getContextPointer`, `setContextPointer`. +- `apps/dashboard/src/api/coordClient.test.ts` — add tests for the new functions. **Approach:** Mirror the existing `getJson`/`fetch` POST patterns. `askInbox` and `replyInbox` POST JSON bodies. `resolveConflict` POSTs to the conflict resolve path. `setContextPointer` POSTs `{ lastSeenSeq }`. -**Patterns to follow:** Existing `annotateVaultItem` and `getWorkspaceEvents` in `apps/dashboard/src/api/teambridgeClient.ts`. +**Patterns to follow:** Existing `annotateVaultItem` and `getWorkspaceEvents` in `apps/dashboard/src/api/coordClient.ts`. **Test scenarios:** - Happy path: `getInbox` calls `GET /workspaces/:id/inbox` and returns messages. @@ -419,7 +419,7 @@ flowchart TB - Happy path: `resolveConflict` calls `POST /workspaces/:id/conflicts/:id/resolve` with `{ resolutionText }`. - Error path: functions throw with the daemon error message on non-OK responses. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. +**Verification:** `pnpm --filter @coord/dashboard test` passes. --- @@ -436,7 +436,7 @@ flowchart TB - `apps/dashboard/src/components/InboxPanel.test.tsx` (new) — component tests. - `apps/dashboard/src/test/factories.ts` — add `makeInboxMessage` factory. -**Approach:** The component takes `messages: InboxMessage[]`, `localUser: LocalUserProfile | null`, `config: TeambridgeClientConfig`, `workspaceId: string`, and optional `error`. It renders messages grouped by status, with a reply input on pending messages addressed to the local user. On reply, it calls `replyInbox` and notifies the parent via `onReply` so `DashboardPage` can refresh the list. +**Approach:** The component takes `messages: InboxMessage[]`, `localUser: LocalUserProfile | null`, `config: CoordClientConfig`, `workspaceId: string`, and optional `error`. It renders messages grouped by status, with a reply input on pending messages addressed to the local user. On reply, it calls `replyInbox` and notifies the parent via `onReply` so `DashboardPage` can refresh the list. **Patterns to follow:** `EventFeed.tsx` for row layout, motion transitions, and error handling. `TrackParticipantsPanel.tsx` for participant name display. Use the existing shadcn/ui form primitives for reply/resolve inputs and add clear focus rings and `aria-label`s for accessibility. @@ -447,7 +447,7 @@ flowchart TB - Edge case: empty inbox renders a "No messages" placeholder. - Error state: renders the error message when `error` is set. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. +**Verification:** `pnpm --filter @coord/dashboard test` passes. --- @@ -474,7 +474,7 @@ flowchart TB - Edge case: no open conflicts renders a "No conflicts" placeholder. - Error state: renders the error message when `error` is set. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. +**Verification:** `pnpm --filter @coord/dashboard test` passes. --- @@ -503,7 +503,7 @@ flowchart TB - Edge case: no new events renders a "No new updates" placeholder. - Edge case: events with non-publish types are excluded from deltas. -**Verification:** `pnpm --filter @teambridge/dashboard test` passes. +**Verification:** `pnpm --filter @coord/dashboard test` passes. --- @@ -531,7 +531,7 @@ flowchart TB - Error state: the panels show error messages when their respective endpoints fail. - Integration: switching tracks resets the inbox/conflicts data and polls the new track. -**Verification:** `pnpm build` succeeds. `pnpm --filter @teambridge/dashboard test` and `pnpm test:integration` pass. +**Verification:** `pnpm build` succeeds. `pnpm --filter @coord/dashboard test` and `pnpm test:integration` pass. --- @@ -557,7 +557,7 @@ flowchart TB - MCP: `team_ask` and `team_reply` tools return real daemon responses in the integration test. - Dashboard: `InboxPanel` and `ConflictsPanel` render data and handle actions. -**Verification:** `pnpm test`, `pnpm --filter @teambridge/mcp test`, `pnpm --filter @teambridge/dashboard test`, and `pnpm test:integration` all pass. +**Verification:** `pnpm test`, `pnpm --filter @coord/mcp test`, `pnpm --filter @coord/dashboard test`, and `pnpm test:integration` all pass. --- @@ -565,10 +565,10 @@ flowchart TB | What | Command | Applies to | |------|---------|------------| -| Core contract tests | `pnpm --filter @teambridge/core test` | U1 | -| Daemon tests | `pnpm --filter @teambridge/daemon test` | U2-U5, U13 | -| MCP tests | `pnpm --filter @teambridge/mcp test` | U6-U7, U13 | -| Dashboard tests | `pnpm --filter @teambridge/dashboard test` | U8-U12 | +| Core contract tests | `pnpm --filter @coord/core test` | U1 | +| Daemon tests | `pnpm --filter @coord/daemon test` | U2-U5, U13 | +| MCP tests | `pnpm --filter @coord/mcp test` | U6-U7, U13 | +| Dashboard tests | `pnpm --filter @coord/dashboard test` | U8-U12 | | Full build | `pnpm build` | All units | | Integration tests | `pnpm test:integration` | U7, U12-U13 | @@ -589,17 +589,17 @@ flowchart TB - **MCP / dashboard / CLI parity:** the daemon endpoints are the single source of truth for inbox and conflict state. MCP tools, dashboard UI, and (eventually) CLI commands all read and write through the same routes, so a question asked via MCP appears in the dashboard and a reply sent from the dashboard is visible to the MCP tool. - **Vault materialization:** `conflict_detected` events append to `conflicts.md`, so `vault context` and the vault search index include conflict summaries automatically. This means conflict state is reachable both through the structured API and through the flat vault files. - **Event log shape:** the plan adds `team_ask`, `team_reply`, `conflict_detected`, and `conflict_resolved` events to `events.jsonl`. These events replay by `seq` and participate in the existing vault rebuild-from-events contract. -- **Context pointer sharing:** the dashboard and CLI share the same `.context.{displayName}.json` read cursor. A "mark seen" action in the dashboard updates the same file that `teambridge context` reads, so the two surfaces stay aligned on what is "new." +- **Context pointer sharing:** the dashboard and CLI share the same `.context.{displayName}.json` read cursor. A "mark seen" action in the dashboard updates the same file that `coord context` reads, so the two surfaces stay aligned on what is "new." - **Permission boundary:** the inbox reply endpoint checks that the local user is the recipient of the message. This is the first per-message authorization check in the daemon and should be reviewed carefully (see Risks & Dependencies). ## Risks & Dependencies -- **Kushagra CLI dependency:** the plan deliberately does not implement `teambridge ask|inbox|reply` CLI commands. Until Kushagra adds them, the CLI cannot drive the new daemon endpoints, but the endpoints are shaped to be compatible. +- **Kushagra CLI dependency:** the plan deliberately does not implement `coord ask|inbox|reply` CLI commands. Until Kushagra adds them, the CLI cannot drive the new daemon endpoints, but the endpoints are shaped to be compatible. - **Inbox authorization:** `POST /inbox/:id/reply` must verify that the caller is the message recipient (`to_user_id`). A bug here would let any participant answer questions meant for someone else. Mitigation: the handler looks up the local user profile and compares it to `to_user_id`; mismatch returns `FORBIDDEN`. Add a daemon test that asserts this check. - **Display-name collision risk:** if two participants share a display name, inbox `to` resolution is ambiguous. Mitigation: the existing `participants` table has a unique `(workspace_id, display_name)` constraint that prevents this at the data layer; the ask handler still validates that exactly one match exists and returns `INVALID_REQUEST` otherwise. - **Conflict detection false positives:** the simple same-targetFile rule may flag normal sequential publishes as conflicts. Mitigation: the rule is scoped to the same sync window and can be tuned or replaced later without changing the conflict resolution contract. - **Context-pointer file race:** CLI and dashboard both write the same pointer file. Mitigation: the daemon writes the pointer file atomically using a temp-file + `rename` pattern (or the existing atomic-write helper), so a crash mid-write cannot leave torn JSON. The worst case is a slightly stale `lastSeenSeq` — acceptable for a read cursor. -- **Daemon client timeouts:** the MCP and dashboard HTTP clients need explicit request/response timeouts so a slow daemon cannot hang the agent or the UI. Mitigation: add a timeout to `fetch` calls in `packages/mcp/src/daemon-client.ts` and `apps/dashboard/src/api/teambridgeClient.ts`. +- **Daemon client timeouts:** the MCP and dashboard HTTP clients need explicit request/response timeouts so a slow daemon cannot hang the agent or the UI. Mitigation: add a timeout to `fetch` calls in `packages/mcp/src/daemon-client.ts` and `apps/dashboard/src/api/coordClient.ts`. - **Conflict resolution race:** two concurrent resolve requests could both emit `conflict_resolved` events. Mitigation: the update uses `WHERE status = 'open'` (or an optimistic lock) and the handler verifies the row was updated before emitting the event. - **Polling backpressure:** inbox and conflicts polls at `TRACK_REFRESH_MS` can pile up if the daemon is slow. Mitigation: reuse the existing `AbortController` pattern in `DashboardPage` to cancel in-flight requests when the track changes or a new poll starts. - **Schema migration:** the new `inbox_messages` and `conflicts` tables are created by `initializeStateDb`, so existing repos pick them up automatically on the next daemon start. No manual migration is needed. diff --git a/package.json b/package.json index 7f528b1..b574be3 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "teambridge", + "name": "coord", "version": "0.0.0", "private": true, - "description": "Condominium — shared workspace for teams building with AI agents.", + "description": "Coord — shared workspace for teams building with AI agents.", "scripts": { "build": "pnpm -r build", - "dashboard": "pnpm --filter @teambridge/dashboard dev --host 127.0.0.1", - "dashboard:preview": "pnpm --filter @teambridge/dashboard build && pnpm --filter @teambridge/dashboard exec vite preview --host 127.0.0.1 --port 5173", - "daemon": "pnpm --filter @teambridge/daemon start", - "cli": "pnpm --filter @teambridge/cli start --", - "teambridge": "node packages/cli/dist/index.js", + "dashboard": "pnpm --filter @coord/dashboard dev --host 127.0.0.1", + "dashboard:preview": "pnpm --filter @coord/dashboard build && pnpm --filter @coord/dashboard exec vite preview --host 127.0.0.1 --port 5173", + "daemon": "pnpm --filter @coord/daemon start", + "cli": "pnpm --filter @coord/cli start --", + "coord": "node packages/cli/dist/index.js", "seed": "node scripts/seed-demo.mjs", "test": "pnpm -r --if-present test", "test:integration": "pnpm build && node --test --test-concurrency=1 tests/integration/*.test.mjs" diff --git a/packages/cli/package.json b/packages/cli/package.json index d373741..6db127a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,9 +1,9 @@ { - "name": "@teambridge/cli", + "name": "@coord/cli", "version": "0.0.0", "private": true, "bin": { - "teambridge": "dist/index.js" + "coord": "dist/index.js" }, "main": "dist/index.js", "types": "dist/index.d.ts", @@ -13,7 +13,7 @@ "test": "pnpm build && node --test test/*.test.cjs" }, "dependencies": { - "@teambridge/core": "workspace:*", - "@teambridge/mcp": "workspace:*" + "@coord/core": "workspace:*", + "@coord/mcp": "workspace:*" } } diff --git a/packages/cli/src/commands/context.ts b/packages/cli/src/commands/context.ts index 5992c25..ddc1884 100644 --- a/packages/cli/src/commands/context.ts +++ b/packages/cli/src/commands/context.ts @@ -1,4 +1,4 @@ -import type { Participant, PublishEventPayload, WorkspaceEvent } from '@teambridge/core'; +import type { Participant, PublishEventPayload, WorkspaceEvent } from '@coord/core'; import type { ClientOptions } from '../daemon-client'; import { getUserProfile, getVaultContext, getWorkspaceStatus, listEvents } from '../daemon-client'; import { currentSessionNameFromBranch, resolveCurrentTrack } from '../lib/current-track'; @@ -127,7 +127,7 @@ export async function runContext(argv: string[], options: ClientOptions): Promis )}\n` ); } else { - const out: string[] = [`# Teambridge context — ${sessionName}`]; + const out: string[] = [`# Coord context — ${sessionName}`]; if (!deltasOnly) { out.push('', '## Shared vault context'); diff --git a/packages/cli/src/commands/daemon.ts b/packages/cli/src/commands/daemon.ts index fc390ca..f997939 100644 --- a/packages/cli/src/commands/daemon.ts +++ b/packages/cli/src/commands/daemon.ts @@ -13,7 +13,7 @@ type DaemonState = { }; function stateDir(repoRoot: string): string { - return join(repoRoot, '.teambridge', 'daemon'); + return join(repoRoot, '.coord', 'daemon'); } function statePath(repoRoot: string): string { @@ -55,13 +55,13 @@ function parsePort(argv: string[], fallback: string | undefined): number { async function runDaemonStart(argv: string[], options: ClientOptions): Promise { const existing = readState(options.repoRoot); if (existing && isProcessAlive(existing.pid)) { - console.log(`Teambridge daemon already running on ${existing.baseUrl}`); + console.log(`Coord daemon already running on ${existing.baseUrl}`); console.log(`PID: ${existing.pid}`); console.log(`Log: ${existing.logPath}`); return; } - const port = parsePort(argv, process.env.TEAMBRIDGE_DAEMON_PORT); + const port = parsePort(argv, process.env.COORD_DAEMON_PORT); const baseUrl = `http://127.0.0.1:${port}`; const dir = stateDir(options.repoRoot); mkdirSync(dir, { recursive: true }); @@ -76,8 +76,8 @@ async function runDaemonStart(argv: string[], options: ClientOptions): Promise { const state = readState(options.repoRoot); if (!state) { - console.log('Teambridge daemon is not managed for this repo yet.'); + console.log('Coord daemon is not managed for this repo yet.'); process.exitCode = 1; return; } if (!isProcessAlive(state.pid)) { - console.log(`Teambridge daemon is stopped. Last PID: ${state.pid}`); + console.log(`Coord daemon is stopped. Last PID: ${state.pid}`); console.log(`Log: ${state.logPath}`); process.exitCode = 1; return; } - console.log(`Teambridge daemon is running on ${state.baseUrl}`); + console.log(`Coord daemon is running on ${state.baseUrl}`); console.log(`PID: ${state.pid}`); console.log(`Log: ${state.logPath}`); } @@ -123,15 +123,15 @@ async function runDaemonStatus(options: ClientOptions): Promise { async function runDaemonStop(options: ClientOptions): Promise { const state = readState(options.repoRoot); if (!state) { - console.log('Teambridge daemon is not managed for this repo yet.'); + console.log('Coord daemon is not managed for this repo yet.'); return; } if (isProcessAlive(state.pid)) { process.kill(state.pid, 'SIGTERM'); - console.log(`Stopped Teambridge daemon on ${state.baseUrl}`); + console.log(`Stopped Coord daemon on ${state.baseUrl}`); } else { - console.log(`Teambridge daemon was already stopped. Last PID: ${state.pid}`); + console.log(`Coord daemon was already stopped. Last PID: ${state.pid}`); } rmSync(statePath(options.repoRoot), { force: true }); @@ -155,5 +155,5 @@ export async function runDaemon(argv: string[], options: ClientOptions): Promise return; } - throw new Error('Usage: teambridge daemon start|status|stop [--port PORT]'); + throw new Error('Usage: coord daemon start|status|stop [--port PORT]'); } diff --git a/packages/cli/src/commands/enter.ts b/packages/cli/src/commands/enter.ts index 8f4fa53..e1d82ed 100644 --- a/packages/cli/src/commands/enter.ts +++ b/packages/cli/src/commands/enter.ts @@ -5,14 +5,14 @@ import { readWorktreePointer } from '../lib/pointers'; /** * Prints ONLY the resolved worktree path to stdout — nothing else — so it is - * safe inside `cd "$(teambridge enter NAME)"` shell substitution. All + * safe inside `cd "$(coord enter NAME)"` shell substitution. All * diagnostics and errors go to stderr via the thrown Error (index.ts logs * caught errors to console.error). */ export async function runEnter(argv: string[], options: ClientOptions): Promise { const sessionName = argv.find((arg) => !arg.startsWith('-')); if (!sessionName?.trim()) { - throw new Error('Usage: teambridge enter '); + throw new Error('Usage: coord enter '); } const profile = await getUserProfile(options); @@ -20,14 +20,14 @@ export async function runEnter(argv: string[], options: ClientOptions): Promise< throw new Error(profile.error.message); } if (!profile.data.profile) { - throw new Error('Run `teambridge init` first to set your name and avatar.'); + throw new Error('Run `coord init` first to set your name and avatar.'); } const displayName = profile.data.profile.displayName; const pointer = readWorktreePointer(options.repoRoot, sessionName.trim(), displayName); if (!pointer) { throw new Error( - `No worktree found for "${sessionName}" as ${displayName}. Run \`teambridge start ${sessionName}\` or \`teambridge join ${sessionName}\` first.` + `No worktree found for "${sessionName}" as ${displayName}. Run \`coord start ${sessionName}\` or \`coord join ${sessionName}\` first.` ); } diff --git a/packages/cli/src/commands/hook.ts b/packages/cli/src/commands/hook.ts index a40ff71..66bd0a8 100644 --- a/packages/cli/src/commands/hook.ts +++ b/packages/cli/src/commands/hook.ts @@ -5,14 +5,14 @@ import { getConfig } from '../daemon-client'; import { parseFlag } from '../prompt'; /** - * The command a Teambridge worktree's Claude Code SessionStart hook runs. Its + * The command a Coord worktree's Claude Code SessionStart hook runs. Its * stdout is injected as additional context, so an agent opening the worktree * automatically sees the shared vault plus what changed since last time — * "hooks only make Claude Code feel automatic" (agent.md rule 7). No * per-session flags: once installed, it just runs. */ -const HOOK_COMMAND = 'teambridge context'; -const HOOK_MARKER = 'teambridge context'; +const HOOK_COMMAND = 'coord context'; +const HOOK_MARKER = 'coord context'; type CommandHook = { type: 'command'; command: string }; type SessionStartEntry = { matcher?: string; hooks: CommandHook[] }; @@ -30,7 +30,7 @@ function readSettings(path: string): ClaudeSettings { try { return JSON.parse(readFileSync(path, 'utf8')) as ClaudeSettings; } catch { - throw new Error(`Could not parse ${path} as JSON — fix or remove it before running \`teambridge hook install\`.`); + throw new Error(`Could not parse ${path} as JSON — fix or remove it before running \`coord hook install\`.`); } } @@ -39,12 +39,12 @@ function writeSettings(path: string, settings: ClaudeSettings): void { writeFileSync(path, `${JSON.stringify(settings, null, 2)}\n`); } -function entryTargetsTeambridge(entry: SessionStartEntry): boolean { +function entryTargetsCoord(entry: SessionStartEntry): boolean { return entry.hooks.some((hook) => hook.type === 'command' && hook.command.includes(HOOK_MARKER)); } function isInstalled(settings: ClaudeSettings): boolean { - return (settings.hooks?.SessionStart ?? []).some(entryTargetsTeambridge); + return (settings.hooks?.SessionStart ?? []).some(entryTargetsCoord); } async function runHookInstall(argv: string[], options: ClientOptions, cwd: string): Promise { @@ -61,23 +61,23 @@ async function runHookInstall(argv: string[], options: ClientOptions, cwd: strin if (isInstalled(settings)) { // Refresh the command in case it changed, keep it idempotent. for (const entry of sessionStart) { - if (entryTargetsTeambridge(entry)) { + if (entryTargetsCoord(entry)) { entry.hooks = entry.hooks.map((hook) => hook.command.includes(HOOK_MARKER) ? { type: 'command', command } : hook ); } } writeSettings(path, settings); - console.log(`Teambridge SessionStart hook already present — refreshed in ${path}`); + console.log(`Coord SessionStart hook already present — refreshed in ${path}`); } else { sessionStart.push({ hooks: [{ type: 'command', command }] }); writeSettings(path, settings); - console.log(`Installed Teambridge SessionStart hook in ${path}`); + console.log(`Installed Coord SessionStart hook in ${path}`); console.log(`Command: ${command}`); } if (!autoInject) { - console.log('Note: config.autoInject is false — the hook is installed but you disabled auto-injection in .teambridge/config.json.'); + console.log('Note: config.autoInject is false — the hook is installed but you disabled auto-injection in .coord/config.json.'); } console.log('New Claude Code sessions in this worktree will now receive shared context automatically.'); } @@ -91,7 +91,7 @@ function runHookUninstall(cwd: string): void { const settings = readSettings(path); const sessionStart = settings.hooks?.SessionStart; if (!sessionStart || !isInstalled(settings)) { - console.log('Teambridge SessionStart hook is not installed here.'); + console.log('Coord SessionStart hook is not installed here.'); return; } @@ -108,7 +108,7 @@ function runHookUninstall(cwd: string): void { } } writeSettings(path, settings); - console.log(`Removed Teambridge SessionStart hook from ${path}`); + console.log(`Removed Coord SessionStart hook from ${path}`); } async function runHookStatus(options: ClientOptions, cwd: string): Promise { @@ -124,7 +124,7 @@ async function runHookStatus(options: ClientOptions, cwd: string): Promise console.log(`config.autoInject: ${autoInject}`); } if (!installed) { - console.log('Run `teambridge hook install` from inside your worktree to enable auto-injection.'); + console.log('Run `coord hook install` from inside your worktree to enable auto-injection.'); } } @@ -144,5 +144,5 @@ export async function runHook(argv: string[], options: ClientOptions): Promise '); + throw new Error('Usage: coord ask '); } const track = await resolveCurrentTrack(options); @@ -61,7 +61,7 @@ export async function runReply(argv: string[], options: ClientOptions): Promise< const messageId = args[0]; const text = args.slice(1).join(' '); if (!messageId || !text.trim()) { - throw new Error('Usage: teambridge reply '); + throw new Error('Usage: coord reply '); } const track = await resolveCurrentTrack(options); @@ -80,7 +80,7 @@ export async function runConflicts(argv: string[], options: ClientOptions): Prom const conflictId = args[0]; const resolutionText = args.slice(1).join(' '); if (!conflictId || !resolutionText.trim()) { - throw new Error('Usage: teambridge conflicts resolve '); + throw new Error('Usage: coord conflicts resolve '); } const actor = await localParticipant(options, track.id); const result = await resolveConflict(options, track.id, conflictId, { resolutionText, actorId: actor?.id }); diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index eba9694..2d30437 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,4 +1,4 @@ -import type { RelayMode } from '@teambridge/core'; +import type { RelayMode } from '@coord/core'; import type { ClientOptions } from '../daemon-client'; import { getUserProfile, initConfig, saveUserProfile } from '../daemon-client'; import { ask, parseFlag } from '../prompt'; @@ -46,7 +46,7 @@ export async function runInit(argv: string[], options: ClientOptions): Promise { await startServer(); diff --git a/packages/cli/src/commands/project.ts b/packages/cli/src/commands/project.ts index 9ac57f7..afa0e29 100644 --- a/packages/cli/src/commands/project.ts +++ b/packages/cli/src/commands/project.ts @@ -8,7 +8,7 @@ export async function runProjectCreate(argv: string[], options: ClientOptions): throw new Error(profile.error.message); } if (!profile.data.profile) { - throw new Error('Run `teambridge init` first to set your name and avatar.'); + throw new Error('Run `coord init` first to set your name and avatar.'); } let name = parseFlag(argv, '--name') ?? argv[0]; @@ -50,7 +50,7 @@ export async function runProjectList(_argv: string[], options: ClientOptions): P } if (projects.data.projects.length === 0) { - console.log('No projects yet. Run `teambridge project create`.'); + console.log('No projects yet. Run `coord project create`.'); return; } diff --git a/packages/cli/src/commands/publish.ts b/packages/cli/src/commands/publish.ts index 0d448ba..9385614 100644 --- a/packages/cli/src/commands/publish.ts +++ b/packages/cli/src/commands/publish.ts @@ -8,7 +8,7 @@ export async function runPublish(argv: string[], options: ClientOptions): Promis const text = positional[1]; if (!targetFile || !text) { - throw new Error('Usage: teambridge publish '); + throw new Error('Usage: coord publish '); } if (!text.trim()) { throw new Error('publish text must not be empty.'); diff --git a/packages/cli/src/commands/relay.ts b/packages/cli/src/commands/relay.ts index ad97bc2..c23389e 100644 --- a/packages/cli/src/commands/relay.ts +++ b/packages/cli/src/commands/relay.ts @@ -14,11 +14,11 @@ function valueAfter(argv: string[], flag: string): string | undefined { } export async function runLogin(argv: string[], options: ClientOptions): Promise { - const email = valueAfter(argv, '--email') ?? process.env.TEAMBRIDGE_EMAIL; - const password = valueAfter(argv, '--password') ?? process.env.TEAMBRIDGE_PASSWORD; + const email = valueAfter(argv, '--email') ?? process.env.COORD_EMAIL; + const password = valueAfter(argv, '--password') ?? process.env.COORD_PASSWORD; if (!email || !password) { - throw new Error('Usage: teambridge login --email EMAIL --password PASSWORD'); + throw new Error('Usage: coord login --email EMAIL --password PASSWORD'); } const result = await loginRelay(options, { email, password }); @@ -26,7 +26,7 @@ export async function runLogin(argv: string[], options: ClientOptions): Promise< throw new Error(result.error.message); } - console.log(`Logged in to Teambridge relay as ${result.data.email ?? result.data.userId}`); + console.log(`Logged in to Coord relay as ${result.data.email ?? result.data.userId}`); console.log(`Relay: ${result.data.relayUrl}`); } diff --git a/packages/cli/src/commands/status.ts b/packages/cli/src/commands/status.ts index 8460752..75b8296 100644 --- a/packages/cli/src/commands/status.ts +++ b/packages/cli/src/commands/status.ts @@ -24,7 +24,7 @@ export async function runStatus(_argv: string[], options: ClientOptions): Promis console.log(`Default project: ${profile.data.profile.defaultProjectId}`); } } else { - console.log('You: (not initialized — run teambridge init)'); + console.log('You: (not initialized — run coord init)'); } console.log(`Projects: ${projects.data.projects.length}`); diff --git a/packages/cli/src/commands/track.ts b/packages/cli/src/commands/track.ts index 3886b3f..e5c40ac 100644 --- a/packages/cli/src/commands/track.ts +++ b/packages/cli/src/commands/track.ts @@ -1,4 +1,4 @@ -import type { ApiOk, LocalUserProfile, StartWorkspaceResponse } from '@teambridge/core'; +import type { ApiOk, LocalUserProfile, StartWorkspaceResponse } from '@coord/core'; import type { ClientOptions } from '../daemon-client'; import { getUserProfile, joinWorkspace, listProjects, listRelaySessions, listTracks, startTrack } from '../daemon-client'; import { ask, parseFlag } from '../prompt'; @@ -34,7 +34,7 @@ export async function registerTrackStart( throw new Error(profile.error.message); } if (!profile.data.profile) { - throw new Error('Run `teambridge init` first to set your name and avatar.'); + throw new Error('Run `coord init` first to set your name and avatar.'); } const positionals = positionalArgs(argv, new Set(['--name', '--project', '--base-ref', '--base'])); @@ -69,7 +69,7 @@ export async function registerTrackStart( const picked = await ask('Project id for this track'); projectId = picked.trim() || undefined; } else { - throw new Error('Create a project first: `teambridge project create`'); + throw new Error('Create a project first: `coord project create`'); } } @@ -94,7 +94,7 @@ export async function runTrackJoin(argv: string[], options: ClientOptions): Prom throw new Error(profile.error.message); } if (!profile.data.profile) { - throw new Error('Run `teambridge init` first to set your name and avatar.'); + throw new Error('Run `coord init` first to set your name and avatar.'); } const positionals = positionalArgs(argv, new Set(['--name', '--as'])); @@ -124,7 +124,7 @@ export async function runTrackJoin(argv: string[], options: ClientOptions): Prom } } if (!track) { - throw new Error(`Session "${sessionName}" not found. Start it first or run \`teambridge sessions\` after \`teambridge login\`.`); + throw new Error(`Session "${sessionName}" not found. Start it first or run \`coord sessions\` after \`coord login\`.`); } if (track.status === 'archived') { throw new Error(`Session "${sessionName}" is archived.`); diff --git a/packages/cli/src/commands/vault.ts b/packages/cli/src/commands/vault.ts index f1e36df..fedf037 100644 --- a/packages/cli/src/commands/vault.ts +++ b/packages/cli/src/commands/vault.ts @@ -5,7 +5,7 @@ import { resolveCurrentTrack } from '../lib/current-track'; async function runVaultRead(argv: string[], options: ClientOptions): Promise { const path = argv.find((arg) => !arg.startsWith('-')); if (!path) { - throw new Error('Usage: teambridge vault read '); + throw new Error('Usage: coord vault read '); } const track = await resolveCurrentTrack(options); @@ -34,7 +34,7 @@ async function runVaultContext(_argv: string[], options: ClientOptions): Promise async function runVaultSearch(argv: string[], options: ClientOptions): Promise { const query = argv.filter((arg) => !arg.startsWith('-')).join(' '); if (!query.trim()) { - throw new Error('Usage: teambridge vault search '); + throw new Error('Usage: coord vault search '); } const track = await resolveCurrentTrack(options); @@ -67,5 +67,5 @@ export async function runVault(argv: string[], options: ClientOptions): Promise< await runVaultSearch(argv.slice(1), options); return; } - throw new Error('Usage: teambridge vault read |context|search '); + throw new Error('Usage: coord vault read |context|search '); } diff --git a/packages/cli/src/commands/ws.ts b/packages/cli/src/commands/ws.ts index a7ff3b0..6146316 100644 --- a/packages/cli/src/commands/ws.ts +++ b/packages/cli/src/commands/ws.ts @@ -8,7 +8,7 @@ async function resolveWorkspaceBySessionName(sessionName: string, options: Clien } const track = tracks.data.tracks.find((candidate) => candidate.sessionName === sessionName); if (!track) { - throw new Error(`Session "${sessionName}" not found. Start it first: \`teambridge start ${sessionName}\`.`); + throw new Error(`Session "${sessionName}" not found. Start it first: \`coord start ${sessionName}\`.`); } return track; } @@ -16,7 +16,7 @@ async function resolveWorkspaceBySessionName(sessionName: string, options: Clien function parseSessionName(argv: string[]): string { const sessionName = argv.find((arg) => !arg.startsWith('-')); if (!sessionName?.trim()) { - throw new Error('Usage: teambridge ws show|who|branches '); + throw new Error('Usage: coord ws show|who|branches '); } return sessionName.trim(); } @@ -86,5 +86,5 @@ export async function runWs(argv: string[], options: ClientOptions): Promise'); + throw new Error('Usage: coord ws show|who|branches '); } diff --git a/packages/cli/src/daemon-client.ts b/packages/cli/src/daemon-client.ts index c32a4b9..082a75a 100644 --- a/packages/cli/src/daemon-client.ts +++ b/packages/cli/src/daemon-client.ts @@ -13,15 +13,15 @@ import type { ReplyResponse, ResolveConflictResponse, StartWorkspaceResponse, - TeambridgeConfig, + CoordConfig, TrackListResponse, VaultContextResponse, VaultReadResponse, VaultSearchResponse, WorkspaceEvent, WorkspaceStatusResponse -} from '@teambridge/core'; -import { apiFail, buildDaemonUrl } from '@teambridge/core'; +} from '@coord/core'; +import { apiFail, buildDaemonUrl } from '@coord/core'; export type ClientOptions = { baseUrl?: string; @@ -40,7 +40,7 @@ async function request(url: string, init: RequestInit = {}): Promise(url: string, init: RequestInit = {}): Promise> { +): Promise> { return request(buildDaemonUrl('/config/init', options), { method: 'POST', body: JSON.stringify({ repoRoot: options.repoRoot, ...(body.relayMode ? { relayMode: body.relayMode } : {}) }) @@ -66,7 +66,7 @@ export async function initConfig( export async function getConfig( options: ClientOptions -): Promise> { +): Promise> { return request(buildDaemonUrl('/config', options)); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 83c6b14..d359a66 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -17,39 +17,39 @@ import { runMcp } from './commands/mcp'; import { daemonBaseUrl, resolveRepoRoot } from './repo'; function usage(): void { - console.log(`Teambridge CLI + console.log(`Coord CLI Usage: - teambridge init [--first-name NAME] [--last-name NAME] [--agent cursor|claude-code|codex] [--relay local|supabase] - teambridge project create [--name NAME] [--description TEXT] - teambridge project list - teambridge start [NAME] [BASE_REF] [--project PROJECT_ID] - teambridge join [NAME] [--as DISPLAY_NAME] - teambridge login --email EMAIL --password PASSWORD - teambridge sessions - teambridge list - teambridge sync - teambridge enter - teambridge publish - teambridge ask - teambridge inbox [--all] - teambridge reply - teambridge conflicts [--open] - teambridge conflicts detect - teambridge conflicts resolve - teambridge vault read - teambridge vault context - teambridge vault search - teambridge context [--json] [--peek] [--deltas-only] [--full] - teambridge hook install|uninstall|status - teambridge ws show|who|branches - teambridge daemon start|status|stop [--port PORT] - teambridge status - teambridge mcp + coord init [--first-name NAME] [--last-name NAME] [--agent cursor|claude-code|codex] [--relay local|supabase] + coord project create [--name NAME] [--description TEXT] + coord project list + coord start [NAME] [BASE_REF] [--project PROJECT_ID] + coord join [NAME] [--as DISPLAY_NAME] + coord login --email EMAIL --password PASSWORD + coord sessions + coord list + coord sync + coord enter + coord publish + coord ask + coord inbox [--all] + coord reply + coord conflicts [--open] + coord conflicts detect + coord conflicts resolve + coord vault read + coord vault context + coord vault search + coord context [--json] [--peek] [--deltas-only] [--full] + coord hook install|uninstall|status + coord ws show|who|branches + coord daemon start|status|stop [--port PORT] + coord status + coord mcp Environment: - TEAMBRIDGE_DAEMON_URL default http://127.0.0.1:9473 - TEAMBRIDGE_DAEMON_PORT used when URL unset + COORD_DAEMON_URL default http://127.0.0.1:9473 + COORD_DAEMON_PORT used when URL unset Run \`pnpm daemon\` in another terminal before CLI commands.`); } @@ -82,7 +82,7 @@ async function main(): Promise { await runProjectList(argv.slice(2), options); return; } - throw new Error('Usage: teambridge project create|list'); + throw new Error('Usage: coord project create|list'); } if (command === 'start') { @@ -183,7 +183,7 @@ async function main(): Promise { process.exit(1); } catch (error) { const message = error instanceof Error ? error.message : String(error); - console.error(`teambridge: ${message}`); + console.error(`coord: ${message}`); process.exit(1); } } diff --git a/packages/cli/src/lib/context-pointer.ts b/packages/cli/src/lib/context-pointer.ts index b0a16b6..319772c 100644 --- a/packages/cli/src/lib/context-pointer.ts +++ b/packages/cli/src/lib/context-pointer.ts @@ -4,7 +4,7 @@ import { safeDisplayName } from './naming'; /** * Local "last context I saw" marker, written next to the track's vault under - * `.teambridge/workspaces//`. `teambridge context` uses it to show + * `.coord/workspaces//`. `coord context` uses it to show * only what changed since a participant last pulled context — the delta the * Claude Code SessionStart hook injects. It is a local read cursor, not shared * state, so it is intentionally kept out of `events.jsonl` and the relay. @@ -18,7 +18,7 @@ export type ContextPointer = { }; function pointerDir(repoRoot: string, sessionName: string): string { - return join(repoRoot, '.teambridge', 'workspaces', sessionName); + return join(repoRoot, '.coord', 'workspaces', sessionName); } function pointerPath(repoRoot: string, sessionName: string, displayName: string): string { diff --git a/packages/cli/src/lib/current-track.ts b/packages/cli/src/lib/current-track.ts index d650bd8..901de4c 100644 --- a/packages/cli/src/lib/current-track.ts +++ b/packages/cli/src/lib/current-track.ts @@ -1,14 +1,14 @@ import { execFileSync } from 'node:child_process'; -import type { Workspace } from '@teambridge/core'; +import type { Workspace } from '@coord/core'; import type { ClientOptions } from '../daemon-client'; import { listTracks } from '../daemon-client'; -const PARTICIPANT_BRANCH_PATTERN = /^teambridge\/([^/]+)\/[^/]+$/; +const PARTICIPANT_BRANCH_PATTERN = /^coord\/([^/]+)\/[^/]+$/; /** * `publish` and `vault *` have no `` argument — they operate on * "the current track", inferred from the branch `start`/`join` already - * created (`teambridge//`, see lib/naming.ts). Reads HEAD + * created (`coord//`, see lib/naming.ts). Reads HEAD * from `cwd` (the literal invocation directory, which is the participant's * worktree — do not pass the U1-normalized repoRoot here). */ @@ -31,14 +31,14 @@ export function currentParticipantSlugFromBranch(cwd: string = process.cwd()): s return null; } const match = branch.match(PARTICIPANT_BRANCH_PATTERN); - return match ? branch.slice(`teambridge/${match[1]}/`.length) : null; + return match ? branch.slice(`coord/${match[1]}/`.length) : null; } export async function resolveCurrentTrack(options: ClientOptions, cwd: string = process.cwd()): Promise { const sessionName = currentSessionNameFromBranch(cwd); if (!sessionName) { throw new Error( - 'Not inside a track worktree. Run `teambridge enter ` and `cd` into the result first.' + 'Not inside a track worktree. Run `coord enter ` and `cd` into the result first.' ); } diff --git a/packages/cli/src/lib/naming.ts b/packages/cli/src/lib/naming.ts index 7624f41..df02cbf 100644 --- a/packages/cli/src/lib/naming.ts +++ b/packages/cli/src/lib/naming.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; * Mirrors the daemon's `safeDisplayName` (packages/daemon/src/index.ts:430) * BYTE-FOR-BYTE. The participant branch is built from this on both sides, and * the daemon enforces UNIQUE(branch); any drift here silently breaks join - * reconciliation. Keep identical until it is lifted into @teambridge/core + * reconciliation. Keep identical until it is lifted into @coord/core * (daemon ask #6 in docs/nihal-daemon-requests.md), then import from there. */ export function safeDisplayName(value: string): string { @@ -21,7 +21,7 @@ const SESSION_NAME_PATTERN = /^[A-Za-z0-9._-]+$/; /** * The daemon stores the session name raw into a git ref segment - * (`teambridge//`). Reject anything that would produce + * (`coord//`). Reject anything that would produce * an invalid ref before the daemon persists it. */ export function assertValidSessionName(sessionName: string): void { @@ -38,7 +38,7 @@ export function assertValidSessionName(sessionName: string): void { /** Branch uses the RAW (validated) session name — matches daemon index.ts:778-779. */ export function branchForParticipant(sessionName: string, displayName: string): string { - return `teambridge/${sessionName}/${safeDisplayName(displayName)}`; + return `coord/${sessionName}/${safeDisplayName(displayName)}`; } /** Path segment is sanitized for filesystem safety (branch is not — keep distinct). */ @@ -47,5 +47,5 @@ export function sessionSlug(sessionName: string): string { } export function worktreePathFor(repoRoot: string, sessionName: string, displayName: string): string { - return join(repoRoot, '.teambridge', 'worktrees', sessionSlug(sessionName), safeDisplayName(displayName)); + return join(repoRoot, '.coord', 'worktrees', sessionSlug(sessionName), safeDisplayName(displayName)); } diff --git a/packages/cli/src/lib/pointers.ts b/packages/cli/src/lib/pointers.ts index 6772e62..2c39c63 100644 --- a/packages/cli/src/lib/pointers.ts +++ b/packages/cli/src/lib/pointers.ts @@ -4,7 +4,7 @@ import { safeDisplayName } from './naming'; /** * Local record of where a participant's worktree lives, written next to the - * track's vault under `.teambridge/workspaces//`. Lets `enter` + * track's vault under `.coord/workspaces//`. Lets `enter` * resolve the path without reading the daemon's SQLite (the daemon does not * expose worktree rows yet — ask #2). */ @@ -19,7 +19,7 @@ export type WorktreePointer = { }; function pointerDir(repoRoot: string, sessionName: string): string { - return join(repoRoot, '.teambridge', 'workspaces', sessionName); + return join(repoRoot, '.coord', 'workspaces', sessionName); } function pointerPath(repoRoot: string, sessionName: string, displayName: string): string { diff --git a/packages/cli/src/lib/worktree.ts b/packages/cli/src/lib/worktree.ts index 42a5901..44124fa 100644 --- a/packages/cli/src/lib/worktree.ts +++ b/packages/cli/src/lib/worktree.ts @@ -30,16 +30,16 @@ export type ParticipantWorktreeResult = { }; /** - * Worktrees live under `.teambridge/`, which must be gitignored or the linked + * Worktrees live under `.coord/`, which must be gitignored or the linked * worktree shows as an untracked dir in the superproject. The repo's root - * .gitignore normally covers `.teambridge/`; if it doesn't, drop a self-contained - * `.teambridge/.gitignore` (`*`) rather than editing the user's root ignore. + * .gitignore normally covers `.coord/`; if it doesn't, drop a self-contained + * `.coord/.gitignore` (`*`) rather than editing the user's root ignore. */ -function ensureTeambridgeIgnored(git: GitRunner, repoRoot: string): void { - if (isGitIgnored(git, repoRoot, '.teambridge/worktrees')) { +function ensureCoordIgnored(git: GitRunner, repoRoot: string): void { + if (isGitIgnored(git, repoRoot, '.coord/worktrees')) { return; } - const dir = join(repoRoot, '.teambridge'); + const dir = join(repoRoot, '.coord'); mkdirSync(dir, { recursive: true }); const ignoreFile = join(dir, '.gitignore'); if (!existsSync(ignoreFile)) { @@ -49,7 +49,7 @@ function ensureTeambridgeIgnored(git: GitRunner, repoRoot: string): void { /** * Create (or reuse) an isolated git worktree for a joiner, cut from the track's - * immutable base commit on branch `teambridge//`. Read-only + * immutable base commit on branch `coord//`. Read-only * preflight first; never clobbers an existing path it doesn't recognize. */ export function prepareParticipantWorktree( @@ -60,7 +60,7 @@ export function prepareParticipantWorktree( const branch = branchForParticipant(sessionName, displayName); const path = worktreePathFor(repoRoot, sessionName, displayName); - ensureTeambridgeIgnored(git, repoRoot); + ensureCoordIgnored(git, repoRoot); // Idempotency / collision preflight (read-only). const registered = listWorktrees(git, repoRoot); diff --git a/packages/cli/src/repo.ts b/packages/cli/src/repo.ts index 31590a8..77806ef 100644 --- a/packages/cli/src/repo.ts +++ b/packages/cli/src/repo.ts @@ -8,9 +8,9 @@ function git(args: string[], cwd: string): string { /** * `git rev-parse --show-toplevel` returns a LINKED worktree's own path when run - * from inside one (e.g. a Teambridge participant worktree under - * `.teambridge/worktrees//`), not the main repo root where - * `.teambridge/state.sqlite` and the vault actually live. Commands meant to run + * from inside one (e.g. a Coord participant worktree under + * `.coord/worktrees//`), not the main repo root where + * `.coord/state.sqlite` and the vault actually live. Commands meant to run * post-`enter` (`publish`, `vault *`) need the main root, so detect a linked * worktree and resolve back to it. * @@ -29,7 +29,7 @@ export function resolveRepoRoot(cwd: string = process.cwd()): string { try { toplevel = git(['rev-parse', '--show-toplevel'], resolve(cwd)); } catch { - throw new Error('Not inside a git repository. Run teambridge from your project repo root.'); + throw new Error('Not inside a git repository. Run coord from your project repo root.'); } let gitDir: string; @@ -53,6 +53,6 @@ export function resolveRepoRoot(cwd: string = process.cwd()): string { } export function daemonBaseUrl(): string { - const port = process.env.TEAMBRIDGE_DAEMON_PORT ?? '9473'; - return process.env.TEAMBRIDGE_DAEMON_URL ?? `http://127.0.0.1:${port}`; + const port = process.env.COORD_DAEMON_PORT ?? '9473'; + return process.env.COORD_DAEMON_URL ?? `http://127.0.0.1:${port}`; } diff --git a/packages/cli/test/cli.test.cjs b/packages/cli/test/cli.test.cjs index 8f61737..9ef38c9 100644 --- a/packages/cli/test/cli.test.cjs +++ b/packages/cli/test/cli.test.cjs @@ -1,6 +1,6 @@ const test = require('node:test'); const assert = require('node:assert/strict'); -const { formatDisplayName, avatarNameSlug } = require('@teambridge/core'); +const { formatDisplayName, avatarNameSlug } = require('@coord/core'); test('display name formatting matches dashboard avatar slug', () => { const displayName = formatDisplayName('Ronish', "O'Brien"); diff --git a/packages/cli/test/current-track.test.cjs b/packages/cli/test/current-track.test.cjs index 139aac2..bb44b93 100644 --- a/packages/cli/test/current-track.test.cjs +++ b/packages/cli/test/current-track.test.cjs @@ -27,7 +27,7 @@ function makeRepoOnBranch(branch) { } test('currentSessionNameFromBranch parses the session out of a participant branch', () => { - const dir = makeRepoOnBranch('teambridge/auth-redesign/kushagra'); + const dir = makeRepoOnBranch('coord/auth-redesign/kushagra'); try { assert.equal(currentSessionNameFromBranch(dir), 'auth-redesign'); } finally { @@ -35,7 +35,7 @@ test('currentSessionNameFromBranch parses the session out of a participant branc } }); -test('currentSessionNameFromBranch returns null on a non-teambridge branch', () => { +test('currentSessionNameFromBranch returns null on a non-coord branch', () => { const dir = makeRepoOnBranch(null); try { assert.equal(currentSessionNameFromBranch(dir), null); @@ -63,7 +63,7 @@ test('resolveCurrentTrack throws when not inside a track worktree', async () => }); test('resolveCurrentTrack resolves the workspace matching the branch session name', async () => { - const dir = makeRepoOnBranch('teambridge/auth-redesign/kushagra'); + const dir = makeRepoOnBranch('coord/auth-redesign/kushagra'); const original = daemonClient.listTracks; daemonClient.listTracks = async () => ({ ok: true, @@ -79,7 +79,7 @@ test('resolveCurrentTrack resolves the workspace matching the branch session nam }); test('resolveCurrentTrack throws when the daemon does not know the branch-derived session', async () => { - const dir = makeRepoOnBranch('teambridge/ghost-session/kushagra'); + const dir = makeRepoOnBranch('coord/ghost-session/kushagra'); const original = daemonClient.listTracks; daemonClient.listTracks = async () => ({ ok: true, data: { tracks: [] } }); try { diff --git a/packages/cli/test/enter.test.cjs b/packages/cli/test/enter.test.cjs index 21e3ff3..e98fec2 100644 --- a/packages/cli/test/enter.test.cjs +++ b/packages/cli/test/enter.test.cjs @@ -35,14 +35,14 @@ function captureStdout() { test('runEnter prints only the resolved worktree path to stdout', async () => { const repoRoot = realpathSync(mkdtempSync(path.join(os.tmpdir(), 'tb-enter-'))); - const worktreePath = path.join(repoRoot, '.teambridge', 'worktrees', 'auth', 'kushagra'); + const worktreePath = path.join(repoRoot, '.coord', 'worktrees', 'auth', 'kushagra'); mkdirSync(worktreePath, { recursive: true }); writeWorktreePointer(repoRoot, { workspaceId: 'ws_1', sessionName: 'auth', displayName: 'Kushagra', path: worktreePath, - branch: 'teambridge/auth/kushagra', + branch: 'coord/auth/kushagra', baseCommit: 'abc123', role: 'joiner' }); @@ -76,13 +76,13 @@ test('runEnter throws a clear error when no pointer was ever recorded', async () test('runEnter throws a clear error when the recorded worktree no longer exists on disk', async () => { const repoRoot = realpathSync(mkdtempSync(path.join(os.tmpdir(), 'tb-enter-stale-'))); - const worktreePath = path.join(repoRoot, '.teambridge', 'worktrees', 'auth', 'kushagra'); + const worktreePath = path.join(repoRoot, '.coord', 'worktrees', 'auth', 'kushagra'); writeWorktreePointer(repoRoot, { workspaceId: 'ws_1', sessionName: 'auth', displayName: 'Kushagra', path: worktreePath, - branch: 'teambridge/auth/kushagra', + branch: 'coord/auth/kushagra', baseCommit: 'abc123', role: 'joiner' }); diff --git a/packages/cli/test/publish.test.cjs b/packages/cli/test/publish.test.cjs index 7bd6491..a4aaf1c 100644 --- a/packages/cli/test/publish.test.cjs +++ b/packages/cli/test/publish.test.cjs @@ -20,7 +20,7 @@ function makeTrackWorktree() { writeFileSync(path.join(dir, 'README.md'), 'hi\n'); git(['add', 'README.md'], dir); git(['commit', '-m', 'init'], dir); - git(['checkout', '-b', 'teambridge/auth-redesign/kushagra'], dir); + git(['checkout', '-b', 'coord/auth-redesign/kushagra'], dir); return dir; } diff --git a/packages/cli/test/repo.test.cjs b/packages/cli/test/repo.test.cjs index df03044..62e375f 100644 --- a/packages/cli/test/repo.test.cjs +++ b/packages/cli/test/repo.test.cjs @@ -33,9 +33,9 @@ test('resolveRepoRoot returns the repo root unchanged when run from the main wor test('resolveRepoRoot resolves to the main repo root when run from inside a linked worktree', () => { const repo = makeRepo(); - const worktreePath = path.join(repo, '.teambridge', 'worktrees', 'auth', 'kushagra'); + const worktreePath = path.join(repo, '.coord', 'worktrees', 'auth', 'kushagra'); try { - git(['worktree', 'add', '-b', 'teambridge/auth/kushagra', worktreePath, 'HEAD'], repo); + git(['worktree', 'add', '-b', 'coord/auth/kushagra', worktreePath, 'HEAD'], repo); assert.equal(resolveRepoRoot(worktreePath), repo); } finally { rmSync(repo, { recursive: true, force: true }); @@ -44,9 +44,9 @@ test('resolveRepoRoot resolves to the main repo root when run from inside a link test('resolveRepoRoot resolves to the main repo root from a subdirectory nested inside a linked worktree', () => { const repo = makeRepo(); - const worktreePath = path.join(repo, '.teambridge', 'worktrees', 'auth', 'kushagra'); + const worktreePath = path.join(repo, '.coord', 'worktrees', 'auth', 'kushagra'); try { - git(['worktree', 'add', '-b', 'teambridge/auth/kushagra', worktreePath, 'HEAD'], repo); + git(['worktree', 'add', '-b', 'coord/auth/kushagra', worktreePath, 'HEAD'], repo); const nested = path.join(worktreePath); assert.equal(resolveRepoRoot(nested), repo); } finally { diff --git a/packages/cli/test/start.test.cjs b/packages/cli/test/start.test.cjs index b63c5ad..dadc908 100644 --- a/packages/cli/test/start.test.cjs +++ b/packages/cli/test/start.test.cjs @@ -59,13 +59,13 @@ test('runStart registers the track and creates a real worktree + pointer for the try { await runStart(['auth-redesign'], { repoRoot: repo, baseUrl: 'http://unused' }); - const worktreePath = path.join(repo, '.teambridge', 'worktrees', 'auth-redesign', 'kushagra'); + const worktreePath = path.join(repo, '.coord', 'worktrees', 'auth-redesign', 'kushagra'); assert.ok(existsSync(worktreePath), 'expected a real worktree directory to be created'); const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: worktreePath, encoding: 'utf8' }).trim(); - assert.equal(branch, 'teambridge/auth-redesign/kushagra'); + assert.equal(branch, 'coord/auth-redesign/kushagra'); - const pointerPath = path.join(repo, '.teambridge', 'workspaces', 'auth-redesign', '.worktree.kushagra.json'); + const pointerPath = path.join(repo, '.coord', 'workspaces', 'auth-redesign', '.worktree.kushagra.json'); assert.ok(existsSync(pointerPath), 'expected a worktree pointer to be written'); const pointer = JSON.parse(readFileSync(pointerPath, 'utf8')); assert.equal(pointer.role, 'creator'); @@ -85,7 +85,7 @@ test('runStart surfaces a clear error and leaves the daemon track registered whe () => runStart(['auth-redesign'], { repoRoot: repo, baseUrl: 'http://unused' }), /was started \(workspace id: ws_1\), but creating your worktree failed/ ); - const worktreePath = path.join(repo, '.teambridge', 'worktrees', 'auth-redesign', 'kushagra'); + const worktreePath = path.join(repo, '.coord', 'worktrees', 'auth-redesign', 'kushagra'); assert.ok(!existsSync(worktreePath), 'must not leave a partial worktree behind'); } finally { restore(); diff --git a/packages/cli/test/status.test.cjs b/packages/cli/test/status.test.cjs index 1ecd987..a5a138d 100644 --- a/packages/cli/test/status.test.cjs +++ b/packages/cli/test/status.test.cjs @@ -60,13 +60,13 @@ test('status prints profile, projects, and tracks when daemon calls succeed', as test('status fails when daemon is unreachable', async () => { const restore = stubStatus({ - profile: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the teambridge daemon' } }, - projects: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the teambridge daemon' } }, - tracks: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the teambridge daemon' } } + profile: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the coord daemon' } }, + projects: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the coord daemon' } }, + tracks: { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Cannot reach the coord daemon' } } }); try { - await assert.rejects(() => runStatus([], OPTIONS), /Cannot reach the teambridge daemon/); + await assert.rejects(() => runStatus([], OPTIONS), /Cannot reach the coord daemon/); } finally { restore(); } diff --git a/packages/cli/test/vault.test.cjs b/packages/cli/test/vault.test.cjs index 75a1ffd..d8df8bd 100644 --- a/packages/cli/test/vault.test.cjs +++ b/packages/cli/test/vault.test.cjs @@ -20,7 +20,7 @@ function makeTrackWorktree() { writeFileSync(path.join(dir, 'README.md'), 'hi\n'); git(['add', 'README.md'], dir); git(['commit', '-m', 'init'], dir); - git(['checkout', '-b', 'teambridge/auth-redesign/kushagra'], dir); + git(['checkout', '-b', 'coord/auth-redesign/kushagra'], dir); return dir; } @@ -162,6 +162,6 @@ test( test( 'vault rejects an unrecognized subcommand', withTrackWorktree(async (options) => { - await assert.rejects(() => runVault(['bogus'], options), /Usage: teambridge vault read/); + await assert.rejects(() => runVault(['bogus'], options), /Usage: coord vault read/); }) ); diff --git a/packages/cli/test/worktree.test.cjs b/packages/cli/test/worktree.test.cjs index c23a202..dee26ab 100644 --- a/packages/cli/test/worktree.test.cjs +++ b/packages/cli/test/worktree.test.cjs @@ -16,7 +16,7 @@ test('safeDisplayName matches the daemon rules byte-for-byte', () => { }); test('branchForParticipant uses the raw session name + safe display name', () => { - assert.equal(naming.branchForParticipant('billing-refactor', 'Kushagra'), 'teambridge/billing-refactor/kushagra'); + assert.equal(naming.branchForParticipant('billing-refactor', 'Kushagra'), 'coord/billing-refactor/kushagra'); }); test('assertValidSessionName rejects unsafe names and accepts good ones', () => { @@ -48,7 +48,7 @@ const FAIL = { stdout: '', stderr: '', status: 1 }; test('prepareParticipantWorktree creates a new branch worktree on the happy path', () => { const repoRoot = path.join(os.tmpdir(), 'tb-wt-happy-does-not-exist'); const git = fakeGit([ - { match: (a) => a[0] === 'check-ignore', result: OK }, // .teambridge ignored + { match: (a) => a[0] === 'check-ignore', result: OK }, // .coord ignored { match: (a) => a[0] === 'worktree' && a[1] === 'list', result: OK }, // none registered { match: (a) => a[0] === 'rev-parse', result: OK }, // base commit present { match: (a) => a[0] === 'show-ref', result: FAIL }, // branch does not exist @@ -62,7 +62,7 @@ test('prepareParticipantWorktree creates a new branch worktree on the happy path assert.equal(result.created, true); assert.equal(result.reused, false); - assert.equal(result.branch, 'teambridge/auth/kushagra'); + assert.equal(result.branch, 'coord/auth/kushagra'); const addCall = git.calls.find((a) => a[0] === 'worktree' && a[1] === 'add'); assert.ok(addCall, 'expected git worktree add to be called'); assert.ok(addCall.includes('-b'), 'expected a new branch to be created'); @@ -87,7 +87,7 @@ test('prepareParticipantWorktree fails clearly when the base commit is missing l test('prepareParticipantWorktree is idempotent: reuses an already-registered worktree', () => { const repoRoot = path.join(os.tmpdir(), 'tb-wt-reuse'); const expectedPath = naming.worktreePathFor(repoRoot, 'auth', 'Kushagra'); - const porcelain = `worktree ${expectedPath}\nHEAD abc123\nbranch refs/heads/teambridge/auth/kushagra\n`; + const porcelain = `worktree ${expectedPath}\nHEAD abc123\nbranch refs/heads/coord/auth/kushagra\n`; const git = fakeGit([ { match: (a) => a[0] === 'check-ignore', result: OK }, { match: (a) => a[0] === 'worktree' && a[1] === 'list', result: { stdout: porcelain, stderr: '', status: 0 } } @@ -105,7 +105,7 @@ test('prepareParticipantWorktree is idempotent: reuses an already-registered wor test('prepareParticipantWorktree refuses when the branch is checked out elsewhere', () => { const repoRoot = path.join(os.tmpdir(), 'tb-wt-branch-elsewhere'); - const porcelain = `worktree /some/other/path\nHEAD abc123\nbranch refs/heads/teambridge/auth/kushagra\n`; + const porcelain = `worktree /some/other/path\nHEAD abc123\nbranch refs/heads/coord/auth/kushagra\n`; const git = fakeGit([ { match: (a) => a[0] === 'check-ignore', result: OK }, { match: (a) => a[0] === 'worktree' && a[1] === 'list', result: { stdout: porcelain, stderr: '', status: 0 } } diff --git a/packages/cli/test/ws.test.cjs b/packages/cli/test/ws.test.cjs index a3a8620..9f6f7cd 100644 --- a/packages/cli/test/ws.test.cjs +++ b/packages/cli/test/ws.test.cjs @@ -39,8 +39,8 @@ const TWO_PARTICIPANTS = { data: { workspace: { id: 'ws_1', sessionName: 'auth-redesign', status: 'active', baseCommit: 'abc123' }, participants: [ - { displayName: 'Kushagra', status: 'active', agent: 'claude-code', branch: 'teambridge/auth-redesign/kushagra' }, - { displayName: 'Ronish', status: 'idle', agent: 'cursor', branch: 'teambridge/auth-redesign/ronish' } + { displayName: 'Kushagra', status: 'active', agent: 'claude-code', branch: 'coord/auth-redesign/kushagra' }, + { displayName: 'Ronish', status: 'idle', agent: 'cursor', branch: 'coord/auth-redesign/ronish' } ], lastSeq: 5 } @@ -79,7 +79,7 @@ test('ws branches lists each participant branch', async () => { await runWs(['branches', 'auth-redesign'], OPTIONS); assert.equal( capture.output, - 'teambridge/auth-redesign/kushagra\tKushagra\nteambridge/auth-redesign/ronish\tRonish\n' + 'coord/auth-redesign/kushagra\tKushagra\ncoord/auth-redesign/ronish\tRonish\n' ); } finally { capture.restore(); diff --git a/packages/core/package.json b/packages/core/package.json index b3ea124..37385dd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@teambridge/core", + "name": "@coord/core", "version": "0.0.0", "private": true, "main": "dist/index.js", diff --git a/packages/core/src/contracts/api.ts b/packages/core/src/contracts/api.ts index cb99465..3534341 100644 --- a/packages/core/src/contracts/api.ts +++ b/packages/core/src/contracts/api.ts @@ -1,6 +1,6 @@ import type { VaultCheckpoint } from './checkpoints'; import type { Conflict } from './conflicts'; -import type { TeambridgeError } from './errors'; +import type { CoordError } from './errors'; import type { WorkspaceEvent } from './events'; import type { WorktreeInfo, RepoContext } from './git'; import type { InboxMessage } from './inbox'; @@ -17,7 +17,7 @@ export type ApiOk = { export type ApiFail = { ok: false; - error: TeambridgeError; + error: CoordError; }; export type ApiResult = ApiOk | ApiFail; @@ -26,7 +26,7 @@ export function apiOk(data: T): ApiOk { return { ok: true, data }; } -export function apiFail(code: TeambridgeError['code'], message: string, details?: unknown): ApiFail { +export function apiFail(code: CoordError['code'], message: string, details?: unknown): ApiFail { return { ok: false, error: { code, message, details } diff --git a/packages/core/src/contracts/config.ts b/packages/core/src/contracts/config.ts index c816bf0..29b352d 100644 --- a/packages/core/src/contracts/config.ts +++ b/packages/core/src/contracts/config.ts @@ -1,7 +1,7 @@ import type { AgentKind } from './participant'; import type { RelayMode } from './workspace'; -export type TeambridgeConfig = { +export type CoordConfig = { schemaVersion: 1; defaultRelayMode: RelayMode; daemonPort: number; @@ -13,7 +13,7 @@ export type TeambridgeConfig = { }; }; -/** Repo-local identity written by `teambridge init`; dashboard reads the same display name + avatar slug. */ +/** Repo-local identity written by `coord init`; dashboard reads the same display name + avatar slug. */ export type LocalUserProfile = { schemaVersion: 1; firstName: string; diff --git a/packages/core/src/contracts/errors.ts b/packages/core/src/contracts/errors.ts index 967cd0c..ec6f872 100644 --- a/packages/core/src/contracts/errors.ts +++ b/packages/core/src/contracts/errors.ts @@ -1,4 +1,4 @@ -export type TeambridgeErrorCode = +export type CoordErrorCode = | 'WORKSPACE_NOT_FOUND' | 'WORKTREE_NOT_FOUND' | 'PROJECT_NOT_FOUND' @@ -10,8 +10,8 @@ export type TeambridgeErrorCode = | 'NOT_FOUND' | 'INTERNAL_ERROR'; -export type TeambridgeError = { - code: TeambridgeErrorCode; +export type CoordError = { + code: CoordErrorCode; message: string; details?: unknown; }; diff --git a/packages/core/src/contracts/mcp.ts b/packages/core/src/contracts/mcp.ts index 7f820a1..4b938f6 100644 --- a/packages/core/src/contracts/mcp.ts +++ b/packages/core/src/contracts/mcp.ts @@ -32,11 +32,11 @@ export type VaultReadToolInput = { }; export const MCP_RESOURCE_NAMES = [ - 'teambridge://workspace', - 'teambridge://participants', - 'teambridge://vault/context', - 'teambridge://inbox', - 'teambridge://conflicts' + 'coord://workspace', + 'coord://participants', + 'coord://vault/context', + 'coord://inbox', + 'coord://conflicts' ] as const; export type McpResourceName = (typeof MCP_RESOURCE_NAMES)[number]; @@ -59,7 +59,7 @@ export type McpToolName = (typeof MCP_TOOL_NAMES)[number]; * When relay is not configured, the field is absent and the resource * degrades to plain `WorkspaceStatusResponse`. * - * On the `teambridge://participants` resource, participant `status` and + * On the `coord://participants` resource, participant `status` and * `lastSeenAt` fields are relay-backed when the workspace's `relayMode` * is `'supabase'`; in local mode they reflect local heartbeat state only. */ diff --git a/packages/core/src/contracts/schemas.ts b/packages/core/src/contracts/schemas.ts index 2546160..b974422 100644 --- a/packages/core/src/contracts/schemas.ts +++ b/packages/core/src/contracts/schemas.ts @@ -16,7 +16,7 @@ export const ParticipantSchema = z.object({ export const WorkspaceStatusSchema = z.enum(['active', 'archived']); export const RelayModeSchema = z.enum(['local', 'supabase']); -export const TeambridgeConfigSchema = z.object({ +export const CoordConfigSchema = z.object({ schemaVersion: z.literal(1), defaultRelayMode: RelayModeSchema, daemonPort: z.number().int().positive(), @@ -274,7 +274,7 @@ export const VaultContextSchema = z.object({ lastSeq: z.number().int().nonnegative().optional() }); -export const TeambridgeErrorCodeSchema = z.enum([ +export const CoordErrorCodeSchema = z.enum([ 'WORKSPACE_NOT_FOUND', 'WORKTREE_NOT_FOUND', 'PROJECT_NOT_FOUND', @@ -287,8 +287,8 @@ export const TeambridgeErrorCodeSchema = z.enum([ 'INTERNAL_ERROR' ]); -export const TeambridgeErrorSchema = z.object({ - code: TeambridgeErrorCodeSchema, +export const CoordErrorSchema = z.object({ + code: CoordErrorCodeSchema, message: z.string().min(1), details: z.unknown().optional() }); @@ -302,7 +302,7 @@ export function ApiOkSchema(data: T) { export const ApiFailSchema = z.object({ ok: z.literal(false), - error: TeambridgeErrorSchema + error: CoordErrorSchema }); export function ApiResultSchema(data: T) { diff --git a/packages/core/test/contracts.test.cjs b/packages/core/test/contracts.test.cjs index 375741d..c0f4080 100644 --- a/packages/core/test/contracts.test.cjs +++ b/packages/core/test/contracts.test.cjs @@ -9,12 +9,12 @@ const { PublishEventSchema, RelayStatusResponseSchema, StartWorkspaceResponseSchema, - TeambridgeConfigSchema, + CoordConfigSchema, VaultContextSchema, WorkspaceManifestSchema, WorkspaceStatusResponseSchema } = require('../dist'); -const contracts = require('@teambridge/core/contracts'); +const contracts = require('@coord/core/contracts'); const createdAt = '2026-06-22T00:00:00.000Z'; @@ -22,7 +22,7 @@ const participant = { id: 'user_nihal', displayName: 'nihal', workspaceId: 'ws_123', - branch: 'teambridge/billing-refactor/nihal', + branch: 'coord/billing-refactor/nihal', agent: 'claude-code', status: 'active', lastSeenAt: createdAt @@ -62,8 +62,8 @@ test('WorkspaceStatusResponseSchema includes participants and worktrees', () => { workspaceId: 'ws_123', userId: 'user_nihal', - path: '/tmp/repo/.teambridge/worktrees/billing-refactor/nihal', - branch: 'teambridge/billing-refactor/nihal', + path: '/tmp/repo/.coord/worktrees/billing-refactor/nihal', + branch: 'coord/billing-refactor/nihal', baseCommit: 'abc123', currentCommit: 'abc123', dirty: false @@ -157,11 +157,11 @@ test('apiOk and apiFail preserve ApiResult envelope shape', () => { }); test('core package keeps contracts subpath export available', () => { - assert.equal(contracts.MCP_RESOURCE_NAMES.includes('teambridge://workspace'), true); + assert.equal(contracts.MCP_RESOURCE_NAMES.includes('coord://workspace'), true); assert.equal(typeof contracts.WorkspaceManifestSchema.parse, 'function'); }); -test('TeambridgeConfigSchema keeps repo config defaults stable', () => { +test('CoordConfigSchema keeps repo config defaults stable', () => { const config = { schemaVersion: 1, defaultRelayMode: 'local', @@ -174,8 +174,8 @@ test('TeambridgeConfigSchema keeps repo config defaults stable', () => { } }; - assert.deepEqual(TeambridgeConfigSchema.parse(config), config); - assert.throws(() => TeambridgeConfigSchema.parse({ ...config, defaultRelayMode: 'remote' })); + assert.deepEqual(CoordConfigSchema.parse(config), config); + assert.throws(() => CoordConfigSchema.parse({ ...config, defaultRelayMode: 'remote' })); }); test('RelayMode accepts both local and supabase', () => { diff --git a/packages/daemon/package.json b/packages/daemon/package.json index 38da354..f91defa 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -1,5 +1,5 @@ { - "name": "@teambridge/daemon", + "name": "@coord/daemon", "version": "0.0.0", "private": true, "main": "dist/index.js", @@ -9,8 +9,8 @@ "start": "node dist/index.js" }, "dependencies": { - "@teambridge/core": "workspace:*", - "@teambridge/vault": "workspace:*", + "@coord/core": "workspace:*", + "@coord/vault": "workspace:*", "dotenv": "^17.4.2", "sharp": "^0.35.2", "zod": "^4.4.3" diff --git a/packages/daemon/src/index.ts b/packages/daemon/src/index.ts index c85e8a4..6f90706 100644 --- a/packages/daemon/src/index.ts +++ b/packages/daemon/src/index.ts @@ -10,8 +10,8 @@ import dotenv from 'dotenv'; dotenv.config(); dotenv.config({ path: join(__dirname, '../../../.env') }); -if (process.env.TEAMBRIDGE_REPO_ROOT) { - dotenv.config({ path: join(resolve(process.env.TEAMBRIDGE_REPO_ROOT), '.env') }); +if (process.env.COORD_REPO_ROOT) { + dotenv.config({ path: join(resolve(process.env.COORD_REPO_ROOT), '.env') }); } import { z } from 'zod'; import type { @@ -48,8 +48,8 @@ import type { StartWorkspaceRequest, StartWorkspaceResponse, SyncStateEntry, - TeambridgeConfig, - TeambridgeErrorCode, + CoordConfig, + CoordErrorCode, TrackListResponse, VaultCheckpoint, VaultAnnotateResponseBody, @@ -62,13 +62,13 @@ import type { WorkspaceEvent, WorkspaceListResponse, WorkspaceManifest -} from '@teambridge/core'; +} from '@coord/core'; import { JoinWorkspaceRequestSchema, PublishEventRequestSchema, RelayModeSchema, StartWorkspaceRequestSchema, - TeambridgeConfigSchema, + CoordConfigSchema, CreateProjectRequestSchema, SaveLocalUserProfileRequestSchema, UpsertProjectMemberRequestSchema, @@ -76,7 +76,7 @@ import { avatarStorageId, avatarNameSlug, formatDisplayName -} from '@teambridge/core'; +} from '@coord/core'; import { annotateVaultItem, createVaultContext, @@ -86,7 +86,7 @@ import { readEventsJsonl, readVaultFile, rebuildPhaseOneVault -} from '@teambridge/vault'; +} from '@coord/vault'; import { DEFAULT_PFP_QUERY, generatePfp, @@ -98,10 +98,10 @@ import { } from './pfp'; const DEFAULT_PORT = 9473; -const RELAY_SYNC_INTERVAL_MS = Number(process.env.TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS ?? 5000); -const RELAY_PRESENCE_INTERVAL_MS = Number(process.env.TEAMBRIDGE_RELAY_PRESENCE_INTERVAL_MS ?? 15000); -const CHECKPOINT_INTERVAL_EVENTS = Number(process.env.TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS ?? 50); -const CHECKPOINT_LEASE_MS = Number(process.env.TEAMBRIDGE_CHECKPOINT_LEASE_MS ?? 60000); +const RELAY_SYNC_INTERVAL_MS = Number(process.env.COORD_RELAY_SYNC_INTERVAL_MS ?? 5000); +const RELAY_PRESENCE_INTERVAL_MS = Number(process.env.COORD_RELAY_PRESENCE_INTERVAL_MS ?? 15000); +const CHECKPOINT_INTERVAL_EVENTS = Number(process.env.COORD_CHECKPOINT_INTERVAL_EVENTS ?? 50); +const CHECKPOINT_LEASE_MS = Number(process.env.COORD_CHECKPOINT_LEASE_MS ?? 60000); const CHECKPOINT_BUCKET = 'teambridge-checkpoints'; const StartRequestBodySchema = StartWorkspaceRequestSchema.extend({ @@ -226,7 +226,7 @@ const RelayRequestBodySchema = z.object({ repoRoot: z.string().min(1).optional() }); -const DEFAULT_CONFIG: TeambridgeConfig = { +const DEFAULT_CONFIG: CoordConfig = { schemaVersion: 1, defaultRelayMode: 'local', daemonPort: DEFAULT_PORT, @@ -268,7 +268,7 @@ function ok(data: T): ApiResult { return { ok: true, data }; } -function fail(code: TeambridgeErrorCode, message: string, details?: unknown): ApiResult { +function fail(code: CoordErrorCode, message: string, details?: unknown): ApiResult { return { ok: false, error: { code, message, details } @@ -290,8 +290,8 @@ function findGitRepoRoot(startDir: string): string { } function parseArgs(argv: string[]): { port: number; repoRoot: string } { - let port = Number(process.env.TEAMBRIDGE_DAEMON_PORT ?? DEFAULT_PORT); - let repoRoot = process.env.TEAMBRIDGE_REPO_ROOT ?? findGitRepoRoot(process.cwd()); + let port = Number(process.env.COORD_DAEMON_PORT ?? DEFAULT_PORT); + let repoRoot = process.env.COORD_REPO_ROOT ?? findGitRepoRoot(process.cwd()); for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; @@ -525,7 +525,7 @@ function safeDisplayName(value: string): string { } function getWorkspaceDir(repoRoot: string, sessionName: string): string { - return join(repoRoot, '.teambridge', 'workspaces', sessionName); + return join(repoRoot, '.coord', 'workspaces', sessionName); } function sqlValue(value: string | number | null): string { @@ -557,10 +557,10 @@ function querySql(dbPath: string, sql: string): T[] { } function initializeStateDb(repoRoot: string): string { - const teambridgeDir = join(repoRoot, '.teambridge'); - mkdirSync(teambridgeDir, { recursive: true }); + const coordDir = join(repoRoot, '.coord'); + mkdirSync(coordDir, { recursive: true }); - const dbPath = join(teambridgeDir, 'state.sqlite'); + const dbPath = join(coordDir, 'state.sqlite'); // Migration: rename legacy workspaces table → tracks const existingTables = querySql<{ name: string }>( @@ -699,7 +699,7 @@ function initializeStateDb(repoRoot: string): string { `); } catch (error) { console.error( - `[teambridge] vault search index unavailable — this sqlite3 build lacks FTS5:\n ${ + `[coord] vault search index unavailable — this sqlite3 build lacks FTS5:\n ${ error instanceof Error ? error.message : String(error) }` ); @@ -1098,15 +1098,15 @@ function sendBinary( response.end(buffer); } -async function ensureTeambridgeDirs(repoRoot: string): Promise { - await mkdir(join(repoRoot, '.teambridge', 'workspaces'), { recursive: true }); +async function ensureCoordDirs(repoRoot: string): Promise { + await mkdir(join(repoRoot, '.coord', 'workspaces'), { recursive: true }); } function getConfigPath(repoRoot: string): string { - return join(repoRoot, '.teambridge', 'config.json'); + return join(repoRoot, '.coord', 'config.json'); } -async function readRepoConfig(repoRoot: string): Promise { +async function readRepoConfig(repoRoot: string): Promise { const configPath = getConfigPath(repoRoot); const content = await readFile(configPath, 'utf8').catch((error: NodeJS.ErrnoException) => { if (error.code === 'ENOENT') { @@ -1120,11 +1120,11 @@ async function readRepoConfig(repoRoot: string): Promise { return DEFAULT_CONFIG; } - return TeambridgeConfigSchema.parse(JSON.parse(content)); + return CoordConfigSchema.parse(JSON.parse(content)); } function getUserProfilePath(repoRoot: string): string { - return join(repoRoot, '.teambridge', 'user.json'); + return join(repoRoot, '.coord', 'user.json'); } async function readLocalUserProfile(repoRoot: string): Promise { @@ -1142,7 +1142,7 @@ async function readLocalUserProfile(repoRoot: string): Promise { - await ensureTeambridgeDirs(repoRoot); + await ensureCoordDirs(repoRoot); const profilePath = getUserProfilePath(repoRoot); await writeFile(profilePath, `${JSON.stringify(profile, null, 2)}\n`); return profilePath; @@ -1188,7 +1188,7 @@ function resolveParticipantDisplayName(repoRoot: string, bodyDisplayName: string } function branchForParticipant(sessionName: string, displayName: string): string { - return `teambridge/${sessionName}/${safeDisplayName(displayName)}`; + return `coord/${sessionName}/${safeDisplayName(displayName)}`; } function upsertProjectMember( @@ -1279,14 +1279,14 @@ async function createProject( } type ConfigOverrides = { - relayMode?: TeambridgeConfig['defaultRelayMode']; + relayMode?: CoordConfig['defaultRelayMode']; }; async function initRepoConfig( repoRoot: string, overrides: ConfigOverrides = {} -): Promise<{ config: TeambridgeConfig; path: string; created: boolean; updated: boolean }> { - await ensureTeambridgeDirs(repoRoot); +): Promise<{ config: CoordConfig; path: string; created: boolean; updated: boolean }> { + await ensureCoordDirs(repoRoot); const configPath = getConfigPath(repoRoot); const existing = await readFile(configPath, 'utf8').catch((error: NodeJS.ErrnoException) => { @@ -1298,19 +1298,19 @@ async function initRepoConfig( }); if (existing) { - const current = TeambridgeConfigSchema.parse(JSON.parse(existing)); + const current = CoordConfigSchema.parse(JSON.parse(existing)); // `init` is safe to re-run: leave an existing config alone unless the caller // explicitly asked for a different relay mode, in which case update just // that field and persist. if (overrides.relayMode && overrides.relayMode !== current.defaultRelayMode) { - const next: TeambridgeConfig = { ...current, defaultRelayMode: overrides.relayMode }; + const next: CoordConfig = { ...current, defaultRelayMode: overrides.relayMode }; await writeFile(configPath, `${JSON.stringify(next, null, 2)}\n`); return { config: next, path: configPath, created: false, updated: true }; } return { config: current, path: configPath, created: false, updated: false }; } - const config: TeambridgeConfig = { + const config: CoordConfig = { ...DEFAULT_CONFIG, ...(overrides.relayMode ? { defaultRelayMode: overrides.relayMode } : {}) }; @@ -1329,7 +1329,7 @@ async function startWorkspace(state: AppState, body: StartRequestBody): Promise< } const repoRoot = getRepoRoot(resolve(body.repoRoot ?? state.defaultRepoRoot)); - await ensureTeambridgeDirs(repoRoot); + await ensureCoordDirs(repoRoot); const dbPath = initializeStateDb(repoRoot); const sessionName = body.sessionName.trim(); @@ -1788,7 +1788,7 @@ function getRemoteIdentity(repoRoot: string): RemoteIdentity | null { function requireRemoteIdentity(repoRoot: string): RemoteIdentity { const identity = getRemoteIdentity(repoRoot); if (!identity) { - throw new Error('Not logged in to Teambridge relay. Run `teambridge login` first.'); + throw new Error('Not logged in to Coord relay. Run `coord login` first.'); } return identity; } @@ -1865,7 +1865,7 @@ async function resolveActorParticipant(repoRoot: string, workspace: Workspace, a } function contextPointerPath(repoRoot: string, sessionName: string, displayName: string): string { - return join(repoRoot, '.teambridge', 'workspaces', sessionName, `.context.${safeDisplayName(displayName)}.json`); + return join(repoRoot, '.coord', 'workspaces', sessionName, `.context.${safeDisplayName(displayName)}.json`); } async function readContextPointer( @@ -1889,7 +1889,7 @@ async function writeContextPointer( repoRoot: string, pointer: ContextPointerResponse ): Promise { - const dir = join(repoRoot, '.teambridge', 'workspaces', pointer.sessionName); + const dir = join(repoRoot, '.coord', 'workspaces', pointer.sessionName); mkdirSync(dir, { recursive: true }); const path = contextPointerPath(repoRoot, pointer.sessionName, pointer.displayName); const tempPath = `${path}.tmp`; @@ -2080,7 +2080,7 @@ async function maybeMirrorWorkspaceToRelay(repoRoot: string, workspace: Workspac try { await mirrorWorkspaceToRelay(repoRoot, workspace, participant, profile); } catch (error) { - console.error(`[teambridge] relay mirror failed: ${error instanceof Error ? error.message : String(error)}`); + console.error(`[coord] relay mirror failed: ${error instanceof Error ? error.message : String(error)}`); } } @@ -2339,7 +2339,7 @@ async function mirrorDerivedEventToRelay(repoRoot: string, event: WorkspaceEvent await mirrorInboxEventToRelay(repoRoot, event); await mirrorConflictEventToRelay(repoRoot, event); } catch (error) { - console.error(`[teambridge] relay side-table mirror failed: ${error instanceof Error ? error.message : String(error)}`); + console.error(`[coord] relay side-table mirror failed: ${error instanceof Error ? error.message : String(error)}`); } } @@ -2465,7 +2465,7 @@ function startRealtimeEventSubscriber(state: AppState): void { let heartbeat: NodeJS.Timeout | undefined; let reconnect: NodeJS.Timeout | undefined; let ref = 1; - const topic = 'realtime:teambridge-events'; + const topic = 'realtime:coord-events'; const nextRef = () => String(ref++); const send = (event: string, payload: unknown, joinRef?: string, messageTopic = topic) => { @@ -2505,12 +2505,12 @@ function startRealtimeEventSubscriber(state: AppState): void { await applyRealtimeEventToKnownRepos(state, record); } } else if (parsed.event === 'system' && parsed.payload?.status === 'error') { - console.error(`[teambridge] realtime system error: ${JSON.stringify(parsed.payload)}`); + console.error(`[coord] realtime system error: ${JSON.stringify(parsed.payload)}`); } else if (parsed.event === 'phx_reply' && parsed.payload?.status === 'error') { - console.error(`[teambridge] realtime join error: ${JSON.stringify(parsed.payload)}`); + console.error(`[coord] realtime join error: ${JSON.stringify(parsed.payload)}`); } })().catch((error) => { - console.error(`[teambridge] realtime event apply failed: ${error instanceof Error ? error.message : String(error)}`); + console.error(`[coord] realtime event apply failed: ${error instanceof Error ? error.message : String(error)}`); }); }); @@ -2545,7 +2545,7 @@ function startRelayPolling(state: AppState): void { } } } catch (error) { - console.error(`[teambridge] relay poll failed: ${error instanceof Error ? error.message : String(error)}`); + console.error(`[coord] relay poll failed: ${error instanceof Error ? error.message : String(error)}`); } finally { running = false; } @@ -2850,7 +2850,7 @@ async function joinWorkspace(state: AppState, body: JoinRequestBody): Promise { - console.log(`teambridge daemon listening on http://127.0.0.1:${port}`); + console.log(`coord daemon listening on http://127.0.0.1:${port}`); console.log(`default repo: ${repoRoot}`); }); startRelayPolling({ defaultRepoRoot: repoRoot }); diff --git a/packages/daemon/src/pfp.ts b/packages/daemon/src/pfp.ts index 2ae3396..5a8abbd 100644 --- a/packages/daemon/src/pfp.ts +++ b/packages/daemon/src/pfp.ts @@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto'; import { readFile, mkdir, rm, stat, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import sharp from 'sharp'; -import { avatarStorageId } from '@teambridge/core'; +import { avatarStorageId } from '@coord/core'; export type DitherAlgorithm = 'floyd-steinberg' | 'atkinson' | 'bayer'; @@ -321,7 +321,7 @@ export function avatarNeedsRegeneration(meta: PfpMeta, expectedQuery = DEFAULT_Q } function avatarsDir(repoRoot: string): string { - return join(repoRoot, '.teambridge', 'avatars'); + return join(repoRoot, '.coord', 'avatars'); } function avatarPath(repoRoot: string, participantId: string): string { diff --git a/packages/mcp/package.json b/packages/mcp/package.json index b4ddce7..78688b8 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,5 +1,5 @@ { - "name": "@teambridge/mcp", + "name": "@coord/mcp", "version": "0.0.0", "private": true, "main": "dist/index.js", @@ -10,7 +10,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", - "@teambridge/core": "workspace:*", + "@coord/core": "workspace:*", "zod": "^4.4.3" } } diff --git a/packages/mcp/src/daemon-client.ts b/packages/mcp/src/daemon-client.ts index 911fff7..763c047 100644 --- a/packages/mcp/src/daemon-client.ts +++ b/packages/mcp/src/daemon-client.ts @@ -15,11 +15,11 @@ import type { WorkspaceEvent, WorkspaceListResponse, WorkspaceStatusResponse -} from '@teambridge/core'; -import { buildDaemonUrl } from '@teambridge/core'; +} from '@coord/core'; +import { buildDaemonUrl } from '@coord/core'; -export { DEFAULT_DAEMON_BASE_URL, buildDaemonUrl } from '@teambridge/core'; -export type { DaemonClientOptions, DaemonQueryParams } from '@teambridge/core'; +export { DEFAULT_DAEMON_BASE_URL, buildDaemonUrl } from '@coord/core'; +export type { DaemonClientOptions, DaemonQueryParams } from '@coord/core'; const DEFAULT_TIMEOUT_MS = 10_000; diff --git a/packages/mcp/src/resolution.ts b/packages/mcp/src/resolution.ts index 004436a..cf716a8 100644 --- a/packages/mcp/src/resolution.ts +++ b/packages/mcp/src/resolution.ts @@ -11,7 +11,7 @@ export type ResolutionInput = { }; export async function resolveWorkspaceContext(input: ResolutionInput): Promise { - const baseUrl = input.baseUrl ?? process.env.TEAMBRIDGE_DAEMON_URL; + const baseUrl = input.baseUrl ?? process.env.COORD_DAEMON_URL; // 1. Explicit params if (input.workspaceId || input.sessionName) { @@ -23,11 +23,11 @@ export async function resolveWorkspaceContext(input: ResolutionInput): Promise> { if (!isMcpResourceName(name)) { - return apiFail('NOT_FOUND', `Unknown Teambridge MCP resource: ${name}`); + return apiFail('NOT_FOUND', `Unknown Coord MCP resource: ${name}`); } const workspaceId = resolveWorkspaceId(context); if (!workspaceId) { - return apiFail('INVALID_REQUEST', 'workspaceId or sessionName is required to resolve Teambridge MCP resources'); + return apiFail('INVALID_REQUEST', 'workspaceId or sessionName is required to resolve Coord MCP resources'); } - if (name === 'teambridge://workspace') { + if (name === 'coord://workspace') { const statusResult = await reader.getWorkspaceStatus(workspaceId, context); if (!statusResult.ok) return statusResult; @@ -79,16 +79,16 @@ export async function resolveMcpResource( return apiOk(statusResult.data); } - if (name === 'teambridge://participants') { + if (name === 'coord://participants') { const status = await reader.getWorkspaceStatus(workspaceId, context); return status.ok ? apiOk({ participants: status.data.participants }) : status; } - if (name === 'teambridge://vault/context') { + if (name === 'coord://vault/context') { return reader.getVaultContext(workspaceId, context); } - if (name === 'teambridge://inbox') { + if (name === 'coord://inbox') { return reader.listInbox(workspaceId, context); } diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index dc2b829..9564282 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -5,7 +5,7 @@ import { resolveMcpResource, type McpResourceContext } from './resources'; import { resolveWorkspaceContext } from './resolution'; import { askInbox, getWorkspaceStatus, publishEvent, readVaultFile, replyInbox, searchVault } from './daemon-client'; -const SERVER_NAME = 'teambridge'; +const SERVER_NAME = 'coord'; const SERVER_VERSION = '0.1.0'; export function createServer(): McpServer { @@ -15,61 +15,61 @@ export function createServer(): McpServer { server.registerResource( 'workspace', - 'teambridge://workspace', + 'coord://workspace', { title: 'Workspace', description: 'Current workspace status with participants, worktrees, and relay sync state', mimeType: 'application/json' }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); - const result = await resolveMcpResource('teambridge://workspace', ctx); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); + const result = await resolveMcpResource('coord://workspace', ctx); if (!result.ok) throw new Error(result.error.message); - return { contents: [{ uri: 'teambridge://workspace', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; + return { contents: [{ uri: 'coord://workspace', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; } ); server.registerResource( 'participants', - 'teambridge://participants', + 'coord://participants', { title: 'Participants', description: 'List of participants on the current track with status and presence', mimeType: 'application/json' }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); - const result = await resolveMcpResource('teambridge://participants', ctx); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); + const result = await resolveMcpResource('coord://participants', ctx); if (!result.ok) throw new Error(result.error.message); - return { contents: [{ uri: 'teambridge://participants', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; + return { contents: [{ uri: 'coord://participants', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; } ); server.registerResource( 'vault-context', - 'teambridge://vault/context', + 'coord://vault/context', { title: 'Vault Context', description: 'Concatenated vault context for the current track', mimeType: 'application/json' }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); - const result = await resolveMcpResource('teambridge://vault/context', ctx); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); + const result = await resolveMcpResource('coord://vault/context', ctx); if (!result.ok) throw new Error(result.error.message); - return { contents: [{ uri: 'teambridge://vault/context', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; + return { contents: [{ uri: 'coord://vault/context', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; } ); server.registerResource( 'inbox', - 'teambridge://inbox', + 'coord://inbox', { title: 'Inbox', description: 'Team inbox messages on the current track', mimeType: 'application/json' }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); - const result = await resolveMcpResource('teambridge://inbox', ctx); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); + const result = await resolveMcpResource('coord://inbox', ctx); if (!result.ok) throw new Error(result.error.message); - return { contents: [{ uri: 'teambridge://inbox', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; + return { contents: [{ uri: 'coord://inbox', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; } ); server.registerResource( 'conflicts', - 'teambridge://conflicts', + 'coord://conflicts', { title: 'Conflicts', description: 'Detected conflicts on the current track', mimeType: 'application/json' }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); - const result = await resolveMcpResource('teambridge://conflicts', ctx); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); + const result = await resolveMcpResource('coord://conflicts', ctx); if (!result.ok) throw new Error(result.error.message); - return { contents: [{ uri: 'teambridge://conflicts', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; + return { contents: [{ uri: 'coord://conflicts', mimeType: 'application/json', text: JSON.stringify(result.data) }] }; } ); @@ -86,7 +86,7 @@ export function createServer(): McpServer { } }, async ({ targetFile, text }) => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for publish'); const result = await publishEvent(wsId, { targetFile, payload: { text } }, ctx); @@ -106,7 +106,7 @@ export function createServer(): McpServer { } }, async ({ query, limit }) => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for search'); const result = await searchVault(wsId, query, ctx, limit); @@ -125,7 +125,7 @@ export function createServer(): McpServer { } }, async ({ path }) => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for read'); const result = await readVaultFile(wsId, path, ctx); @@ -142,7 +142,7 @@ export function createServer(): McpServer { inputSchema: {} }, async () => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for status'); const result = await getWorkspaceStatus(wsId, ctx); @@ -162,7 +162,7 @@ export function createServer(): McpServer { } }, async ({ to, text }) => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for ask'); const result = await askInbox(wsId, { to, text }, ctx); @@ -182,7 +182,7 @@ export function createServer(): McpServer { } }, async ({ messageId, text }) => { - const ctx = await resolveWorkspaceContext({ repoRoot: process.env.TEAMBRIDGE_REPO_ROOT, baseUrl: process.env.TEAMBRIDGE_DAEMON_URL }); + const ctx = await resolveWorkspaceContext({ repoRoot: process.env.COORD_REPO_ROOT, baseUrl: process.env.COORD_DAEMON_URL }); const wsId = ctx.workspaceId ?? ctx.sessionName; if (!wsId) throw new Error('Unable to resolve workspace for reply'); const result = await replyInbox(wsId, messageId, { text }, ctx); diff --git a/packages/mcp/src/tools.ts b/packages/mcp/src/tools.ts index 37618ea..9c29583 100644 --- a/packages/mcp/src/tools.ts +++ b/packages/mcp/src/tools.ts @@ -1,4 +1,4 @@ -import { MCP_TOOL_NAMES as CORE_MCP_TOOL_NAMES } from '@teambridge/core'; +import { MCP_TOOL_NAMES as CORE_MCP_TOOL_NAMES } from '@coord/core'; /** Stub registry — HTTP MCP server wiring is Phase 3; names match contracts. */ export const MCP_TOOL_NAMES = CORE_MCP_TOOL_NAMES; diff --git a/packages/mcp/test/integration.test.cjs b/packages/mcp/test/integration.test.cjs index 52ac48f..3ce0824 100644 --- a/packages/mcp/test/integration.test.cjs +++ b/packages/mcp/test/integration.test.cjs @@ -11,11 +11,11 @@ const SERVER_SCRIPT = path.resolve(__dirname, '../dist/server.js'); // All 5 resource URIs registered by the server. const EXPECTED_RESOURCE_URIS = [ - 'teambridge://workspace', - 'teambridge://participants', - 'teambridge://vault/context', - 'teambridge://inbox', - 'teambridge://conflicts' + 'coord://workspace', + 'coord://participants', + 'coord://vault/context', + 'coord://inbox', + 'coord://conflicts' ]; // All 6 tool names registered by the server. @@ -35,7 +35,7 @@ const EXPECTED_TOOL_NAMES = [ function createMcpServer() { const child = spawn('node', [SERVER_SCRIPT], { cwd: REPO_ROOT, - env: { ...process.env, TEAMBRIDGE_REPO_ROOT: os.tmpdir() }, + env: { ...process.env, COORD_REPO_ROOT: os.tmpdir() }, stdio: ['pipe', 'pipe', 'pipe'] }); @@ -94,7 +94,7 @@ async function bootServer() { capabilities: {}, clientInfo: { name: 'test', version: '0.1' } }); - assert.equal(init.result.serverInfo.name, 'teambridge'); + assert.equal(init.result.serverInfo.name, 'coord'); server.notify('notifications/initialized', {}); return server; } catch (err) { @@ -103,7 +103,7 @@ async function bootServer() { } } -test('initialize handshake returns teambridge server info', async () => { +test('initialize handshake returns coord server info', async () => { const server = createMcpServer(); try { const result = await server.request('initialize', { @@ -111,7 +111,7 @@ test('initialize handshake returns teambridge server info', async () => { capabilities: {}, clientInfo: { name: 'test', version: '0.1' } }); - assert.equal(result.result.serverInfo.name, 'teambridge'); + assert.equal(result.result.serverInfo.name, 'coord'); server.notify('notifications/initialized', {}); } finally { server.kill(); diff --git a/packages/mcp/test/resolution.test.cjs b/packages/mcp/test/resolution.test.cjs index 6e477cd..c690e0d 100644 --- a/packages/mcp/test/resolution.test.cjs +++ b/packages/mcp/test/resolution.test.cjs @@ -33,11 +33,11 @@ test('explicit sessionName returns with that session name', async () => { assert.equal(result.workspaceId, undefined); }); -test('.teambridge/.active containing "billing-refactor\\n" resolves to that session name', async () => { +test('.coord/.active containing "billing-refactor\\n" resolves to that session name', async () => { const dir = makeTempRepo(); try { - mkdirSync(join(dir, '.teambridge'), { recursive: true }); - writeFileSync(join(dir, '.teambridge', '.active'), 'billing-refactor\n'); + mkdirSync(join(dir, '.coord'), { recursive: true }); + writeFileSync(join(dir, '.coord', '.active'), 'billing-refactor\n'); const result = await resolveWorkspaceContext({ repoRoot: dir }); assert.equal(result.sessionName, 'billing-refactor'); assert.equal(result.workspaceId, undefined); @@ -47,11 +47,11 @@ test('.teambridge/.active containing "billing-refactor\\n" resolves to that sess } }); -test('.teambridge/.active that is empty/whitespace falls through to error', async () => { +test('.coord/.active that is empty/whitespace falls through to error', async () => { const dir = makeTempRepo(); try { - mkdirSync(join(dir, '.teambridge'), { recursive: true }); - writeFileSync(join(dir, '.teambridge', '.active'), ' \n\t\n'); + mkdirSync(join(dir, '.coord'), { recursive: true }); + writeFileSync(join(dir, '.coord', '.active'), ' \n\t\n'); await assert.rejects( () => resolveWorkspaceContext({ repoRoot: dir }), /Unable to resolve workspace/ @@ -61,7 +61,7 @@ test('.teambridge/.active that is empty/whitespace falls through to error', asyn } }); -test('no params and no .teambridge/.active file throws an error', async () => { +test('no params and no .coord/.active file throws an error', async () => { const dir = makeTempRepo(); try { await assert.rejects( @@ -73,11 +73,11 @@ test('no params and no .teambridge/.active file throws an error', async () => { } }); -test('.teambridge/.active with trailing newline is trimmed correctly', async () => { +test('.coord/.active with trailing newline is trimmed correctly', async () => { const dir = makeTempRepo(); try { - mkdirSync(join(dir, '.teambridge'), { recursive: true }); - writeFileSync(join(dir, '.teambridge', '.active'), 'feature-x\n\n'); + mkdirSync(join(dir, '.coord'), { recursive: true }); + writeFileSync(join(dir, '.coord', '.active'), 'feature-x\n\n'); const result = await resolveWorkspaceContext({ repoRoot: dir }); assert.equal(result.sessionName, 'feature-x'); } finally { diff --git a/packages/mcp/test/resources.test.cjs b/packages/mcp/test/resources.test.cjs index 5a745c0..6e343ff 100644 --- a/packages/mcp/test/resources.test.cjs +++ b/packages/mcp/test/resources.test.cjs @@ -34,7 +34,7 @@ const workspaceStatus = { id: 'user_ronish', displayName: 'ronish', workspaceId: 'ws_123', - branch: 'teambridge/billing-refactor/ronish', + branch: 'coord/billing-refactor/ronish', agent: 'cursor', status: 'active', lastSeenAt: createdAt @@ -116,14 +116,14 @@ const relayStatus = { test('resource registry matches core MCP resource contract', () => { assert.deepEqual(MCP_RESOURCE_NAMES, [ - 'teambridge://workspace', - 'teambridge://participants', - 'teambridge://vault/context', - 'teambridge://inbox', - 'teambridge://conflicts' + 'coord://workspace', + 'coord://participants', + 'coord://vault/context', + 'coord://inbox', + 'coord://conflicts' ]); - assert.equal(isMcpResourceName('teambridge://workspace'), true); - assert.equal(isMcpResourceName('teambridge://missing'), false); + assert.equal(isMcpResourceName('coord://workspace'), true); + assert.equal(isMcpResourceName('coord://missing'), false); }); test('daemon URL builder includes repoRoot and encoded params', () => { @@ -142,14 +142,14 @@ test('daemon URL builder includes repoRoot and encoded params', () => { }); test('workspace and participant resources resolve through workspace status', async () => { - const workspace = await resolveMcpResource('teambridge://workspace', { workspaceId: 'ws_123' }, reader); + const workspace = await resolveMcpResource('coord://workspace', { workspaceId: 'ws_123' }, reader); assert.equal(workspace.ok, true); assert.deepEqual(workspace.data.workspace, workspaceStatus.workspace); assert.deepEqual(workspace.data.participants, workspaceStatus.participants); assert.equal(workspace.data.lastSeq, workspaceStatus.lastSeq); assert.deepEqual(workspace.data.relayStatus, relayStatus); - const participants = await resolveMcpResource('teambridge://participants', { workspaceId: 'ws_123' }, reader); + const participants = await resolveMcpResource('coord://participants', { workspaceId: 'ws_123' }, reader); assert.deepEqual(participants, { ok: true, data: { participants: workspaceStatus.participants } }); }); @@ -173,7 +173,7 @@ test('resources can use sessionName as the daemon workspace identifier', async ( } }; - const workspace = await resolveMcpResource('teambridge://workspace', { sessionName: 'billing-refactor' }, sessionReader); + const workspace = await resolveMcpResource('coord://workspace', { sessionName: 'billing-refactor' }, sessionReader); assert.equal(workspace.ok, true); assert.deepEqual(workspace.data.workspace, workspaceStatus.workspace); assert.equal(workspace.data.relayStatus, undefined); @@ -205,7 +205,7 @@ test('participants resource propagates workspace status failures', async () => { } }; - const participants = await resolveMcpResource('teambridge://participants', { workspaceId: 'ws_missing' }, failingReader); + const participants = await resolveMcpResource('coord://participants', { workspaceId: 'ws_missing' }, failingReader); assert.deepEqual(participants, fail); }); @@ -235,29 +235,29 @@ test('daemon fetch wrappers construct endpoints and preserve ApiResult envelopes }); test('vault, inbox, conflict, and unknown resources have stable behavior', async () => { - const vault = await resolveMcpResource('teambridge://vault/context', { workspaceId: 'ws_123' }, reader); + const vault = await resolveMcpResource('coord://vault/context', { workspaceId: 'ws_123' }, reader); assert.equal(vault.ok, true); assert.deepEqual(vault.data.context.includedPaths, ['decisions.md']); - const inbox = await resolveMcpResource('teambridge://inbox', { workspaceId: 'ws_123' }, reader); + const inbox = await resolveMcpResource('coord://inbox', { workspaceId: 'ws_123' }, reader); assert.deepEqual(inbox, { ok: true, data: { messages: [inboxMessage] } }); - const conflicts = await resolveMcpResource('teambridge://conflicts', { workspaceId: 'ws_123' }, reader); + const conflicts = await resolveMcpResource('coord://conflicts', { workspaceId: 'ws_123' }, reader); assert.deepEqual(conflicts, { ok: true, data: { conflicts: [conflict] } }); - const missing = await resolveMcpResource('teambridge://missing', { workspaceId: 'ws_123' }, reader); + const missing = await resolveMcpResource('coord://missing', { workspaceId: 'ws_123' }, reader); assert.equal(missing.ok, false); assert.equal(missing.error.code, 'NOT_FOUND'); }); test('resources require workspace context', async () => { - const result = await resolveMcpResource('teambridge://workspace', {}, reader); + const result = await resolveMcpResource('coord://workspace', {}, reader); assert.equal(result.ok, false); assert.equal(result.error.code, 'INVALID_REQUEST'); }); test('workspace resource includes relay status when relay is configured', async () => { - const result = await resolveMcpResource('teambridge://workspace', { workspaceId: 'ws_123' }, reader); + const result = await resolveMcpResource('coord://workspace', { workspaceId: 'ws_123' }, reader); assert.equal(result.ok, true); assert.deepEqual(result.data.relayStatus, relayStatus); }); @@ -281,7 +281,7 @@ test('workspace resource degrades gracefully when relay is not configured', asyn } }; - const result = await resolveMcpResource('teambridge://workspace', { workspaceId: 'ws_123' }, noRelayReader); + const result = await resolveMcpResource('coord://workspace', { workspaceId: 'ws_123' }, noRelayReader); assert.equal(result.ok, true); assert.deepEqual(result.data.workspace, workspaceStatus.workspace); assert.equal(result.data.relayStatus, undefined); @@ -308,7 +308,7 @@ test('workspace resource returns error when workspace status fails, without fetc } }; - const result = await resolveMcpResource('teambridge://workspace', { workspaceId: 'ws_missing' }, errorReader); + const result = await resolveMcpResource('coord://workspace', { workspaceId: 'ws_missing' }, errorReader); assert.equal(result.ok, false); assert.equal(result.error.code, 'WORKSPACE_NOT_FOUND'); assert.equal(relayCalled, false); diff --git a/packages/vault/package.json b/packages/vault/package.json index 722721b..51d8fcc 100644 --- a/packages/vault/package.json +++ b/packages/vault/package.json @@ -1,5 +1,5 @@ { - "name": "@teambridge/vault", + "name": "@coord/vault", "version": "0.0.0", "private": true, "main": "dist/index.js", @@ -8,6 +8,6 @@ "build": "tsc -p tsconfig.json" }, "dependencies": { - "@teambridge/core": "workspace:*" + "@coord/core": "workspace:*" } } diff --git a/packages/vault/src/index.ts b/packages/vault/src/index.ts index d3234e6..fe64cf2 100644 --- a/packages/vault/src/index.ts +++ b/packages/vault/src/index.ts @@ -1,12 +1,12 @@ import { appendFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import type { Conflict, PhaseOneVaultFile, PublishEventPayload, VaultContext, VaultFile, VaultItemAnnotation, VaultItemMeta, WorkspaceEvent } from '@teambridge/core'; +import type { Conflict, PhaseOneVaultFile, PublishEventPayload, VaultContext, VaultFile, VaultItemAnnotation, VaultItemMeta, WorkspaceEvent } from '@coord/core'; import { extractVaultAnnotations, parseVaultListLine, reapplyVaultAnnotations, updateVaultFileItemMeta -} from '@teambridge/core'; +} from '@coord/core'; export const PHASE_ONE_VAULT_FILES: PhaseOneVaultFile[] = [ 'README.md', @@ -19,7 +19,7 @@ export const PHASE_ONE_VAULT_FILES: PhaseOneVaultFile[] = [ ]; const INITIAL_CONTENT: Record = { - 'README.md': '# Teambridge Vault\n\nThis flat vault is generated from `events.jsonl`.\n', + 'README.md': '# Coord Vault\n\nThis flat vault is generated from `events.jsonl`.\n', 'decisions.md': '# Decisions\n', 'observations.md': '# Observations\n', 'blockers.md': '# Blockers\n', @@ -75,7 +75,7 @@ export async function materializePublishEvent(vaultDir: string, event: Workspace throw new Error('README.md is not a publish target'); } if (targetFile === 'conflicts.md') { - throw new Error('conflicts.md is managed by Teambridge and is not a publish target'); + throw new Error('conflicts.md is managed by Coord and is not a publish target'); } await initializePhaseOneVault(vaultDir); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36fd0cf..0103202 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@base-ui/react': specifier: ^1.6.0 version: 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@coord/core': + specifier: workspace:* + version: link:../../packages/core '@fontsource-variable/jetbrains-mono': specifier: ^5.2.8 version: 5.2.8 @@ -35,9 +38,6 @@ importers: '@tabler/icons-react': specifier: ^3.44.0 version: 3.44.0(react@19.2.7) - '@teambridge/core': - specifier: workspace:* - version: link:../../packages/core class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -96,10 +96,10 @@ importers: packages/cli: dependencies: - '@teambridge/core': + '@coord/core': specifier: workspace:* version: link:../core - '@teambridge/mcp': + '@coord/mcp': specifier: workspace:* version: link:../mcp @@ -111,10 +111,10 @@ importers: packages/daemon: dependencies: - '@teambridge/core': + '@coord/core': specifier: workspace:* version: link:../core - '@teambridge/vault': + '@coord/vault': specifier: workspace:* version: link:../vault dotenv: @@ -129,19 +129,19 @@ importers: packages/mcp: dependencies: + '@coord/core': + specifier: workspace:* + version: link:../core '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) - '@teambridge/core': - specifier: workspace:* - version: link:../core zod: specifier: ^4.4.3 version: 4.4.3 packages/vault: dependencies: - '@teambridge/core': + '@coord/core': specifier: workspace:* version: link:../core @@ -457,89 +457,105 @@ packages: resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.3.1': resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.3.1': resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.3.1': resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.3.1': resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.3.1': resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.3.1': resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.3.1': resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.35.2': resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.35.2': resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.35.2': resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.35.2': resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.35.2': resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.35.2': resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.35.2': resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.35.2': resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.35.2': resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} @@ -650,36 +666,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.1.3': resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.1.3': resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.1.3': resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.1.3': resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.1.3': resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.1.3': resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} @@ -763,24 +785,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.1': resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.1': resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.1': resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.1': resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==} @@ -1633,24 +1659,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} diff --git a/report/phase-2-supabase-relay-plan.md b/report/phase-2-supabase-relay-plan.md index 94db7d0..97e84ae 100644 --- a/report/phase-2-supabase-relay-plan.md +++ b/report/phase-2-supabase-relay-plan.md @@ -12,15 +12,15 @@ Implemented: - `tc_` tables, indexes, RLS helpers/policies, checkpoint storage bucket, and Realtime publication. - `tc_append_event` RPC for canonical per-workspace `seq` assignment and `dedupeKey` dedupe. - Daemon Supabase relay client using REST/RPC. -- Minimal `teambridge login` auth flow with local daemon identity storage. +- Minimal `coord login` auth flow with local daemon identity storage. - Device registration in `tc_devices`. - Project/session/participant mirroring to Supabase when logged in. -- Remote publish path from `teambridge publish` through daemon to Supabase. +- Remote publish path from `coord publish` through daemon to Supabase. - Local `pending_remote_events` queue for failed remote appends. -- Manual `teambridge sync` and autonomous polling via `TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS`. +- Manual `coord sync` and autonomous polling via `COORD_RELAY_SYNC_INTERVAL_MS`. - Pull-after-last-remote-seq and local vault rematerialization from canonical remote events. -- Remote session discovery through `teambridge sessions` / `teambridge list`. -- `teambridge join ` can import a remote workspace and then create the local Git worktree. +- Remote session discovery through `coord sessions` / `coord list`. +- `coord join ` can import a remote workspace and then create the local Git worktree. - Dashboard calls daemon `/relay/sessions` and merges remote relay sessions with local sessions. - Supabase Realtime websocket subscription in the daemon, with polling/manual sync kept as fallback. - Checkpoint upload/download using `tc_workspace_vault_checkpoints` and `teambridge-checkpoints`. @@ -34,7 +34,7 @@ Verified: - `teambridge-checkpoints` storage bucket is reachable. - `tc_append_event` assigns `seq = 1, 2...` and dedupes duplicate `dedupeKey` values. - Live CLI/daemon relay smoke passed: login, start, sessions/list, sync, status relay. -- Live `teambridge publish` reached Supabase with canonical `seq = 1` and the expected payload. +- Live `coord publish` reached Supabase with canonical `seq = 1` and the expected payload. - Live two-user verification with `nihal@test.com` and `kush@test.com` passed: sessions, join, checkpoint bootstrap, realtime receive, presence, and remote events. - Local verification still passes: `pnpm build`, `pnpm test`, `pnpm test:integration`, dashboard test/build. @@ -50,14 +50,14 @@ This plan started as the Phase 2 backend blueprint. The migration has now been a Current Phase 1 command surface is: ```bash -teambridge start [base_ref] -teambridge join --as -teambridge enter -teambridge publish -teambridge vault read|search|context +coord start [base_ref] +coord join --as +coord enter +coord publish +coord vault read|search|context ``` -Do not bring back `teambridge track start` / `teambridge track join` as user-facing commands. Internally the dashboard and database may still use the word "track", but CLI users should think in sessions. +Do not bring back `coord track start` / `coord track join` as user-facing commands. Internally the dashboard and database may still use the word "track", but CLI users should think in sessions. For auth: Phase 2 needs minimal Supabase Auth so RLS can protect relay data. It does **not** need polished auth UX yet. Defer invites, orgs, GitHub OAuth polish, dashboard login UI, keychain storage, and role management until after cross-device sync works. @@ -119,12 +119,12 @@ Use Supabase Auth for real users, but keep it minimal in Phase 2. Recommended Phase 2 dev flow: ```bash -teambridge login --email --password -teambridge init -teambridge start billing-refactor main -teambridge sessions -teambridge join billing-refactor -teambridge sync +coord login --email --password +coord init +coord start billing-refactor main +coord sessions +coord join billing-refactor +coord sync ``` Daemon auth rules: @@ -146,10 +146,10 @@ SUPABASE_REST_URL= SUPABASE_ANON_KEY= SUPABASE_SERVICE_ROLE_KEY= SUPABASE_JWT_SECRET= -TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS=5000 -TEAMBRIDGE_RELAY_PRESENCE_INTERVAL_MS=15000 -TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS=50 -TEAMBRIDGE_CHECKPOINT_LEASE_MS=60000 +COORD_RELAY_SYNC_INTERVAL_MS=5000 +COORD_RELAY_PRESENCE_INTERVAL_MS=15000 +COORD_CHECKPOINT_INTERVAL_EVENTS=50 +COORD_CHECKPOINT_LEASE_MS=60000 ``` For the final app, prefer publishable/anon key for user auth and keep `SUPABASE_SERVICE_ROLE_KEY` restricted to trusted local daemon or server-side relay operations only. @@ -158,7 +158,7 @@ For the final app, prefer publishable/anon key for user auth and keep `SUPABASE_ Use `public` for exposed tables and enable RLS on every table. Put helper functions that need elevated privileges in a private schema. -All Teambridge-owned Supabase tables use the `tc_` prefix. This is the SQL-safe version of the "TC_" naming convention: Postgres folds unquoted identifiers to lowercase, so writing literal uppercase `"TC_"` would force every query to quote table names forever. +All Coord-owned Supabase tables use the `tc_` prefix. This is the SQL-safe version of the "TC_" naming convention: Postgres folds unquoted identifiers to lowercase, so writing literal uppercase `"TC_"` would force every query to quote table names forever. ```sql create schema if not exists teambridge_private; @@ -402,7 +402,7 @@ create index tc_checkpoint_leases_expires_idx ### Inbox Messages -Used later by `teambridge ask`, `teambridge inbox`, `teambridge reply`, and MCP tools. +Used later by `coord ask`, `coord inbox`, `coord reply`, and MCP tools. ```sql create table public.tc_inbox_messages ( @@ -812,7 +812,7 @@ For production, move tokens to OS keychain later. For Phase 2 dev, local file/SQ Online happy path: ```text -teambridge publish decisions.md "..." +coord publish decisions.md "..." -> CLI calls local daemon -> daemon validates targetFile/payload -> daemon creates dedupeKey @@ -826,7 +826,7 @@ teambridge publish decisions.md "..." Offline path: ```text -teambridge publish blockers.md "..." +coord publish blockers.md "..." -> daemon cannot reach relay -> daemon writes pending_remote_events row -> daemon may optimistically materialize pending publish locally @@ -902,7 +902,7 @@ Storage object must be private. Members get signed URLs through daemon/relay, or When Ronish joins an existing relay workspace: ```text -teambridge join billing-refactor +coord join billing-refactor -> daemon authenticates user -> fetch workspace manifest + participants -> create participant row if needed diff --git a/report/team-implementation-plan.md b/report/team-implementation-plan.md index 8039fd3..0b8862c 100644 --- a/report/team-implementation-plan.md +++ b/report/team-implementation-plan.md @@ -1,12 +1,12 @@ -# Teambridge Team Implementation Plan +# Coord Team Implementation Plan -This document is the execution plan for the first Teambridge build. `agent.md` is the product vision and long-lived agent guide; this file is the team plan for Nihal, Kushagra, and Ronish. +This document is the execution plan for the first Coord build. `agent.md` is the product vision and long-lived agent guide; this file is the team plan for Nihal, Kushagra, and Ronish. ## TL;DR -- Build Teambridge contract-first. Shared schemas live in `packages/core/src/contracts/` and every package imports from there. +- Build Coord contract-first. Shared schemas live in `packages/core/src/contracts/` and every package imports from there. - Nihal handles backend/core: daemon, local state, Supabase relay, event ordering, checkpoints, vault materialization, conflicts. -- Kushagra handles CLI and agent UX: `teambridge` commands, Claude Code hooks, auto-injection, inbox commands, terminal workflows. +- Kushagra handles CLI and agent UX: `coord` commands, Claude Code hooks, auto-injection, inbox commands, terminal workflows. - Ronish handles MCP and dashboard: MCP resources/tools, dashboard views, inbox/conflict UI, workspace visibility. - Keep the first version local-first. Supabase is the relay and bootstrap layer, not where agents directly reason. - Use one workspace vault. No personal/team/task vault split. @@ -15,13 +15,13 @@ This document is the execution plan for the first Teambridge build. `agent.md` i ## Product Shape -Teambridge creates a shared coding session around one repo/task. +Coord creates a shared coding session around one repo/task. ```bash -teambridge init -teambridge start [base_ref] -teambridge join -teambridge enter +coord init +coord start [base_ref] +coord join +coord enter ``` `start` records an immutable `base_commit`. Every joiner gets their own worktree/branch from that same commit. The shared context is not the code folder. The shared context is an event-sourced workspace vault that every teammate materializes locally. @@ -60,7 +60,7 @@ Responsible for: Primary output: -- A `teambridge` CLI that makes start/join/status/ask/inbox/vault flows feel simple and requires no per-session flags in normal use. +- A `coord` CLI that makes start/join/status/ask/inbox/vault flows feel simple and requires no per-session flags in normal use. ### Ronish: MCP/Dashboard @@ -90,7 +90,7 @@ Responsible for: Primary output: -- Agents can use Teambridge through MCP, and humans can inspect workspace state through a local dashboard. +- Agents can use Coord through MCP, and humans can inspect workspace state through a local dashboard. See also: [docs/CONCEPTS.md](../docs/CONCEPTS.md), [docs/daemon-api.md](../docs/daemon-api.md), [docs/dashboard.md](../docs/dashboard.md). @@ -138,7 +138,7 @@ Rules: ```text packages/ ├── core/ # contracts, shared helpers, validation -├── cli/ # teambridge command +├── cli/ # coord command ├── daemon/ # local runtime authority ├── mcp/ # HTTP MCP server └── vault/ # event -> vault materializer @@ -154,7 +154,7 @@ apps/ Command: ```bash -teambridge start [base_ref] +coord start [base_ref] ``` Behavior: @@ -172,7 +172,7 @@ Behavior: Command: ```bash -teambridge join +coord join ``` Behavior: @@ -200,10 +200,10 @@ Required behavior: Phase 1 should use one user-published event type: `publish`. The CLI writes to a vault file chosen by the user/agent: ```bash -teambridge publish decisions.md "Backend is the source of truth for invoice state." -teambridge publish observations.md "Frontend reads derived totals from the invoice API." -teambridge publish blockers.md "Need refresh-token behavior decided before UI retry logic." -teambridge publish test-results.md "pnpm test passed for billing package." +coord publish decisions.md "Backend is the source of truth for invoice state." +coord publish observations.md "Frontend reads derived totals from the invoice API." +coord publish blockers.md "Need refresh-token behavior decided before UI retry logic." +coord publish test-results.md "pnpm test passed for billing package." ``` Phase 1 keeps the vault intentionally flat: @@ -227,7 +227,7 @@ The vault is a readable local projection of ordered events. Default files: ```text -.teambridge/workspaces/{session_name}/vault/ +.coord/workspaces/{session_name}/vault/ ├── README.md ├── decisions.md ├── observations.md @@ -355,11 +355,11 @@ MCP is the primary agent-facing API. Resources: ```text -teambridge://workspace -teambridge://participants -teambridge://vault/context -teambridge://inbox -teambridge://conflicts +coord://workspace +coord://participants +coord://vault/context +coord://inbox +coord://conflicts ``` Tools: @@ -378,7 +378,7 @@ Workspace resolution must be explicit and deterministic: - Prefer explicit query params or configured workspace ID. - Accept headers from hook/agent wrapper when available. - Use daemon `state.sqlite` to map CWD/worktree path to workspace. -- Use `.teambridge/.active` only as fallback. +- Use `.coord/.active` only as fallback. ## Supabase Contract @@ -427,7 +427,7 @@ Execution order: - Kushagra adds `packages/cli` and CLI parser skeleton. - Ronish adds `packages/mcp` and `apps/dashboard` skeletons. - Step 3, Nihal first: make the local daemon real enough for other packages to call it: health, config discovery, local SQLite, local workspace store, and contract validation/tests. -- Step 4, Kushagra after daemon health/config exist: implement `teambridge init`, `teambridge status`, `teambridge ws show`, `teambridge ws who`, `teambridge ws branches`, and CLI-to-daemon wiring. +- Step 4, Kushagra after daemon health/config exist: implement `coord init`, `coord status`, `coord ws show`, `coord ws who`, `coord ws branches`, and CLI-to-daemon wiring. - Step 5, Nihal + Kushagra in parallel: - Nihal implements daemon workspace create/join APIs and workspace manifest persistence. - Kushagra implements `start`, `join`, `enter`, base commit resolution, and worktree creation. @@ -439,14 +439,14 @@ Execution order: Pass example: ```bash -teambridge init -teambridge start billing-refactor main -teambridge join billing-refactor --as kushagra -teambridge join billing-refactor --as ronish -teambridge publish decisions.md "Backend is the source of truth for invoice state." -teambridge vault read decisions.md -teambridge vault search "invoice state" -teambridge vault context +coord init +coord start billing-refactor main +coord join billing-refactor --as kushagra +coord join billing-refactor --as ronish +coord publish decisions.md "Backend is the source of truth for invoice state." +coord vault read decisions.md +coord vault search "invoice state" +coord vault context ``` Pass when: @@ -481,20 +481,20 @@ Pass example: ```bash # Device A, Nihal -teambridge start billing-refactor main -teambridge publish observations.md "Refresh endpoint retries forever when token refresh fails." +coord start billing-refactor main +coord publish observations.md "Refresh endpoint retries forever when token refresh fails." # Device B, Kushagra -teambridge join billing-refactor -teambridge vault search "Refresh endpoint" +coord join billing-refactor +coord vault search "Refresh endpoint" # Device A goes offline, publishes locally, then reconnects. -teambridge publish blockers.md "Need backend decision before changing retry UI." -teambridge status +coord publish blockers.md "Need backend decision before changing retry UI." +coord status # Device C, Ronish, joins late. -teambridge join billing-refactor -teambridge vault read observations.md +coord join billing-refactor +coord vault read observations.md ``` Pass when: @@ -507,7 +507,7 @@ Pass when: ### Phase 3: Agent UX, MCP, Inbox, and Dashboard -Goal: agents and humans can use Teambridge naturally through hooks, MCP, inbox, and dashboard without per-session flags. +Goal: agents and humans can use Coord naturally through hooks, MCP, inbox, and dashboard without per-session flags. Execution order: @@ -517,15 +517,15 @@ Execution order: - Kushagra upgrades the Phase 1 context endpoint into smarter compact context UX, then wires Claude Code hook auto-injection and delta injection. - Step 3, Ronish + Kushagra in parallel after inbox endpoints exist: - Ronish implements MCP tools: `team_publish`, `team_ask`, `team_reply`, `vault_search`, `vault_read`, `workspace_status`. - - Kushagra implements `teambridge ask`, `teambridge inbox`, `teambridge reply`, and unread/pending question UX. + - Kushagra implements `coord ask`, `coord inbox`, `coord reply`, and unread/pending question UX. - Step 4, Ronish after dashboard APIs are stable: implement dashboard workspace, participants, branches, presence, inbox, conflicts, and vault highlights. - Step 5, Nihal while integrations land: add end-to-end tests for local participants, offline/reconnect sync, and new joiner bootstrap. -- Step 6, Kushagra + everyone: document and dogfood one real Teambridge session. +- Step 6, Kushagra + everyone: document and dogfood one real Coord session. Pass example: ```bash -cd "$(teambridge enter billing-refactor)" +cd "$(coord enter billing-refactor)" claude ``` @@ -546,12 +546,12 @@ team_ask({ ``` ```bash -teambridge dashboard +coord dashboard ``` Pass when: -- Claude Code receives compact context automatically inside a Teambridge worktree. +- Claude Code receives compact context automatically inside a Coord worktree. - Agent can publish, read, search, and ask through MCP. - CLI inbox and dashboard show the same questions/replies. - Dashboard shows participants, branches, presence, conflicts, and vault highlights. diff --git a/scripts/seed-demo.mjs b/scripts/seed-demo.mjs index 5551854..6b25ddd 100644 --- a/scripts/seed-demo.mjs +++ b/scripts/seed-demo.mjs @@ -1,17 +1,17 @@ #!/usr/bin/env node -// Populate the local Teambridge daemon with 3 realistic demo projects. +// Populate the local Coord daemon with 3 realistic demo projects. // Talks to the daemon's HTTP API + sqlite3 CLI for project/member tables. // // Usage: // node scripts/seed-demo.mjs # seed against http://127.0.0.1:9473 // node scripts/seed-demo.mjs --reset # wipe + reseed -// TEAMBRIDGE_DAEMON_URL=http://127.0.0.1:9473 node scripts/seed-demo.mjs +// COORD_DAEMON_URL=http://127.0.0.1:9473 node scripts/seed-demo.mjs const { execFileSync } = await import('node:child_process'); const { rm, mkdir, writeFile } = await import('node:fs/promises'); const { join } = await import('node:path'); -const DAEMON_URL = process.env.TEAMBRIDGE_DAEMON_URL ?? 'http://127.0.0.1:9473'; +const DAEMON_URL = process.env.COORD_DAEMON_URL ?? 'http://127.0.0.1:9473'; const RESET = process.argv.includes('--reset'); // --------------------------------------------------------------------------- @@ -473,7 +473,7 @@ async function wipeAllDemoData(dbPath, repoRoot) { runSql(dbPath, `delete from local_sequences where workspace_id = ${sq(id)};`); runSql(dbPath, `delete from participants where workspace_id = ${sq(id)};`); runSql(dbPath, `delete from tracks where id = ${sq(id)};`); - await rm(join(repoRoot, '.teambridge', 'workspaces', sessionName), { recursive: true, force: true }); + await rm(join(repoRoot, '.coord', 'workspaces', sessionName), { recursive: true, force: true }); } console.log(`wiped ${tracks.length} tracks and ${allProjectIds.length} projects`); @@ -483,10 +483,10 @@ async function wipeAllDemoData(dbPath, repoRoot) { // Main // --------------------------------------------------------------------------- async function main() { - console.log(`\nseeding Teambridge daemon at ${DAEMON_URL}\n`); + console.log(`\nseeding Coord daemon at ${DAEMON_URL}\n`); const repoRoot = repoRootPath(); - const dbPath = join(repoRoot, '.teambridge', 'state.sqlite'); + const dbPath = join(repoRoot, '.coord', 'state.sqlite'); // Ensure daemon initialized the DB first await api('/workspaces'); diff --git a/tests/integration/cli-flow.test.mjs b/tests/integration/cli-flow.test.mjs index 033db75..c65f133 100644 --- a/tests/integration/cli-flow.test.mjs +++ b/tests/integration/cli-flow.test.mjs @@ -27,7 +27,7 @@ test('CLI init → project create → start → status against a live daemon', a baseUrl: daemon.baseUrl }); assert.equal(init.exitCode, 0, init.stderr || init.stdout); - assert.match(init.stdout, /Initialized Teambridge for Ada Lovelace/); + assert.match(init.stdout, /Initialized Coord for Ada Lovelace/); const profileAfterInit = await apiGet('/user/profile', { repoRoot, baseUrl: daemon.baseUrl }); assert.equal(profileAfterInit.response.status, 200); @@ -103,7 +103,7 @@ test('CLI init → project create → start → status against a live daemon', a }); assert.equal(scopedContext.response.status, 200); assert.equal(scopedContext.body.ok, true); - assert.equal(scopedContext.body.data.context.branch, 'teambridge/billing-refactor/ada-lovelace'); + assert.equal(scopedContext.body.data.context.branch, 'coord/billing-refactor/ada-lovelace'); }); test('CLI start picks the only project when --project is omitted', async (t) => { @@ -146,5 +146,5 @@ test('CLI fails fast when daemon is unreachable', async (t) => { baseUrl: 'http://127.0.0.1:1' }); assert.notEqual(status.exitCode, 0); - assert.match(status.stderr + status.stdout, /fetch failed|ECONNREFUSED|teambridge:/i); + assert.match(status.stderr + status.stdout, /fetch failed|ECONNREFUSED|coord:/i); }); diff --git a/tests/integration/context-hook-flow.test.mjs b/tests/integration/context-hook-flow.test.mjs index a2c8e3a..1279f38 100644 --- a/tests/integration/context-hook-flow.test.mjs +++ b/tests/integration/context-hook-flow.test.mjs @@ -13,7 +13,7 @@ function parseWorktreePath(output) { } function readConfig(repoRoot) { - return JSON.parse(readFileSync(join(repoRoot, '.teambridge', 'config.json'), 'utf8')); + return JSON.parse(readFileSync(join(repoRoot, '.coord', 'config.json'), 'utf8')); } test('init relay-mode config + context deltas + hook install/uninstall', async (t) => { @@ -29,7 +29,7 @@ test('init relay-mode config + context deltas + hook install/uninstall', async ( const ctx = { repoRoot, baseUrl: daemon.baseUrl }; - // --- Relay-mode configuration in `teambridge init` (Phase 2 Step 2) --- + // --- Relay-mode configuration in `coord init` (Phase 2 Step 2) --- const init = runCli(['init', '--first-name', 'Ctx', '--last-name', 'User', '--relay', 'supabase'], ctx); assert.equal(init.exitCode, 0, init.stderr || init.stdout); assert.match(init.stdout, /Relay mode: supabase/); @@ -51,7 +51,7 @@ test('init relay-mode config + context deltas + hook install/uninstall', async ( const worktree = parseWorktreePath(start.stdout); const wt = { ...ctx, cwd: worktree }; - // --- `teambridge context` — smart compact context + deltas (Phase 3) --- + // --- `coord context` — smart compact context + deltas (Phase 3) --- // No events yet: context reports an empty delta. const emptyContext = runCli(['context'], wt); assert.equal(emptyContext.exitCode, 0, emptyContext.stderr || emptyContext.stdout); @@ -107,7 +107,7 @@ test('init relay-mode config + context deltas + hook install/uninstall', async ( assert.equal(daemonDeltas.body.data.deltas.length, 1); assert.equal(daemonDeltas.body.data.deltas[0].seq, 2); - // --- Claude Code hook auto-injection (`teambridge hook`) --- + // --- Claude Code hook auto-injection (`coord hook`) --- const settingsFile = join(worktree, '.claude', 'settings.json'); const statusBefore = runCli(['hook', 'status'], wt); @@ -119,7 +119,7 @@ test('init relay-mode config + context deltas + hook install/uninstall', async ( assert.ok(existsSync(settingsFile), 'settings.json should exist after install'); const settings = JSON.parse(readFileSync(settingsFile, 'utf8')); const commands = settings.hooks.SessionStart.flatMap((entry) => entry.hooks.map((h) => h.command)); - assert.ok(commands.some((c) => c.includes('teambridge context')), 'hook command should run `teambridge context`'); + assert.ok(commands.some((c) => c.includes('coord context')), 'hook command should run `coord context`'); // Install is idempotent — a second install does not add a duplicate entry. runCli(['hook', 'install'], wt); diff --git a/tests/integration/daemon-inbox-conflicts.test.mjs b/tests/integration/daemon-inbox-conflicts.test.mjs index 3fbd177..336e1dd 100644 --- a/tests/integration/daemon-inbox-conflicts.test.mjs +++ b/tests/integration/daemon-inbox-conflicts.test.mjs @@ -55,7 +55,7 @@ test('daemon inbox and conflict endpoints work end-to-end', async (t) => { // Switch local profile to Bob so he can reply. await writeFile( - path.join(repoRoot, '.teambridge', 'user.json'), + path.join(repoRoot, '.coord', 'user.json'), JSON.stringify({ schemaVersion: 1, firstName: 'Bob', lastName: 'B', displayName: 'Bob' }, null, 2) ); diff --git a/tests/integration/helpers.mjs b/tests/integration/helpers.mjs index b183c04..7963ccb 100644 --- a/tests/integration/helpers.mjs +++ b/tests/integration/helpers.mjs @@ -26,11 +26,11 @@ export async function getFreePort() { } export async function createTempGitRepo() { - const dir = await mkdtemp(join(tmpdir(), 'teambridge-it-')); + const dir = await mkdtemp(join(tmpdir(), 'coord-it-')); execFileSync('git', ['init', '-b', 'main'], { cwd: dir, stdio: 'ignore' }); - execFileSync('git', ['config', 'user.email', 'teambridge-test@local'], { cwd: dir, stdio: 'ignore' }); - execFileSync('git', ['config', 'user.name', 'Teambridge Test'], { cwd: dir, stdio: 'ignore' }); - await writeFile(join(dir, 'README.md'), '# teambridge integration fixture\n'); + execFileSync('git', ['config', 'user.email', 'coord-test@local'], { cwd: dir, stdio: 'ignore' }); + execFileSync('git', ['config', 'user.name', 'Coord Test'], { cwd: dir, stdio: 'ignore' }); + await writeFile(join(dir, 'README.md'), '# coord integration fixture\n'); execFileSync('git', ['add', 'README.md'], { cwd: dir, stdio: 'ignore' }); execFileSync('git', ['commit', '-m', 'init'], { cwd: dir, stdio: 'ignore' }); return dir; @@ -64,8 +64,8 @@ export async function startTestDaemon(repoRoot) { cwd: repoRoot, env: { ...process.env, - TEAMBRIDGE_DAEMON_PORT: String(port), - TEAMBRIDGE_REPO_ROOT: repoRoot + COORD_DAEMON_PORT: String(port), + COORD_REPO_ROOT: repoRoot }, stdio: ['ignore', 'pipe', 'pipe'] } @@ -110,8 +110,8 @@ export function runCli(args, { repoRoot, baseUrl, cwd }) { cwd: cwd ?? repoRoot, env: { ...process.env, - TEAMBRIDGE_DAEMON_URL: baseUrl, - TEAMBRIDGE_REPO_ROOT: repoRoot + COORD_DAEMON_URL: baseUrl, + COORD_REPO_ROOT: repoRoot }, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] diff --git a/tests/integration/inbox-conflicts-flow.test.mjs b/tests/integration/inbox-conflicts-flow.test.mjs index cb21e32..5967783 100644 --- a/tests/integration/inbox-conflicts-flow.test.mjs +++ b/tests/integration/inbox-conflicts-flow.test.mjs @@ -91,7 +91,7 @@ test('ask/reply inbox and conflict marker parsing resolve through CLI and daemon assert.match(conflicts.stdout, /open\s+content\s+Conflict in blockers\.md/); const conflictId = parseConflictId(conflicts.stdout); - const conflictsFile = await readFile(join(repoRoot, '.teambridge', 'workspaces', 'handoff-flow', 'vault', 'conflicts.md'), 'utf8'); + const conflictsFile = await readFile(join(repoRoot, '.coord', 'workspaces', 'handoff-flow', 'vault', 'conflicts.md'), 'utf8'); assert.match(conflictsFile, new RegExp(conflictId)); assert.match(conflictsFile, /retry forever/); assert.match(conflictsFile, /stop after three failed refresh attempts/); @@ -106,6 +106,6 @@ test('ask/reply inbox and conflict marker parsing resolve through CLI and daemon assert.equal(resolved.exitCode, 0, resolved.stderr || resolved.stdout); assert.match(resolved.stdout, new RegExp(`${conflictId}\\s+resolved`)); - const resolvedFile = await readFile(join(repoRoot, '.teambridge', 'workspaces', 'handoff-flow', 'vault', 'conflicts.md'), 'utf8'); + const resolvedFile = await readFile(join(repoRoot, '.coord', 'workspaces', 'handoff-flow', 'vault', 'conflicts.md'), 'utf8'); assert.match(resolvedFile, /Use the three-attempt cap\./); }); diff --git a/tests/integration/mcp-flow.test.mjs b/tests/integration/mcp-flow.test.mjs index 760bf4b..d9dc38e 100644 --- a/tests/integration/mcp-flow.test.mjs +++ b/tests/integration/mcp-flow.test.mjs @@ -28,8 +28,8 @@ function createMcpClient({ daemonUrl, repoRoot }) { cwd: repoRoot, env: { ...process.env, - TEAMBRIDGE_DAEMON_URL: daemonUrl, - TEAMBRIDGE_REPO_ROOT: repoRoot + COORD_DAEMON_URL: daemonUrl, + COORD_REPO_ROOT: repoRoot }, stdio: ['pipe', 'pipe', 'pipe'] }); @@ -129,7 +129,7 @@ test('MCP tools work end-to-end against a real daemon', async (t) => { // .active file lets the MCP server resolve workspace without explicit params. const { writeFile } = await import('node:fs/promises'); - await writeFile(pathJoin(repoRoot, '.teambridge', '.active'), 'smoke-track'); + await writeFile(pathJoin(repoRoot, '.coord', '.active'), 'smoke-track'); // --- Spawn the MCP server --- const mcp = createMcpClient({ daemonUrl: daemon.baseUrl, repoRoot }); @@ -141,7 +141,7 @@ test('MCP tools work end-to-end against a real daemon', async (t) => { capabilities: {}, clientInfo: { name: 'smoke-test', version: '0.0.1' } }); - assert.equal(init.result.serverInfo.name, 'teambridge'); + assert.equal(init.result.serverInfo.name, 'coord'); mcp.notify('notifications/initialized'); // --- workspace_status: returns real workspace + participants --- @@ -197,7 +197,7 @@ test('MCP tools work end-to-end against a real daemon', async (t) => { }); assert.ok(!conflictPublishRes.result.isError, `team_publish conflict failed: ${conflictPublishRes.result.content?.[0]?.text}`); - const conflictsRes = await mcp.request('resources/read', { uri: 'teambridge://conflicts' }); + const conflictsRes = await mcp.request('resources/read', { uri: 'coord://conflicts' }); assert.ok(conflictsRes.result, 'resources/read should return a result'); assert.ok(conflictsRes.result.contents?.[0], 'resources/read should return contents'); assert.ok(!conflictsRes.result.contents[0].text?.includes('not yet'), 'conflicts resource should be live'); @@ -222,7 +222,7 @@ test('MCP tools work end-to-end against a real daemon', async (t) => { // The CLI init short-circuits if a profile already exists, so write the local // user.json directly to match Bob's participant displayName. await writeFile( - pathJoin(repoRoot, '.teambridge', 'user.json'), + pathJoin(repoRoot, '.coord', 'user.json'), JSON.stringify({ schemaVersion: 1, firstName: 'Bob', lastName: 'B', displayName: 'Bob' }, null, 2) ); @@ -236,7 +236,7 @@ test('MCP tools work end-to-end against a real daemon', async (t) => { assert.equal(repliedMessage.replyText, 'Yes, FTS5 is included in the sqlite3 build.'); // --- Inbox resource: the answered message appears --- - const inboxRes = await mcp.request('resources/read', { uri: 'teambridge://inbox' }); + const inboxRes = await mcp.request('resources/read', { uri: 'coord://inbox' }); const inboxData = JSON.parse(inboxRes.result.contents[0].text); assert.ok(Array.isArray(inboxData.messages), 'Expected messages array'); assert.ok(inboxData.messages.length >= 1, 'Expected at least one inbox message'); diff --git a/tests/integration/relay-reconnect-bootstrap.test.mjs b/tests/integration/relay-reconnect-bootstrap.test.mjs index 1e2ea58..06a352a 100644 --- a/tests/integration/relay-reconnect-bootstrap.test.mjs +++ b/tests/integration/relay-reconnect-bootstrap.test.mjs @@ -8,7 +8,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createTempGitRepo, getFreePort, parseCreatedProjectId, removeTempDir, runCli, startTestDaemon } from './helpers.mjs'; -const TEST_REMOTE = 'https://example.com/teambridge/relay-bootstrap.git'; +const TEST_REMOTE = 'https://example.com/coord/relay-bootstrap.git'; async function readBody(request) { const chunks = []; @@ -24,7 +24,7 @@ function queryValue(url, key, prefix = 'eq.') { function seedRemoteIdentity(repoRoot, relayUrl, email) { const userId = `remote_${email.replace(/[^a-z0-9]/gi, '_')}`; execFileSync('sqlite3', [ - join(repoRoot, '.teambridge', 'state.sqlite'), + join(repoRoot, '.coord', 'state.sqlite'), ` insert into remote_identity ( relay_url, user_id, email, access_token, refresh_token, expires_at, updated_at @@ -282,16 +282,16 @@ test('relay reconnect pushes queued events and late join bootstraps from checkpo SUPABASE_REST_URL: process.env.SUPABASE_REST_URL, SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY, - TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS: process.env.TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS, - TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS: process.env.TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS + COORD_RELAY_SYNC_INTERVAL_MS: process.env.COORD_RELAY_SYNC_INTERVAL_MS, + COORD_CHECKPOINT_INTERVAL_EVENTS: process.env.COORD_CHECKPOINT_INTERVAL_EVENTS }; Object.assign(process.env, { SUPABASE_URL: mock.url, SUPABASE_REST_URL: `${mock.url}/rest/v1`, SUPABASE_ANON_KEY: 'anon-test-key', SUPABASE_SERVICE_ROLE_KEY: 'service-test-key', - TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS: '60000', - TEAMBRIDGE_CHECKPOINT_INTERVAL_EVENTS: '1' + COORD_RELAY_SYNC_INTERVAL_MS: '60000', + COORD_CHECKPOINT_INTERVAL_EVENTS: '1' }); t.after(async () => { for (const [key, value] of Object.entries(previousEnv)) { @@ -302,7 +302,7 @@ test('relay reconnect pushes queued events and late join bootstraps from checkpo }); const repoRoot = await createTempGitRepo(); - const cloneRoot = await mkdtemp(join(tmpdir(), 'teambridge-it-clone-')); + const cloneRoot = await mkdtemp(join(tmpdir(), 'coord-it-clone-')); t.after(async () => { await removeTempDir(repoRoot); await rm(cloneRoot, { recursive: true, force: true }); @@ -311,8 +311,8 @@ test('relay reconnect pushes queued events and late join bootstraps from checkpo execFileSync('git', ['remote', 'add', 'origin', TEST_REMOTE], { cwd: repoRoot }); execFileSync('git', ['clone', repoRoot, cloneRoot], { stdio: 'ignore' }); execFileSync('git', ['remote', 'set-url', 'origin', TEST_REMOTE], { cwd: cloneRoot }); - execFileSync('git', ['config', 'user.email', 'teambridge-test@local'], { cwd: cloneRoot }); - execFileSync('git', ['config', 'user.name', 'Teambridge Test'], { cwd: cloneRoot }); + execFileSync('git', ['config', 'user.email', 'coord-test@local'], { cwd: cloneRoot }); + execFileSync('git', ['config', 'user.name', 'Coord Test'], { cwd: cloneRoot }); const daemon = await startTestDaemon(repoRoot); const cloneDaemon = await startTestDaemon(cloneRoot); @@ -362,7 +362,7 @@ test('relay reconnect pushes queued events and late join bootstraps from checkpo }); const bootstrappedDecision = await readFile( - join(cloneRoot, '.teambridge', 'workspaces', 'relay-bootstrap', 'vault', 'decisions.md'), + join(cloneRoot, '.coord', 'workspaces', 'relay-bootstrap', 'vault', 'decisions.md'), 'utf8' ); assert.match(bootstrappedDecision, /Queued while relay is down/); diff --git a/tests/integration/vault-flow.test.mjs b/tests/integration/vault-flow.test.mjs index eba4852..f9533a3 100644 --- a/tests/integration/vault-flow.test.mjs +++ b/tests/integration/vault-flow.test.mjs @@ -128,7 +128,7 @@ test('start + join + publish + vault read/context/search + ws who/branches, all const branches = runCli(['ws', 'branches', 'auth-redesign'], { ...ctx, cwd: ronishWorktree }); assert.equal(branches.exitCode, 0, branches.stderr || branches.stdout); - assert.match(branches.stdout, /teambridge\/auth-redesign\/kushagra-a/); - assert.match(branches.stdout, /teambridge\/auth-redesign\/ronish/); - assert.match(branches.stdout, /teambridge\/auth-redesign\/nihal/); + assert.match(branches.stdout, /coord\/auth-redesign\/kushagra-a/); + assert.match(branches.stdout, /coord\/auth-redesign\/ronish/); + assert.match(branches.stdout, /coord\/auth-redesign\/nihal/); }); diff --git a/todo.md b/todo.md index e6a0229..0d15cc9 100644 --- a/todo.md +++ b/todo.md @@ -1,6 +1,6 @@ -# Condominium TODO +# Coord TODO -Working checklist for building Condominium. Anything not checked is still pending. +Working checklist for building Coord. Anything not checked is still pending. ## Done @@ -46,25 +46,25 @@ Goal: one machine can simulate Nihal, Kushagra, and Ronish as separate participa - [x] Implement local SQLite state using a Phase 1 SQLite adapter. - [x] Implement local workspace store. - [x] Step 4, Kushagra after daemon health/config exist: - - [x] Implement `teambridge init` (profile + avatar via daemon). - - [x] Implement `teambridge status`. - - [x] Implement `teambridge project create` and `teambridge project list`. + - [x] Implement `coord init` (profile + avatar via daemon). + - [x] Implement `coord status`. + - [x] Implement `coord project create` and `coord project list`. - [x] Wire CLI calls to the local daemon. - [x] Make CLI requests repo-aware: resolve the current working directory to the current git repo root and send that `repoRoot` to the daemon. - [x] Daemon discovers git repo root from cwd when started via `pnpm daemon` (walk up to `.git`). - - [x] Implement `teambridge ws show `. - - [x] Implement `teambridge ws who `. - - [x] Implement `teambridge ws branches `. - - [x] Keep daemon startup generic; normal users should not have to start the daemon with `--repo` (`teambridge daemon start|status|stop` manages a background daemon for Phase 1; IDE/OS launch can come later). + - [x] Implement `coord ws show `. + - [x] Implement `coord ws who `. + - [x] Implement `coord ws branches `. + - [x] Keep daemon startup generic; normal users should not have to start the daemon with `--repo` (`coord daemon start|status|stop` manages a background daemon for Phase 1; IDE/OS launch can come later). - [x] Step 5, Nihal backend workspace APIs: - [x] Nihal: implement daemon workspace create/join APIs and persist workspace manifests. - [x] Local user profile APIs (`GET/POST /user/profile`, flower avatar on init). (moved from Step 9 — backend API work, not dashboard UI) - [x] `POST /projects` + roster member upsert; start/join link tracks to projects. (moved from Step 9 — backend API work, not dashboard UI) - [x] Stub MCP resource names from contracts (`packages/mcp/src/resources.ts`, `tools.ts`). (moved from Step 9 — done early; real MCP server work is Phase 3 Step 2) - [x] Step 6, Kushagra workspace CLI after backend workspace APIs exist: - - [x] Kushagra: implement `teambridge start [base_ref]` — creates the session, links `projectId`, and creates a real worktree/branch for the starter. - - [x] Kushagra: implement `teambridge join --as ` — uses recorded `base_commit`, creates an isolated git worktree + branch `teambridge//` under `.teambridge/worktrees/`, idempotent re-join. See `docs/cli-worktrees.md`. - - [x] Kushagra: implement `teambridge enter ` — prints the resolved worktree path for `cd "$(teambridge enter NAME)"`. + - [x] Kushagra: implement `coord start [base_ref]` — creates the session, links `projectId`, and creates a real worktree/branch for the starter. + - [x] Kushagra: implement `coord join --as ` — uses recorded `base_commit`, creates an isolated git worktree + branch `coord//` under `.coord/worktrees/`, idempotent re-join. See `docs/cli-worktrees.md`. + - [x] Kushagra: implement `coord enter ` — prints the resolved worktree path for `cd "$(coord enter NAME)"`. - [x] Step 7, Nihal backend event and vault APIs: - [x] Nihal: implement local event append to `events.jsonl`. - [x] Nihal: implement the single local user event type: `publish`. @@ -73,19 +73,19 @@ Goal: one machine can simulate Nihal, Kushagra, and Ronish as separate participa - [x] Nihal: implement local vault materialization and vault rebuild from events. - [x] Nihal: implement basic `vault context` by concatenating flat vault files up to a byte/character limit and returning `truncated`. - [x] Step 8, Kushagra vault CLI after backend event and vault APIs exist: - - [x] Kushagra: implement `teambridge publish ` — resolves the current track from the participant branch (`teambridge//`), no `` argument needed. - - [x] Kushagra: implement `teambridge vault read `. - - [x] Kushagra: implement `teambridge vault search ` — backed by a real SQLite FTS5 index (`vault_search_index` in `state.sqlite`), kept consistent through `vault rebuild`. See `packages/daemon/src/index.ts` (`reindexVaultFile`) and the `GET /workspaces/:id/vault/search` route. - - [x] Kushagra: implement `teambridge vault context`. + - [x] Kushagra: implement `coord publish ` — resolves the current track from the participant branch (`coord//`), no `` argument needed. + - [x] Kushagra: implement `coord vault read `. + - [x] Kushagra: implement `coord vault search ` — backed by a real SQLite FTS5 index (`vault_search_index` in `state.sqlite`), kept consistent through `vault rebuild`. See `packages/daemon/src/index.ts` (`reindexVaultFile`) and the `GET /workspaces/:id/vault/search` route. + - [x] Kushagra: implement `coord vault context`. - [x] Step 9, Ronish after daemon read endpoints exist: - [x] Stub dashboard API client against daemon response contracts. - [x] Show local workspace list (project picker + track sidebar). - [x] Show local participants (project members sidebar) — display name/status only; branch/agent are fetched but not rendered (see Dashboard milestone follow-ups below). - [x] Show vault file highlights (with color/assign annotations). - - [x] CLI scaffold aligned with dashboard (`teambridge init`, `project create`, `start` → same daemon data). + - [x] CLI scaffold aligned with dashboard (`coord init`, `project create`, `start` → same daemon data). - [x] Sidebar repo context panel (`GET /repo/context`, `POST /repo/open-path` — remote, branch, local path, last push + commit link). - [x] Vault chip first-name display (`participantFirstName`). - - [x] Root ergonomics: `pnpm teambridge`, `pnpm dashboard:preview`, `VITE_TEAMBRIDGE_REPO_ROOT` baked at dashboard build. + - [x] Root ergonomics: `pnpm coord`, `pnpm dashboard:preview`, `VITE_COORD_REPO_ROOT` baked at dashboard build. - [x] CLI integration tests in `tests/integration/` (`pnpm test:integration`). - [x] Step 10, everyone: prove the local pass example below. @@ -103,21 +103,21 @@ Shipped on `feat/ronish-mcp-dashboard`: - [x] Integration tests for CLI + daemon (`tests/integration/`, `pnpm test:integration`) - [x] Topbar cleanup (removed teammate count + note # chips) -Now complete: MCP HTTP server, inbox UI, conflicts UI, presence polish, `teambridge ask`/`inbox`/`reply`, and CLI/dashboard parity for the local workflow. Remaining later polish: packaged installer / IDE auto-launch daemon. `start`/`enter`/`publish`/`vault read|context|search`/`ws show|who|branches` are done on the CLI — see `docs/cli-worktrees.md` and `tests/integration/vault-flow.test.mjs` — and the dashboard now exposes the matching Phase 1 read/search/worktree surfaces plus inbox, conflicts, and teammate-delta panels: +Now complete: MCP HTTP server, inbox UI, conflicts UI, presence polish, `coord ask`/`inbox`/`reply`, and CLI/dashboard parity for the local workflow. Remaining later polish: packaged installer / IDE auto-launch daemon. `start`/`enter`/`publish`/`vault read|context|search`/`ws show|who|branches` are done on the CLI — see `docs/cli-worktrees.md` and `tests/integration/vault-flow.test.mjs` — and the dashboard now exposes the matching Phase 1 read/search/worktree surfaces plus inbox, conflicts, and teammate-delta panels: - [x] Ronish: render participant `branch` and `agent` in `TrackParticipantsPanel.tsx` — the daemon's `/workspaces/:id/status` response includes both per participant. - [x] Ronish: add a vault search UI (search box + ranked results list) calling the new `GET /workspaces/:id/vault/search` route. - [x] Ronish: add a single-file vault viewer calling `GET /workspaces/:id/vault/read`. -- [x] Ronish: surface each participant's worktree path (or an "Enter" affordance) in the dashboard, mirroring `teambridge enter `. +- [x] Ronish: surface each participant's worktree path (or an "Enter" affordance) in the dashboard, mirroring `coord enter `. CLI + dashboard dogfood (no seed): ```bash pnpm build pnpm daemon -pnpm teambridge init -pnpm teambridge project create --name "My App" --description "Local dogfood" -pnpm teambridge start auth-redesign --project +pnpm coord init +pnpm coord project create --name "My App" --description "Local dogfood" +pnpm coord start auth-redesign --project pnpm dashboard # dev # or: pnpm dashboard:preview # production build pnpm test:integration # verify CLI + daemon end-to-end @@ -129,20 +129,20 @@ pnpm test:integration # verify CLI + daemon end-to-end ```bash # Nihal starts the workspace from main. -teambridge init -teambridge start billing-refactor main +coord init +coord start billing-refactor main # Kushagra and Ronish are simulated locally as separate participants/worktrees. -teambridge join billing-refactor --as kushagra -teambridge join billing-refactor --as ronish +coord join billing-refactor --as kushagra +coord join billing-refactor --as ronish -# Nihal publishes into a flat vault file (run from inside Nihal's worktree — see `teambridge enter`). -teambridge publish decisions.md "Backend is the source of truth for invoice state." +# Nihal publishes into a flat vault file (run from inside Nihal's worktree — see `coord enter`). +coord publish decisions.md "Backend is the source of truth for invoice state." # Any participant can read/search the materialized vault (run from inside their own worktree). -teambridge vault read decisions.md -teambridge vault search "invoice state" -teambridge vault context +coord vault read decisions.md +coord vault search "invoice state" +coord vault context ``` Pass when: @@ -175,10 +175,10 @@ Implemented and live-verified: - [x] Daemon relay identity/device registration is implemented. - [x] Daemon mirrors local projects, sessions, and participants to Supabase after login. - [x] Daemon queues failed remote publishes in `pending_remote_events`. -- [x] Daemon push/pull sync exists, with autonomous polling via `TEAMBRIDGE_RELAY_SYNC_INTERVAL_MS` and manual `teambridge sync`. +- [x] Daemon push/pull sync exists, with autonomous polling via `COORD_RELAY_SYNC_INTERVAL_MS` and manual `coord sync`. - [x] Remote canonical events rebuild/materialize the local vault. -- [x] CLI commands exist: `teambridge login`, `teambridge sessions`, `teambridge list`, `teambridge sync`, and `teambridge status relay`. -- [x] `teambridge join ` can discover/import a remote relay session before creating the local worktree. +- [x] CLI commands exist: `coord login`, `coord sessions`, `coord list`, `coord sync`, and `coord status relay`. +- [x] `coord join ` can discover/import a remote relay session before creating the local worktree. - [x] Dashboard calls `/relay/sessions` and merges relay sessions with local sessions. - [x] Live verification passed against Supabase for schema reachability, storage bucket, append RPC, CLI login/start/sessions/sync/status, and CLI publish reaching Supabase. @@ -208,9 +208,9 @@ Implemented after inbox/conflict pass: - [x] Implement canonical event insert with per-workspace monotonic `seq` via `tc_append_event`. - [x] Step 2, Kushagra + Ronish in parallel while Nihal finishes relay internals: - [x] Kushagra: implement CLI auth/login flow against relay responses. - - [x] Kushagra: add relay mode configuration to `teambridge init` (`--relay local|supabase`, interactive prompt; `/config/init` accepts `relayMode` and updates `defaultRelayMode`). - - [x] Kushagra: add `teambridge status relay` output for relay configured/logged-in/pending state. - - [x] Kushagra: add `teambridge sessions` / `teambridge list` for remote session discovery. + - [x] Kushagra: add relay mode configuration to `coord init` (`--relay local|supabase`, interactive prompt; `/config/init` accepts `relayMode` and updates `defaultRelayMode`). + - [x] Kushagra: add `coord status relay` output for relay configured/logged-in/pending state. + - [x] Kushagra: add `coord sessions` / `coord list` for remote session discovery. - [x] Ronish: build dashboard screens for realtime event feed, participant presence, checkpoint state, and sync health using mocked/live events. (PR #3) - [x] Ronish: merge remote relay sessions into the dashboard session list. - [x] Ronish: update MCP resource contracts to include relay-backed workspace state. (PR #4) @@ -220,9 +220,9 @@ Implemented after inbox/conflict pass: - [x] Implement event dedupe via `dedupeKey`. - [x] Implement polling pull-after-last-remote-seq and vault rematerialization. - [ ] Step 4, Kushagra after relay event APIs exist: - - [x] Update `teambridge start` to register workspace remotely when logged into relay. - - [x] Update `teambridge join` to fetch/import remote workspace/events before creating the local worktree. - - [x] Update `teambridge status relay` to show real sync state. + - [x] Update `coord start` to register workspace remotely when logged into relay. + - [x] Update `coord join` to fetch/import remote workspace/events before creating the local worktree. + - [x] Update `coord status relay` to show real sync state. - Add clearer CLI messages for reconnect/retry behavior. - [x] Step 5, Nihal + Ronish in parallel: - [x] Nihal: implement checkpoint upload/download. @@ -239,20 +239,20 @@ Implemented after inbox/conflict pass: ```bash # Device A, Nihal -teambridge start billing-refactor main -teambridge publish observations.md "Refresh endpoint retries forever when token refresh fails." +coord start billing-refactor main +coord publish observations.md "Refresh endpoint retries forever when token refresh fails." # Device B, Kushagra -teambridge join billing-refactor -teambridge vault search "Refresh endpoint" +coord join billing-refactor +coord vault search "Refresh endpoint" # Device A goes offline, publishes locally, then reconnects. -teambridge publish blockers.md "Need backend decision before changing retry UI." -teambridge status +coord publish blockers.md "Need backend decision before changing retry UI." +coord status # Device C, Ronish, joins late. -teambridge join billing-refactor -teambridge vault read observations.md +coord join billing-refactor +coord vault read observations.md ``` Pass when: @@ -265,7 +265,7 @@ Pass when: ## Phase 3: Agent UX, MCP, Inbox, and Dashboard -Goal: agents and humans can use Teambridge naturally through hooks, MCP, inbox, and dashboard without per-session flags. +Goal: agents and humans can use Coord naturally through hooks, MCP, inbox, and dashboard without per-session flags. ### Phase 3 Execution Order @@ -277,17 +277,17 @@ Goal: agents and humans can use Teambridge naturally through hooks, MCP, inbox, - [x] Add participant-level actor validation for MCP/dashboard mutating calls. - [x] Step 2, Ronish + Kushagra in parallel: - [x] Ronish: implement MCP server over stdio transport (Claude Code compatible). (PR #5) - - [x] Ronish: implement MCP workspace/worktree resolution using explicit query params, local `state.sqlite` worktree path mapping, and `.teambridge/.active` fallback. (PR #5) - - [x] Ronish: implement MCP resources: `teambridge://workspace`, `teambridge://participants`, `teambridge://vault/context`, `teambridge://inbox` (live), `teambridge://conflicts` (live). (PR #5 + inbox/conflicts cross-surface branch) - - [x] Kushagra: implement Claude Code hook auto-injection (`teambridge hook install|uninstall|status` writes a SessionStart hook into `.claude/settings.json` that runs `teambridge context`). - - [x] Kushagra: ensure normal use needs no per-session CLI flags (the installed hook runs `teambridge context` with no flags; `context`/`publish`/`vault *` resolve the current track from the branch). - - [x] Kushagra: upgrade Phase 1 `vault context` into smarter compact vault context generation UX (`teambridge context` drops empty files, strips per-file titles, and dedupes bullets). - - [x] Kushagra: implement delta injection for teammate updates (`teambridge context` shows what changed since a per-participant last-seen `seq`; `--peek`/`--deltas-only`/`--json` supported). + - [x] Ronish: implement MCP workspace/worktree resolution using explicit query params, local `state.sqlite` worktree path mapping, and `.coord/.active` fallback. (PR #5) + - [x] Ronish: implement MCP resources: `coord://workspace`, `coord://participants`, `coord://vault/context`, `coord://inbox` (live), `coord://conflicts` (live). (PR #5 + inbox/conflicts cross-surface branch) + - [x] Kushagra: implement Claude Code hook auto-injection (`coord hook install|uninstall|status` writes a SessionStart hook into `.claude/settings.json` that runs `coord context`). + - [x] Kushagra: ensure normal use needs no per-session CLI flags (the installed hook runs `coord context` with no flags; `context`/`publish`/`vault *` resolve the current track from the branch). + - [x] Kushagra: upgrade Phase 1 `vault context` into smarter compact vault context generation UX (`coord context` drops empty files, strips per-file titles, and dedupes bullets). + - [x] Kushagra: implement delta injection for teammate updates (`coord context` shows what changed since a per-participant last-seen `seq`; `--peek`/`--deltas-only`/`--json` supported). - [x] Step 3, Ronish + Kushagra in parallel after inbox endpoints exist: - [x] Ronish: implement MCP tools: `team_publish`, `team_ask`, `team_reply`, `vault_search`, `vault_read`, `workspace_status`. - - [x] Kushagra: implement `teambridge ask`. - - [x] Kushagra: implement `teambridge inbox`. - - [x] Kushagra: implement `teambridge reply`. + - [x] Kushagra: implement `coord ask`. + - [x] Kushagra: implement `coord inbox`. + - [x] Kushagra: implement `coord reply`. - [x] Kushagra: add CLI affordances for pending questions. - [x] Step 4, Ronish after dashboard APIs are stable: - [x] Implement dashboard shell (React Router, project picker, track sidebar). @@ -304,13 +304,13 @@ Goal: agents and humans can use Teambridge naturally through hooks, MCP, inbox, - [x] Add end-to-end tests for new joiner bootstrap (`tests/integration/relay-reconnect-bootstrap.test.mjs`). - [ ] Step 6, Kushagra + everyone: - Kushagra: document the first real dogfood workflow for Nihal, Kushagra, and Ronish. - - Everyone: dogfood one real Teambridge session and fix gaps. + - Everyone: dogfood one real Coord session and fix gaps. ### Phase 3 Pass Example ```bash -# Kushagra enters his Teambridge worktree and starts Claude Code. -cd "$(teambridge enter billing-refactor)" +# Kushagra enters his Coord worktree and starts Claude Code. +cd "$(coord enter billing-refactor)" claude ``` @@ -331,12 +331,12 @@ team_ask({ ``` ```bash -teambridge dashboard +coord dashboard ``` Pass when: -- [x] Claude Code receives compact context automatically inside a Teambridge worktree. +- [x] Claude Code receives compact context automatically inside a Coord worktree. - [x] Agent can publish, read, search, and ask through MCP. - [x] CLI inbox and dashboard show the same questions/replies. - [x] Dashboard shows participants, branches, presence, conflicts, and vault highlights.