diff --git a/README.md b/README.md index 132f618..27c8251 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ CodePlans sits between your issue tracker and your architecture diagram: | Analytics wired to real data (velocity, effort accuracy, debt by product) | ✅ Available | | Activity feed | ✅ Available | | GitHub, GitLab, Jira, Asana & Linear integrations (pull-only mirror into work items) | ✅ Available | -| MCP server — 41 tools incl. product/asset/dependency management, releases, asset design notes & the asset record | ✅ Available | +| MCP server — 42 tools incl. product/asset/dependency management, model refactoring (move_asset), releases, asset design notes & the asset record | ✅ Available | | Milestone-linked plans with mirrored tasks (mixed mode) | ✅ Available | | PR auto-linking (plan-asset PR status refreshed on sync) | ✅ Available | | Releases — delivery grouping with per-asset version stamps & derived release notes | ✅ Available | @@ -413,7 +413,7 @@ Your agent can then read plans/work items/tech debt and (with a write-scope key) - [x] **v0.4.3 — AI drafting (feature-flagged):** release notes & design notes drafted from delivered work, always landing in an editor - [x] **v0.4.4 — Asset Record (Phase A):** per-asset capabilities register with delivery lineage, graduation from resolved work items, derived known-issues/debt sections, tombstoned removals, `get_asset_record` + `graduate_work_item` MCP tools (41 total) - [x] **v0.4.5 — Asset Atlas** (see [`docs/specs/asset-atlas-spec.md`](docs/specs/asset-atlas-spec.md)): top-level Assets view — a live system map (products as columns, dependency edges, health/debt/activity lenses, blast-radius hover) plus grid and sortable table views -- [ ] **v0.4.6 — Layers & model boundaries** (see [`docs/specs/layers-and-boundaries-spec.md`](docs/specs/layers-and-boundaries-spec.md)): asset `layer` field, Atlas layer columns for single-product systems, `move_asset` model refactoring, boundary guidance in MCP +- [x] **v0.4.6 — Layers & model boundaries** (see [`docs/specs/layers-and-boundaries-spec.md`](docs/specs/layers-and-boundaries-spec.md)): asset `layer` field with display-time type defaults, Atlas layer columns (auto for single-product scope), `move_asset` model refactoring, boundary rule + layer taxonomy in the MCP modeling guide (42 tools) - [ ] **v0.4.7+ — Reconciliation & round-trip engineering** (see [`docs/specs/asset-record-spec.md`](docs/specs/asset-record-spec.md)): agent reconciliation proposals, release publishing - [ ] AI-assisted effort estimation - [ ] Billing / subscription management (hosted tier, optional & feature-flagged) diff --git a/app/(dashboard)/actions.ts b/app/(dashboard)/actions.ts index 75db99f..e65d21d 100644 --- a/app/(dashboard)/actions.ts +++ b/app/(dashboard)/actions.ts @@ -212,6 +212,7 @@ export async function updateAssetAction(id: string, productSlug: string, formDat const repositoryUrl = (formData.get('repositoryUrl') as string) || undefined const repoPath = (formData.get('repoPath') as string) || undefined const documentationUrl = (formData.get('documentationUrl') as string) || undefined + const layerRaw = formData.get('layer') await updateAsset(id, { name, @@ -223,6 +224,8 @@ export async function updateAssetAction(id: string, productSlug: string, formDat repositoryUrl, repoPath, documentationUrl, + // Absent field = form without the input (no change); blank = clear. + ...(layerRaw !== null ? { layer: (layerRaw as string).trim() || null } : {}), }) revalidatePath(`/products/${productSlug}`) diff --git a/app/(dashboard)/assets/[id]/page.tsx b/app/(dashboard)/assets/[id]/page.tsx index 12ae60d..750c007 100644 --- a/app/(dashboard)/assets/[id]/page.tsx +++ b/app/(dashboard)/assets/[id]/page.tsx @@ -129,6 +129,9 @@ export default async function AssetDetailPage({ params }: { params: Promise<{ id
{isPending ? 'Saving…' : 'Changes save automatically'}
diff --git a/app/api/mcp/[transport]/route.ts b/app/api/mcp/[transport]/route.ts index 0bc81b9..b8a60a2 100644 --- a/app/api/mcp/[transport]/route.ts +++ b/app/api/mcp/[transport]/route.ts @@ -42,6 +42,7 @@ import { setReleaseAsset, createDesignNote, graduateWorkItem, + moveAsset, } from '@/lib/db/mutations' import { getAssetOptions, getAssetDetail } from '@/lib/db/queries' import { resolveAssigneeEmail } from '@/lib/mcp/users' @@ -158,6 +159,21 @@ const handler = createMcpHandler( 'How to break a codebase (especially a monorepo) into CodePlans products, assets, and dependency edges. Read this before bulk-creating assets.', {}, async () => json({ + boundaries: { + rule: 'A product is something you SHIP, VERSION, and GRANT ACCESS TO as a unit. An asset is something you CHANGE. When in doubt, prefer fewer products with more assets — assets are movable (move_asset); products are commitments (access control, plans, and releases all scope by product, and releases exist to coordinate a revision ACROSS assets — products drawn too small make coordinated releases impossible to express).', + examples: [ + 'Single SaaS (one app + API + shared libs + datastores): ONE product, many assets — even at 30-40 assets. Use layers for internal structure.', + 'Portfolio (web platform + mobile apps on app-store trains + public API with its own versioning contract): several products — each genuinely ships on its own cadence to its own audience.', + 'Monorepo: orthogonal — model the shipping boundary, not the folder structure (repoPath maps folders).', + 'Internal platform consumed by other teams on its own version contract: a product — that is an audience.', + ], + refining: 'Audit the Atlas → assign explicit layers (update_asset) → move_asset where a boundary was drawn wrong (blocked while draft/active plans target the asset; work items follow; history is preserved) → re-check the map.', + }, + layers: { + purpose: 'A layer describes where an asset sits INSIDE its product — structure, not boundary. Layers drive the Atlas map columns for single-product systems. Free text; prefer the taxonomy.', + taxonomy: ['edge', 'frontend', 'backend', 'domain', 'data', 'infra', 'shared'], + defaults: 'Unset layers display a default from asset type (app→frontend, service→backend, datastore→data, platform→infra, library→shared). Set explicit layers where the default is wrong — e.g. a library that is really domain logic gets layer "domain".', + }, product: 'A planning boundary, not a repo boundary. One product can span repos; a monorepo can host several products. Shared platform libraries serving multiple products go in a dedicated Platform product — cross-product dependency edges and impact analysis work.', assetTest: 'Model something as an asset only if: (a) code plans will target it, (b) tech debt will be registered against it, or (c) it must appear in blast-radius reports. Assets are coordination units, not folders.', tiers: [ @@ -174,7 +190,7 @@ const handler = createMcpHandler( ) server.tool( 'create_product', - 'Create a product in your workspace. A product is a PLANNING boundary, not a repo boundary — one product may span several repos, and shared platform libraries serving multiple products belong in their own "Platform" product (cross-product dependency edges work).', + 'Create a product in your workspace. Create a product ONLY for something shipped, versioned, and access-controlled as a unit — subsystems of one shippable system are assets (with layers), not products. A product is a PLANNING boundary, not a repo boundary — one product may span several repos, and shared platform libraries serving multiple products belong in their own "Platform" product (cross-product dependency edges work).', { name: z.string(), description: z.string().default(''), tags: z.array(z.string()).default([]), slug: z.string().optional() }, async ({ name, slug, ...rest }, extra) => { requireWrite(extra) @@ -204,7 +220,7 @@ const handler = createMcpHandler( server.tool( 'create_asset', - 'Add an asset to a product. Assets are COORDINATION units, not a folder inventory — model something only if plans will target it, debt will be registered against it, or it belongs in blast-radius reports. Apps and services: always. Libraries: only high-fanout/high-churn ones individually; cluster the long tail into domain-group assets (work items use their `area` field for lib-level precision inside a cluster). Use repoPath for monorepo folders. Call get_modeling_guide for the full heuristic.', + 'Add an asset to a product. Assets are COORDINATION units, not a folder inventory — model something only if plans will target it, debt will be registered against it, or it belongs in blast-radius reports. Apps and services: always. Libraries: only high-fanout/high-churn ones individually; cluster the long tail into domain-group assets (work items use their `area` field for lib-level precision inside a cluster). Use repoPath for monorepo folders, and `layer` (edge/frontend/backend/domain/data/infra/shared) for where the asset sits inside the product — subsystems are assets with layers, not new products. Call get_modeling_guide for the full heuristic.', { productId: z.string(), name: z.string(), @@ -214,6 +230,7 @@ const handler = createMcpHandler( repositoryUrl: z.string().optional(), repoPath: z.string().optional(), documentationUrl: z.string().optional(), + layer: z.string().optional(), ownerEmails: z.array(z.string()).optional(), }, async ({ ownerEmails, ...args }, extra) => { @@ -230,7 +247,7 @@ const handler = createMcpHandler( server.tool( 'update_asset', - 'Update an asset: description, notes (freeform ideation doc, markdown), tags, health, manual tech-debt score, repo details, owners (ownerEmails replaces the full owner set; [] clears it).', + 'Update an asset: description, notes (freeform ideation doc, markdown), tags, health, manual tech-debt score, repo details, layer (edge/frontend/backend/domain/data/infra/shared — where it sits inside the product), owners (ownerEmails replaces the full owner set; [] clears it).', { id: z.string(), name: z.string().optional(), @@ -242,6 +259,7 @@ const handler = createMcpHandler( repositoryUrl: z.string().optional(), repoPath: z.string().optional(), documentationUrl: z.string().optional(), + layer: z.string().optional(), ownerEmails: z.array(z.string()).optional(), }, async ({ id, ownerEmails, ...data }, extra) => { @@ -256,6 +274,20 @@ const handler = createMcpHandler( }, ) + server.tool( + 'move_asset', + 'Move an asset to another product — model refactoring for when a boundary was drawn wrong. Blocked while draft/active plans target the asset (the error lists them; retarget or complete first). Work items follow the asset; history (release stamps, completed-plan links, capabilities, design log) is preserved untouched.', + { assetId: z.string(), targetProductId: z.string() }, + async ({ assetId, targetProductId }, extra) => { + requireWrite(extra) + const userId = uid(extra) + const options = await getAssetOptions(userId) + if (!options.some((a) => a.id === assetId)) return json({ error: 'Asset not found or not accessible' }) + await assertProductAccess(userId, targetProductId) + return json(await moveAsset(assetId, targetProductId)) + }, + ) + server.tool( 'get_asset', 'Full asset context: description, notes, owners, debt score breakdown, linked code plans (with per-plan notes/PRs), and dependency edges in both directions.', diff --git a/docs/app-spec.md b/docs/app-spec.md index 9fd81f3..dec20b7 100644 --- a/docs/app-spec.md +++ b/docs/app-spec.md @@ -1,6 +1,6 @@ ## CodePlans App Spec -> **Status:** current implemented state as of **v0.4.5** (2026-08). For the target +> **Status:** current implemented state as of **v0.4.6** (2026-08). For the target > design and rationale, see `docs/specs/design-spec-v3.md` (all phases shipped), > `docs/specs/releases-and-asset-history-spec.md` (Phases A–D shipped), and > `docs/specs/asset-record-spec.md` (Phase A shipped; Phases B–C are the next @@ -8,7 +8,7 @@ ### Overview -CodePlans is a **code change coordination tool** for engineering teams. It organizes work around the hierarchy **Products → Assets → Code Plans → Tasks**, with **Work Items** (features, bugs, UX issues, tech debt) as the demand side linked many-to-many to code plans, per-asset **branch/PR tracking** on plans, **releases** grouping the plans that ship together (with per-asset version stamps and derived release notes), a per-asset **history timeline and design log**, a per-asset **record** (capabilities register graduated from delivered work), a top-level **Asset Atlas** (live system map with health/debt/activity lenses, plus grid/table views), **asset dependencies** with impact analysis, pull-only **integrations** that mirror external tracker items into work items, and a 41-tool **MCP server** for AI coding agents. Users track technical debt, coordinate architectural changes, and measure team velocity. Deployed at `codeplans.ai`. Stack: Next.js 16 (App Router), Drizzle ORM, pluggable auth/DB (SQLite local / Supabase+Postgres cloud). +CodePlans is a **code change coordination tool** for engineering teams. It organizes work around the hierarchy **Products → Assets → Code Plans → Tasks**, with **Work Items** (features, bugs, UX issues, tech debt) as the demand side linked many-to-many to code plans, per-asset **branch/PR tracking** on plans, **releases** grouping the plans that ship together (with per-asset version stamps and derived release notes), a per-asset **history timeline and design log**, a per-asset **record** (capabilities register graduated from delivered work), a top-level **Asset Atlas** (live system map with health/debt/activity lenses, plus grid/table views), **asset dependencies** with impact analysis, pull-only **integrations** that mirror external tracker items into work items, and a 42-tool **MCP server** for AI coding agents. Users track technical debt, coordinate architectural changes, and measure team velocity. Deployed at `codeplans.ai`. Stack: Next.js 16 (App Router), Drizzle ORM, pluggable auth/DB (SQLite local / Supabase+Postgres cloud). --- @@ -77,6 +77,7 @@ CodePlans is a **code change coordination tool** for engineering teams. It organ | status | `active\|deprecated\|planned` | default `active` | | techDebtScore | integer? | 0–100 scale | | repositoryUrl | text? | | +| layer | text? | v0.4.6 — where the asset sits inside its product; free text, taxonomy edge/frontend/backend/domain/data/infra/shared; display default derived from type | | documentationUrl | text? | | | metadata | JSON object | extensible | | createdAt / updatedAt | timestamp | | @@ -222,6 +223,7 @@ Provenance columns (`source` default `native`, `connectionId`, `externalId/Key/U | `removeReleaseAsset(releaseId, assetId)` | none | Returns `{ id }` or null | | `createDesignNote(data)` / `updateDesignNote` / `deleteDesignNote` | none | `authorKind` defaults `user`; MCP sets `agent` | | `graduateWorkItem(workItemId)` | none | Validates resolved + feature/enhancement + has asset; idempotent per work item; composes `originSummary` from item → first linked plan → that plan's release stamp | +| `moveAsset(assetId, targetProductId)` | at caller layer (both products) | (v0.4.6) Blocked while draft/active plans target the asset (returns `blockingPlans`); work items follow the asset; history (stamps, completed-plan links, capabilities, design log) untouched; no-op if already there | | `updateCapability(id, data)` | none | Edits title/description/area; lineage untouched | | `removeCapability(id, reason?)` | none | Tombstone: `status='removed'` + `removedAt`; reason appended to description as `**Removed:**` | @@ -295,7 +297,7 @@ Two tabs: **Assets** and **Code Plans**. #### `/assets` — Asset Atlas (v0.4.5; future phases in `docs/specs/asset-atlas-spec.md`) Top-level inventory of every visible asset (respects the global product scope + `?product=`), with a stats strip (totals, health breakdown, open debt, active plan targets), search (name/tag), and type/health filters. Three views: -- **Map** (default) — a system map drawn live from the inventory: products as columns, assets as nodes (type icon, name, shipped-version chip), `asset_dependencies` edges as curves (line style per dependency type: solid depends_on, dashed integrates_with, dotted aggregates, arrowheads at the target). **Lenses** recolor node accents and detail lines by Health, Debt (effective score thresholds 25/50), or Activity (active plan targets). Hovering an asset highlights its edges and neighbors and dims the rest (blast radius); click navigates to the asset. Hand-rolled deterministic layout (barycenter-ordered columns) — HTML nodes over an SVG underlay, no graph library. +- **Map** (default) — a system map drawn live from the inventory: columns are **products or layers** (v0.4.6 toggle; auto-defaults to Layer columns when a single product is in scope, since the product axis degenerates to one column), assets as nodes (type icon, name, shipped-version chip), `asset_dependencies` edges as curves (line style per dependency type: solid depends_on, dashed integrates_with, dotted aggregates, arrowheads at the target). Layer columns order by the taxonomy (edge → frontend → backend → domain → data → infra → shared, unknown layers appended); in Layer mode with multi-product scope, node sublabels show the product. **Lenses** recolor node accents and detail lines by Health, Debt (effective score thresholds 25/50), or Activity (active plan targets). Hovering an asset highlights its edges and neighbors and dims the rest (blast radius); click navigates to the asset. Hand-rolled deterministic layout (barycenter-ordered columns) — HTML nodes over an SVG underlay, no graph library. - **Grid** — cards: type icon, product, health dot, version chip, active plans / open debt / capabilities counts, debt-score bar, owners. - **Table** — sortable by name/product/debt/active plans/last shipped. @@ -373,7 +375,7 @@ Client component (`IntegrationsClient`) with: - Delete with confirm (mirrored items are kept, stop syncing) #### `/api/mcp/[transport]` — MCP server (no UI) -Streamable HTTP MCP endpoint (`mcp-handler`) with 41 tools wrapping the query/mutation layer (task assignees resolved by workspace-member email) — reads, plus management of products/assets/dependencies, plan lifecycle (activate/complete incl. write-back), plan targets, work items, tasks, releases (create/update/attach/version-stamp/ship — shipped releases reject mutation), `get_asset_history`, `record_design_note` (agent-attributed design-log entries), `get_asset_record`, and `graduate_work_item`; record deletes are deliberately excluded (link removals only) — see `docs/specs/mcp-server-spec.md`. Auth: `Authorization: Bearer cpk_…` resolved by `lib/mcp/auth.ts` to a user (scopes: read/write); 401 without a valid key. `proxy.ts` exempts this path from session redirects. Connect: `claude mcp add --transport http codeplansCodePlans gives engineering teams a shared view of what's changing, in which components, @@ -495,7 +495,7 @@
An MCP server with 41 tools lets Claude Code, Cursor, and other agents read plans, log work, record design notes, and manage releases — with optional AI-drafted release notes.
+An MCP server with 42 tools lets Claude Code, Cursor, and other agents read plans, log work, record design notes, and manage releases — with optional AI-drafted release notes.
Connect Claude Code, Cursor, and friends over MCP: the full 41-tool catalog, recommended agent workflows, and the AI drafting flags.
+Connect Claude Code, Cursor, and friends over MCP: the full 42-tool catalog, recommended agent workflows, and the AI drafting flags.
Next (v0.4.6): the product-vs-asset rule made operational — asset layers, layer columns on the Atlas, and move_asset for refining a model.
+Shipped in v0.4.6: the product-vs-asset rule made operational — asset layers, layer columns on the Atlas, and move_asset for refining a model.
How the MCP endpoint works: Streamable HTTP inside the app, API-key auth with read/write scopes, and the design of all 41 tools.
+How the MCP endpoint works: Streamable HTTP inside the app, API-key auth with read/write scopes, and the design of all 42 tools.