Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "archeyes",
"source": "./plugins/archeyes",
"description": "Render an implementation plan, tech spec, or codebase as an interactive architecture graph you edit directly; the structured edits flow back to the agent. Skill: /archeyes:review (needs `npx archeyes` — install `archeyes` from npm).",
"version": "0.1.0",
"version": "0.2.0",
"category": "workflow",
"tags": ["architecture", "diagram", "plan", "review"]
}
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Changelog

All notable changes to ArchEyes are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2026-07-29

### Added

- **Edge inspector.** Click an edge to see and act on its `kind` ("used for"),
`description`, and `calls`, with live connection feedback while reconnecting.
- **Canvas toolbar.** A 5-tool toolbar with on-canvas node creation — draw nodes and
edges directly on the graph instead of only editing existing ones.

### Fixed

- **Group layout no longer overlaps.** Dagre laid out nodes ignoring their `group`, so
members scattered across ranks and the group bounding boxes overlapped into a hairball
on any grouped graph. Layout now uses a dagre compound graph so group members cluster
and their boxes stack cleanly in dependency order.

### Changed

- Renamed the **"Approve plan"** button to **"Approve design"**.
- **Review skill guidance:** don't interview the developer before the first render (the
graph is the question); state the chosen altitude and diff-vs-single-state framing up
front; keep the ~30-node aim but allow more when necessary; use short identifier labels
and push prose into `description`/`files`/`calls`.

## [0.1.0] - 2026-07-27

- Initial release: `archeyes review` CLI, React Flow canvas, the `plan-graph.json` +
feedback protocol, and the `/archeyes:review` skill.
15 changes: 8 additions & 7 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ page can't reach it via DNS rebinding. Nothing leaves your machine.

## Scope

**V1 ships:** the full round-trip; before/after diff; drag / reconnect / draw-edge /
comment (`@mention`) / delete; layout persistence; `--resume`; colorblind-safe diff
encoding; light + dark.

**Deferred (V2):** live re-render over websockets, on-canvas node creation, full
keyboard/screen-reader parity (the prose plan is the accessible representation in V1),
whole-project collapse/expand, an MCP-server transport.
**V1 ships:** the full round-trip; before/after diff; a 5-tool canvas toolbar
(select / pan / add node / comment / filter-by-status); drag / reconnect / draw-edge /
on-canvas node creation / comment (`@mention`) / delete; layout persistence; `--resume`;
colorblind-safe diff encoding; light + dark.

**Deferred (V2):** live re-render over websockets, full keyboard/screen-reader parity
(the prose plan is the accessible representation in V1), whole-project collapse/expand,
an MCP-server transport.

## Run it locally

Expand Down
34 changes: 34 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# TODOS

## Before publish: bump version to 0.2.0 (edge-inspector feature)

**What:** Bump `version` from `0.1.0` → `0.2.0` in all three places, together:
- `package.json`
- `plugins/archeyes/.claude-plugin/plugin.json`
- `.claude-plugin/marketplace.json` (the plugin entry's `version`)

**Why:** The `feat/edge-inspector` work added user-facing capability — edge
`description` + `calls[]` in the graph schema, a clickable edge inspector, and a new
`edgeComments` feedback channel. The plugin/marketplace still advertise `0.1.0`, so
consumers can't tell the skill's behavior grew.

**Context:** Additive/backward-compatible (all new schema fields are optional), so a
minor bump fits pre-1.0 semantics — not a breaking change. Deliberately deferred:
nothing is published to npm yet (`NPM_TOKEN` secret not set per CLAUDE.md), so the
version only matters once something ships. Do this as part of the first release.

**Depends on / blocked by:** none. Pairs with cutting the matching GitHub release
(`gh release create v0.2.0`) per CLAUDE.md's publish flow.

## Design provenance: port EdgeInspector back into Claude Design

**What:** The shipped edge inspector lives in `ui/src/Rail.tsx` (`EdgeInspector`). The
Claude Design System project's `ui_kits/canvas/Inspector.jsx` is still node-only.

**Why:** Keep the design source matching what shipped, per the one-way sync rule in
CLAUDE.md (Claude Design → `_ds/` → `ui/src/tokens`). The React port itself lives in
`ui/src`; only the design-system reference component drifts.

**Context:** The full edge-inspector layout is already specced in `proto/Panel.jsx`
(`EdgeInspectorView`) — port that into the design-system project when convenient. Not
blocking; provenance hygiene only.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "archeyes",
"version": "0.1.0",
"version": "0.2.0",
"description": "A diagram you talk back to — edit an AI agent's plan and send structured feedback. Interactive architecture diagrams for a plan, an existing codebase, or a feature.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -30,6 +30,7 @@
"dist/cli",
"dist/ui",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/archeyes/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "archeyes",
"displayName": "ArchEyes",
"version": "0.1.0",
"version": "0.2.0",
"description": "A diagram you talk back to — render a plan, an existing codebase, or a feature as an interactive architecture graph, edit it directly (drag, reconnect, comment), and send the structured edits back to the agent.",
"author": { "name": "thisAAY", "url": "https://github.com/thisAAY" },
"homepage": "https://github.com/thisAAY/archeyes",
Expand Down
54 changes: 44 additions & 10 deletions plugins/archeyes/skills/review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ until they approve.
from whatever is at hand: a fresh plan-mode plan, an existing markdown tech spec, the
codebase itself, or current + planned together (which powers the before/after diff).

**Don't interview the developer first.** The graph is the question — render your best
guess and let them fix it on the canvas. That's faster and more precise than terminal
Q&A. The one thing the canvas *can't* express as an edit is a framing choice: **altitude**
(system vs service vs class level) and **before/after diff vs single-state graph**. Pick one,
state which you picked in your terminal message, and ask up front only when you're genuinely
torn — fixing altitude visually means the developer deleting and merging a dozen nodes by hand.

## The loop

1. **Author the graph.** Write `plan-graph.json` (schema below). Keep your prose plan too —
Expand Down Expand Up @@ -55,7 +62,9 @@ renamed something you shouldn't have; reconcile before re-rendering.
"status": "new", "files": ["src/services/payment.ts"] }
],
"edges": [
{ "id": "e1", "from": "OrderService", "to": "DB", "kind": "calls", "status": "existing" }
{ "id": "e1", "from": "OrderService", "to": "DB", "kind": "reads / writes", "status": "existing",
"description": "Reads and writes order rows through the shared Postgres pool.",
"calls": ["store.query<Order>(sql, params)", "store.tx(fn)"] }
]
}
```
Expand All @@ -68,18 +77,32 @@ renamed something you shouldn't have; reconcile before re-rendering.
changing `modify`, what you're removing `delete`.
- Every `edge.from`/`edge.to` must be a real node `id`; every `node.group` a real group `id`.
The CLI validates this and refuses an inconsistent graph.
- On an edge, `kind` is the short "used for" verb (rendered on the arrow); `description` is the
free-text "what the source uses the target for"; `calls[]` lists the specific methods/functions
the source invokes on the target (the edge-analog of `node.files[]`). The dev sees all three by
clicking the arrow. Author them — a bare arrow with no `description`/`calls` is a weaker diagram.

**Granularity.** Class/module-level for a feature plan. Aim for ~30 nodes or fewer;
beyond that, collapse detail into groups. Go higher only when the plan genuinely can't
be expressed with fewer moving parts. A graph you can read at a glance beats a
complete-but-dense one.

**Granularity.** Class/module-level for a feature plan. Cap at ~30 nodes — beyond that,
collapse detail into groups. A graph you can read at a glance beats a complete-but-dense one.
**Labels are short identifiers** — `PaymentService`, not "the service that handles
payments". Node boxes are fixed-width and truncate long labels. Put the sentence in
`description`, the paths in `files`, the methods in `calls`.

## Interpreting the feedback envelope

```json
{
"action": "revise",
"comments": [{ "nodeId": "OrderService", "text": "move token refresh into @PaymentService" }],
"reconnected": [{ "edgeId": "e1", "end": "target", "was": "DB", "now": "OrderRepo" }],
"added": { "edges": [{ "from": "PaymentService", "to": "PaymentRepo" }] },
"comments": [{ "nodeId": "OrderService", "text": "move token refresh into @PaymentService" }],
"edgeComments": [{ "edgeId": "e1", "text": "this read/write should go through @OrderRepo, not the DB directly" }],
"reconnected": [{ "edgeId": "e1", "end": "target", "was": "DB", "now": "OrderRepo" }],
"added": {
"nodes": [{ "tempId": "new:1", "label": "PricingService", "kind": "service", "group": "domain", "description": "owns price calc" }],
"edges": [{ "from": "OrderService", "to": "new:1" }, { "from": "PaymentService", "to": "PaymentRepo" }]
},
"deleted": { "nodes": ["LegacyPayAdapter"], "edges": [] },
"moved": [{ "nodeId": "PaymentRepo", "toGroup": "infra" }],
"generalNote": "optional free-text"
Expand All @@ -90,17 +113,28 @@ What each edit MEANS architecturally:

- **comments** — the developer's intent for that node. `@Name` mentions reference other
nodes by id. This is the richest signal; read it as a direct instruction.
- **edgeComments** — the developer's intent for that *connection* (left via the edge inspector).
`{edgeId, text}`, `@Name` mentions allowed. Read it as an instruction about the relationship —
revise the edge's `kind`/`description`/`calls` and the code plan to match.
- **reconnected** — an edge's endpoint was re-dragged. `{edgeId, end, was, now}`: the `end`
(`source`|`target`) of `edgeId` should now point at `now` instead of `was`. Treat it as a
decision: "this dependency should target `now`, not `was`." Update the edge in the graph
and change the code plan to match (e.g. call the repository, not the DB directly).
- **added.edges** — a new dependency the developer drew. V1 sends edges only. Add it and
reflect the new coupling in the plan.
- **added.nodes** — a new component the developer drew on the canvas. Each carries a
client-side `tempId` (e.g. `"new:1"`), a `label`, a `kind`, and optional `group`/`description`.
Create a real node for each: **assign it a real, stable id** (do NOT keep `new:1`), honor the
label/kind/group, and fold the component into the code plan. In your revised `plan-graph.json`,
set the new node's `status` to `"new"`.
- **added.edges** — new dependencies the developer drew. An endpoint (`from`/`to`) may be a real
node id OR an added-node `tempId` — resolve each tempId to the real id you assigned above, then
add the edge and reflect the new coupling in the plan.
- **deleted** — nodes/edges the developer wants gone. Remove them and remove the
corresponding code from the plan.
- **moved** — a node reassigned to a different group/layer. Re-home it.
- New **nodes** are requested via comments (e.g. "add a @PaymentRepo below this"), because
V1's canvas draws edges but not nodes. Create the node when a comment asks for one.

When you create nodes from `added.nodes`, **echo the tempId → real-id mapping** to the developer
in your terminal reply (e.g. `new:1 → PricingService`) so the next render shows the real name and
they can see their node was understood.

After revising, rewrite BOTH `plan-graph.json` and the prose plan so they stay in sync,
then re-run `archeyes review plan-graph.json`.
Expand Down
41 changes: 38 additions & 3 deletions schema/feedback.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
}
}
},
"edgeComments": {
"type": "array",
"default": [],
"description": "Feedback the developer left on a connection (via the edge inspector). Edge-scoped counterpart of comments[]; kept separate so the agent reads a dedicated edge stream.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["edgeId", "text"],
"properties": {
"edgeId": { "type": "string", "minLength": 1 },
"text": { "type": "string", "minLength": 1, "description": "May contain @NodeId mentions" }
}
}
},
"reconnected": {
"type": "array",
"default": [],
Expand Down Expand Up @@ -53,8 +67,29 @@
"type": "object",
"additionalProperties": false,
"default": { "edges": [] },
"description": "V1 accepts new EDGES only. New nodes are requested via comments (e.g. 'add a @PaymentRepo below this').",
"description": "New nodes and/or edges the developer drew on the canvas. A new node carries a client-side tempId (e.g. 'new:1'); added edges may reference that tempId as an endpoint. The agent creates each node, assigns a real id, wires the edges, and echoes the tempId → real-id mapping back so the next render shows the real name.",
"properties": {
"nodes": {
"type": "array",
"default": [],
"description": "Nodes the dev created this round. tempId is dev-side only; the agent replaces it with a real node id.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["tempId", "label", "kind"],
"properties": {
"tempId": { "type": "string", "minLength": 1, "description": "Client-side placeholder id (e.g. 'new:1'). Referenced by added.edges until the agent assigns a real id." },
"label": { "type": "string", "minLength": 1 },
"kind": {
"type": "string",
"enum": ["service", "repository", "datastore", "adapter", "external", "module", "component", "other"],
"description": "Same kind vocabulary as plan-graph nodes; drives the node icon."
},
"group": { "type": "string", "description": "Optional target group id (references graph.groups[].id)" },
"description": { "type": "string", "description": "Optional free-text intent for the new node" }
}
}
},
"edges": {
"type": "array",
"default": [],
Expand All @@ -63,8 +98,8 @@
"additionalProperties": false,
"required": ["from", "to"],
"properties": {
"from": { "type": "string", "minLength": 1 },
"to": { "type": "string", "minLength": 1 },
"from": { "type": "string", "minLength": 1, "description": "Source node id, or an added-node tempId" },
"to": { "type": "string", "minLength": 1, "description": "Target node id, or an added-node tempId" },
"kind": { "type": "string" }
}
}
Expand Down
40 changes: 39 additions & 1 deletion schema/feedback.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ export interface Feedback {
*/
text: string;
}[];
/**
* Feedback the developer left on a connection (via the edge inspector). Edge-scoped counterpart of comments[]; kept separate so the agent reads a dedicated edge stream.
*/
edgeComments?: {
edgeId: string;
/**
* May contain @NodeId mentions
*/
text: string;
}[];
reconnected?: {
edgeId: string;
/**
Expand All @@ -40,11 +50,39 @@ export interface Feedback {
edges?: string[];
};
/**
* V1 accepts new EDGES only. New nodes are requested via comments (e.g. 'add a @PaymentRepo below this').
* New nodes and/or edges the developer drew on the canvas. A new node carries a client-side tempId (e.g. 'new:1'); added edges may reference that tempId as an endpoint. The agent creates each node, assigns a real id, wires the edges, and echoes the tempId → real-id mapping back so the next render shows the real name.
*/
added?: {
/**
* Nodes the dev created this round. tempId is dev-side only; the agent replaces it with a real node id.
*/
nodes?: {
/**
* Client-side placeholder id (e.g. 'new:1'). Referenced by added.edges until the agent assigns a real id.
*/
tempId: string;
label: string;
/**
* Same kind vocabulary as plan-graph nodes; drives the node icon.
*/
kind: "service" | "repository" | "datastore" | "adapter" | "external" | "module" | "component" | "other";
/**
* Optional target group id (references graph.groups[].id)
*/
group?: string;
/**
* Optional free-text intent for the new node
*/
description?: string;
}[];
edges?: {
/**
* Source node id, or an added-node tempId
*/
from: string;
/**
* Target node id, or an added-node tempId
*/
to: string;
kind?: string;
}[];
Expand Down
9 changes: 7 additions & 2 deletions schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ export function validateFeedback(data: unknown): ValidationResult<Feedback> {

/**
* Every node id a feedback envelope points at (comments, reconnect endpoints,
* added-edge endpoints, moves). Nodes the dev explicitly deleted are excluded —
* those are *supposed* to be gone in the next graph.
* added-edge endpoints, moves). Two classes of id are *supposed* to be absent
* from the next graph, so they're excluded to avoid false orphan-warnings:
* - nodes the dev explicitly deleted (gone on purpose), and
* - added-node tempIds (e.g. "new:1"): the dev drew these this round; the agent
* replaces each tempId with a real node id, so an added edge pointing at a
* tempId will never find that tempId in the revised graph — by design.
*/
export function referencedNodeIds(feedback: Feedback): string[] {
const refs = new Set<string>();
Expand All @@ -105,6 +109,7 @@ export function referencedNodeIds(feedback: Feedback): string[] {
}
for (const m of feedback.moved ?? []) refs.add(m.nodeId);
for (const del of feedback.deleted?.nodes ?? []) refs.delete(del);
for (const n of feedback.added?.nodes ?? []) refs.delete(n.tempId);
return [...refs];
}

Expand Down
14 changes: 12 additions & 2 deletions schema/plan-graph.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,20 @@
"to": { "type": "string", "minLength": 1, "description": "Target node id" },
"kind": {
"type": "string",
"description": "Relationship label rendered on the edge, e.g. calls, reads, writes, depends",
"description": "Short relationship label rendered on the edge and shown as 'Used for' in the inspector, e.g. calls, reads, writes, depends, 'reads / writes'",
"default": "calls"
},
"status": { "$ref": "#/definitions/status" }
"status": { "$ref": "#/definitions/status" },
"description": {
"type": "string",
"description": "Free text: what the source uses the target for. Shown as 'What it's used for' in the edge inspector. The edge-analog of node.description."
},
"calls": {
"type": "array",
"items": { "type": "string" },
"default": [],
"description": "The specific methods/functions the source invokes on the target, e.g. 'charge(orderId, amount)'. Shown as 'Calls · N' in the edge inspector. The edge-analog of node.files[]. For a 'new' edge these are planned calls that may not exist yet."
}
}
}
}
Expand Down
Loading
Loading