loom-spec 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,34 +2,72 @@
2
2
 
3
3
  > A node-based architecture spec that lives in your repo. AI-readable, AI-writable, git-diffable.
4
4
 
5
- `loom-spec` keeps a structured visual spec of your application's architecture **inside your repo**, designed to be edited by both humans (in a browser-based node editor) and AI coding agents (directly via JSON files).
5
+ `loom-spec` keeps a structured visual spec of your application's architecture **inside your repo**, designed to be edited by both humans (in a browser-based node editor) and AI coding agents (directly via JSON files or MCP tool calls).
6
6
 
7
- It's a spec layer, not an execution layer. The nodes don't run — they describe.
7
+ It's a spec layer, not an execution layer. The nodes describe; they don't run.
8
8
 
9
- ## Install
9
+ ## Why
10
+
11
+ - **Architecture drift.** Mermaid diagrams in `docs/` go stale the moment you refactor.
12
+ - **Agents losing the forest for the trees.** An agent grepping through `src/` doesn't see the system. Every session rebuilds the mental model from scratch.
13
+ - **The mental-model gap.** People who think in signal flow but don't read code well get cut out.
14
+
15
+ `loom-spec` is one canonical, machine-readable file-set under `.loom/` that says what exists and how it connects. Humans edit it visually. The agent reads it before implementing and updates it when code changes. `code_refs` anchor each node to actual source, so [`loom-spec validate`](#loom-spec-validate---root-dir---json) catches drift instead of letting it accumulate.
16
+
17
+ ## Install + first run
10
18
 
11
19
  ```bash
12
- # No global install needed — use via npx
13
- npx loom-spec init
14
- npx loom-spec view
20
+ cd your-project
21
+ npx loom-spec init --mcp # scaffolds .loom/, the agent skill, and .mcp.json
22
+ npx loom-spec view # opens the editor on http://localhost:7777
15
23
  ```
16
24
 
17
- Or as a dev dependency:
25
+ `--mcp` is optional but recommended — it auto-registers the MCP server for Claude Code (and other MCP-capable agents). Skip it if you want to wire that up manually later via `npx loom-spec install-mcp`.
26
+
27
+ If you prefer it as a dev dependency:
18
28
 
19
29
  ```bash
20
30
  npm install --save-dev loom-spec
21
31
  ```
22
32
 
23
- Then in `package.json`:
24
-
25
33
  ```json
26
34
  {
27
35
  "scripts": {
28
- "loom": "loom-spec view"
36
+ "loom": "loom-spec view",
37
+ "loom:check": "loom-spec validate"
29
38
  }
30
39
  }
31
40
  ```
32
41
 
42
+ ## A typical workflow
43
+
44
+ 1. **Sketch the high-level architecture once.** Open `loom-spec view`, click **+ Add** to drop services, data stores, and UI components onto the canvas. Connect them with edges. Mark nodes as `planned` if you haven't built them yet, `implemented` once the code exists.
45
+ 2. **Let the agent grow it.** With Claude Code (or any MCP-capable agent) in the same repo, tell it what to build: *"add a payments service that the checkout flow calls"*. The agent calls `loom_add_node` and `loom_add_edge`, then writes the actual code. Your open editor updates live via SSE.
46
+ 3. **Anchor nodes to code.** When a feature is done, the agent (or you) adds `code_refs`: `{ "path": "src/server/payments.ts", "symbol": "chargeCard" }`. This is what makes drift detection work.
47
+ 4. **Catch drift in CI.** Add `loom-spec validate` as a pre-commit hook or CI step. It exits non-zero if any `code_refs` point at missing files or unresolved symbols.
48
+ 5. **Stale, don't delete.** When the underlying code goes away, the agent marks the node `status: stale` instead of deleting it. Humans review staleness — the architectural history stays.
49
+
50
+ ## What lives in your repo
51
+
52
+ ```
53
+ .loom/
54
+ ├── README.md Why this directory exists; for humans.
55
+ ├── node-types.json The vocabulary: ui, service, data, event, external (plus your customs).
56
+ └── diagrams/
57
+ └── overview.flow.json { nodes, edges, groups }
58
+
59
+ .claude/
60
+ └── skills/
61
+ └── loom-spec/
62
+ └── SKILL.md Tells Claude Code (or any Agent-Skills-aware tool)
63
+ when and how to maintain the spec, with five
64
+ worked examples.
65
+
66
+ .mcp.json Registers the MCP server, if you ran init --mcp.
67
+ ```
68
+
69
+ Nothing of the `loom-spec` package itself is committed — the npm install lives in `node_modules/`. Only the spec and the skill are tracked.
70
+
33
71
  ## Commands
34
72
 
35
73
  ### `loom-spec init [--path <dir>] [--force]`
@@ -51,6 +89,18 @@ Add `--mcp` to also register the MCP server in `.mcp.json` (idempotent merge —
51
89
 
52
90
  Starts a local browser editor. Walks up from `--root` (default: cwd) to find the nearest `.loom/`. Opens on port 7777 by default.
53
91
 
92
+ In the editor you can:
93
+
94
+ - Drag nodes; edits debounce and write to disk within ~500ms
95
+ - Click a node or edge to inspect and edit fields, code refs, tags, type-specific properties
96
+ - Drag from a node's right handle to another node to create an edge
97
+ - Use the **+ Add** menu in the top bar to add a new node by type
98
+ - Use the diagram switcher (top-left dropdown) to navigate between diagrams or create new ones
99
+ - Use the "Drill into" chevron on any node or group with `drill_down` set to jump to a sub-diagram
100
+ - Toggle light/dark theme; preference is persisted
101
+
102
+ External edits to the JSON files (e.g. by an AI agent) propagate to the open UI live via Server-Sent Events — no reload needed.
103
+
54
104
  ### `loom-spec validate [--root <dir>] [--json]`
55
105
 
56
106
  Checks every diagram for schema validity plus **code-ref drift**: missing files, missing symbols, out-of-range line ranges. Skips nodes marked `planned` or `deprecated` (their code may legitimately not exist). Exit code is non-zero if any issue is found — useful as a CI step or pre-commit hook.
@@ -86,17 +136,9 @@ The server exposes semantic tools that validate against the schema before writin
86
136
  - `loom_add_edge`, `loom_delete_edge`
87
137
  - `loom_validate` (same drift check as the CLI)
88
138
 
89
- In the editor you can:
90
-
91
- - Drag nodes; edits debounce and write to disk within ~500ms
92
- - Click a node or edge to inspect and edit fields, code refs, tags, type-specific properties
93
- - Drag from a node's right handle to another node to create an edge
94
- - Use the "+ Add" menu in the top bar to add a new node by type
95
- - Use the diagram switcher (top-left dropdown) to navigate between diagrams or create new ones
96
- - Use the "Drill into" chevron on any node or group with `drill_down` set to jump to a sub-diagram
97
- - Toggle light/dark theme; preference is persisted
139
+ ### `loom-spec install-mcp [--path <dir>]`
98
140
 
99
- External edits to the JSON files (e.g. by an AI agent) propagate to the open UI live via Server-Sent Events no reload needed.
141
+ Writes the MCP-server entry into `.mcp.json` without touching anything else. Idempotent (safe to run repeatedly) and non-destructive (other MCP servers and unrelated top-level keys are preserved).
100
142
 
101
143
  ## File format
102
144
 
@@ -157,6 +199,68 @@ Edge kinds: `request`, `event`, `data-read`, `data-write`, `signal`, `dependency
157
199
 
158
200
  Full JSON Schemas ship with the package — see `schema/diagram.schema.json` and `schema/node-types.schema.json`.
159
201
 
202
+ ### Adding custom node types
203
+
204
+ `node-types.json` is yours to edit. Add a type for whatever domain you're modelling. A worker with typed ports:
205
+
206
+ ```json
207
+ {
208
+ "types": {
209
+ "worker": {
210
+ "label": "Worker",
211
+ "color": "#fb923c",
212
+ "icon": "server",
213
+ "fields": [
214
+ { "name": "queue", "type": "string", "required": true },
215
+ { "name": "concurrency", "type": "number" }
216
+ ],
217
+ "ports": {
218
+ "in": [{ "name": "jobs", "signal": "data" }],
219
+ "out": [{ "name": "results", "signal": "data" }, { "name": "errors", "signal": "data" }]
220
+ }
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ Once that's saved, the **+ Add** menu shows "Worker"; new worker nodes render with three labeled handles instead of one generic one. Edges can target a specific port via `from: "worker-1:results"`.
227
+
228
+ ### Drill-down between diagrams
229
+
230
+ For LangGraph-style multi-step agents or anything with non-trivial internal flow: one node in the overview, plus a sub-diagram with the steps inside.
231
+
232
+ ```json
233
+ // overview.flow.json
234
+ {
235
+ "id": "agent",
236
+ "type": "service",
237
+ "label": "Agent",
238
+ "drill_down": "agent-internals",
239
+ "code_refs": [{ "path": "agent.py" }]
240
+ }
241
+ ```
242
+
243
+ ```json
244
+ // agent-internals.flow.json
245
+ {
246
+ "nodes": [
247
+ { "id": "decide", "type": "service", "label": "decide_next_step",
248
+ "code_refs": [{ "path": "agent.py", "symbol": "decide_next_step" }] },
249
+ { "id": "call-tool", "type": "service", "label": "call_tool",
250
+ "code_refs": [{ "path": "agent.py", "symbol": "call_tool" }] },
251
+ { "id": "format", "type": "service", "label": "format_response",
252
+ "code_refs": [{ "path": "agent.py", "symbol": "format_response" }] }
253
+ ],
254
+ "edges": [
255
+ { "id": "e1", "from": "decide", "to": "call-tool", "kind": "control", "label": "if tool needed" },
256
+ { "id": "e2", "from": "call-tool", "to": "format", "kind": "control" },
257
+ { "id": "e3", "from": "decide", "to": "format", "kind": "control", "label": "if final" }
258
+ ]
259
+ }
260
+ ```
261
+
262
+ Click the chevron on the overview's `agent` node to navigate in.
263
+
160
264
  ## How AI agents use it
161
265
 
162
266
  `loom-spec init` writes a `SKILL.md` to `.claude/skills/loom-spec/` following the [Agent Skills open standard](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview). Claude Code (and other tools that adopt the convention) auto-discovers it.
@@ -0,0 +1,15 @@
1
+ export interface ImportTraceArgs {
2
+ /** Path to the OTLP JSON trace file. */
3
+ trace: string;
4
+ /** Timeline id to create or append into. */
5
+ asId: string;
6
+ /** Diagram id the new timeline overlays. */
7
+ diagramId: string;
8
+ /** Optional path to a mapping file (see MappingFile shape below). */
9
+ map?: string;
10
+ /** Append to an existing timeline instead of overwriting. */
11
+ append: boolean;
12
+ /** Working directory root (walked up to find .loom/). */
13
+ root: string;
14
+ }
15
+ export declare function runImportTrace(args: ImportTraceArgs): Promise<void>;
@@ -0,0 +1,188 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { findLoomRoot } from "../server/findLoomRoot.js";
4
+ import { readDiagram, readTimeline, writeTimeline } from "../server/fileOps.js";
5
+ import { parseOtlpJson } from "../server/otel.js";
6
+ import { validateTimeline } from "../validate.js";
7
+ const KIND_TO_EVENT_KIND = {
8
+ internal: "compute",
9
+ server: "io",
10
+ client: "io",
11
+ producer: "io",
12
+ consumer: "io",
13
+ unknown: undefined,
14
+ };
15
+ async function loadMapping(path) {
16
+ const raw = await readFile(path, "utf8");
17
+ const parsed = JSON.parse(raw);
18
+ return {
19
+ serviceMap: new Map(Object.entries(parsed.services ?? {})),
20
+ spanMap: new Map(Object.entries(parsed.spans ?? {})),
21
+ };
22
+ }
23
+ /**
24
+ * Decide which node a span belongs to.
25
+ * Precedence: explicit span map > explicit service map > heuristic match.
26
+ */
27
+ function resolveNode(span, mapping, nodes) {
28
+ if (mapping) {
29
+ const direct = mapping.spanMap.get(span.name);
30
+ if (direct)
31
+ return direct;
32
+ if (span.serviceName) {
33
+ const svc = mapping.serviceMap.get(span.serviceName);
34
+ if (svc)
35
+ return svc;
36
+ }
37
+ }
38
+ // Heuristic: try matching span.name first (it's most specific — for
39
+ // CLIENT/PRODUCER spans it names the *downstream* target, which is the
40
+ // node we want), then fall back to service.name.
41
+ const candidates = [];
42
+ candidates.push(span.name.toLowerCase());
43
+ if (span.serviceName)
44
+ candidates.push(span.serviceName.toLowerCase());
45
+ for (const c of candidates) {
46
+ // 1. exact id match
47
+ const idHit = nodes.find((n) => n.id.toLowerCase() === c);
48
+ if (idHit)
49
+ return idHit.id;
50
+ // 2. node id appears as a token inside the candidate
51
+ // (e.g. "todo-store update" → finds id "todo-store")
52
+ const idInCandidate = nodes.find((n) => c.includes(n.id.toLowerCase()));
53
+ if (idInCandidate)
54
+ return idInCandidate.id;
55
+ // 3. candidate appears in a node label
56
+ // (e.g. "todo-api" → finds label "Todo API")
57
+ const labelHit = nodes.find((n) => n.label.toLowerCase().includes(c));
58
+ if (labelHit)
59
+ return labelHit.id;
60
+ // 4. code-ref path includes the candidate
61
+ const refHit = nodes.find((n) => (n.code_refs ?? []).some((r) => r.path.toLowerCase().includes(c)));
62
+ if (refHit)
63
+ return refHit.id;
64
+ }
65
+ return null;
66
+ }
67
+ function eventIdFor(spanId, existingIds) {
68
+ // Stable: take the first 8 hex chars of the span id, prefix with "ev".
69
+ // Fall back to a sequence if collision (shouldn't happen in practice).
70
+ const base = `ev-${spanId.slice(0, 8) || "span"}`.toLowerCase();
71
+ if (!existingIds.has(base))
72
+ return base;
73
+ let i = 2;
74
+ while (existingIds.has(`${base}-${i}`))
75
+ i++;
76
+ return `${base}-${i}`;
77
+ }
78
+ export async function runImportTrace(args) {
79
+ // 1. Locate the .loom/ root and load the diagram we're overlaying.
80
+ const loomRoot = await findLoomRoot(args.root);
81
+ const diagram = await readDiagram(loomRoot.loomPath, args.diagramId);
82
+ // 2. Load and parse the trace.
83
+ const traceRaw = await readFile(resolve(args.trace), "utf8");
84
+ const traceJson = JSON.parse(traceRaw);
85
+ const spans = parseOtlpJson(traceJson);
86
+ if (spans.length === 0) {
87
+ console.error("Trace contained 0 spans — nothing to import.");
88
+ process.exit(1);
89
+ }
90
+ // 3. Optional mapping file.
91
+ const mapping = args.map ? await loadMapping(resolve(args.map)) : null;
92
+ // 4. Compute t=0 (earliest span start) so the timeline is repo-portable.
93
+ const minStartNs = spans.reduce((m, s) => (s.startNs < m ? s.startNs : m), spans[0].startNs);
94
+ // 5. If appending, load existing timeline; otherwise start fresh.
95
+ let existing = null;
96
+ if (args.append) {
97
+ try {
98
+ existing = await readTimeline(loomRoot.loomPath, args.asId);
99
+ if (existing.diagram !== args.diagramId) {
100
+ console.error(`Refusing to append: existing timeline '${args.asId}' references diagram ` +
101
+ `'${existing.diagram}', not '${args.diagramId}'.`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+ catch (e) {
106
+ if (e.code !== "ENOENT")
107
+ throw e;
108
+ // Fall through: --append on a missing file behaves like create.
109
+ }
110
+ }
111
+ const existingIds = new Set(existing?.events.map((e) => e.id) ?? []);
112
+ const spanIdToEventId = new Map();
113
+ const events = [];
114
+ const skipped = [];
115
+ // 6. First pass: pick node + event id for each span.
116
+ for (const s of spans) {
117
+ const node = resolveNode(s, mapping, diagram.nodes);
118
+ if (!node) {
119
+ skipped.push({ span: s, reason: "no matching node" });
120
+ continue;
121
+ }
122
+ const id = eventIdFor(s.spanId, existingIds);
123
+ existingIds.add(id);
124
+ spanIdToEventId.set(s.spanId, id);
125
+ const start_ms = Number((s.startNs - minStartNs) / 1000000n);
126
+ const duration_ms = Number((s.endNs - s.startNs) / 1000000n);
127
+ events.push({
128
+ id,
129
+ node,
130
+ start_ms: Math.max(0, start_ms),
131
+ duration_ms: Math.max(0, duration_ms),
132
+ label: s.name.length > 60 ? s.name.slice(0, 57) + "…" : s.name,
133
+ kind: KIND_TO_EVENT_KIND[s.kind] ?? "compute",
134
+ track: s.serviceName ?? undefined,
135
+ tags: [`otel-import`, `kind:${s.kind}`],
136
+ });
137
+ }
138
+ // 7. Second pass: wire triggered_by from span parent.
139
+ for (let i = 0; i < spans.length; i++) {
140
+ const s = spans[i];
141
+ const ev = events.find((e) => e.id === spanIdToEventId.get(s.spanId));
142
+ if (!ev)
143
+ continue;
144
+ if (s.parentSpanId) {
145
+ const parentEventId = spanIdToEventId.get(s.parentSpanId);
146
+ if (parentEventId)
147
+ ev.triggered_by = parentEventId;
148
+ }
149
+ }
150
+ // 8. Build the timeline. Auto-derive tracks from distinct services.
151
+ const trackIds = new Set((existing?.tracks?.map((t) => t.id) ?? []).concat(events.map((e) => e.track).filter((t) => !!t)));
152
+ const tracks = Array.from(trackIds).map((id) => ({ id, label: id }));
153
+ const timeline = {
154
+ version: "1",
155
+ id: args.asId,
156
+ title: existing?.title ?? `Imported: ${args.asId}`,
157
+ description: existing?.description ??
158
+ `Generated by 'loom-spec import-trace' from ${args.trace}.`,
159
+ diagram: args.diagramId,
160
+ events: existing ? [...existing.events, ...events] : events,
161
+ tracks,
162
+ };
163
+ // 9. Validate before writing.
164
+ const v = await validateTimeline(timeline);
165
+ if (!v.ok) {
166
+ console.error("Generated timeline failed schema validation:");
167
+ for (const e of v.errors)
168
+ console.error(` - ${e}`);
169
+ process.exit(1);
170
+ }
171
+ await writeTimeline(loomRoot.loomPath, args.asId, timeline);
172
+ // 10. Report.
173
+ console.log(`Wrote ${events.length} event${events.length === 1 ? "" : "s"} to ` +
174
+ `${loomRoot.loomPath}/timelines/${args.asId}.timeline.json` +
175
+ (existing ? ` (appended; total ${timeline.events.length})` : ""));
176
+ if (skipped.length > 0) {
177
+ console.log(`Skipped ${skipped.length} span${skipped.length === 1 ? "" : "s"} ` +
178
+ `with no matching node. Pass --map mapping.json to override.`);
179
+ const sample = skipped.slice(0, 5);
180
+ for (const { span, reason } of sample) {
181
+ console.log(` • ${span.name} (service=${span.serviceName ?? "—"}) ${reason}`);
182
+ }
183
+ if (skipped.length > sample.length) {
184
+ console.log(` … and ${skipped.length - sample.length} more.`);
185
+ }
186
+ }
187
+ }
188
+ //# sourceMappingURL=importTrace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importTrace.js","sourceRoot":"","sources":["../../src/cli/importTrace.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ import { runView } from "./view.js";
4
4
  import { runValidate } from "./validate.js";
5
5
  import { runMcp } from "./mcp.js";
6
6
  import { runInstallMcp } from "./installMcp.js";
7
+ import { runImportTrace } from "./importTrace.js";
7
8
  const HELP = `loom-spec — node-based architecture spec for your repo
8
9
 
9
10
  Usage:
@@ -27,11 +28,18 @@ Usage:
27
28
  pre-commit hook.
28
29
 
29
30
  loom-spec mcp [--root <dir>]
30
- Start a Model Context Protocol server on stdio. Exposes
31
- loom_list_diagrams, loom_read_diagram, loom_add_node,
32
- loom_update_node, loom_mark_stale, loom_delete_node,
33
- loom_add_edge, loom_delete_edge, loom_validate as MCP tools.
34
- Wire it into Claude Code's mcp.json (or any MCP-capable client).
31
+ Start a Model Context Protocol server on stdio. Exposes 15 tools
32
+ for diagrams (loom_list_diagrams, loom_add_node, loom_add_edge, …)
33
+ and timelines (loom_list_timelines, loom_add_event, …) — wire it
34
+ into Claude Code's mcp.json (or any MCP-capable client).
35
+
36
+ loom-spec import-trace <trace.json> --as <timeline-id> --diagram <diagram-id>
37
+ [--map <mapping.json>] [--append] [--root <dir>]
38
+ Read an OpenTelemetry OTLP-JSON trace and generate a timeline that
39
+ mirrors the actual spans on the named diagram. Each span becomes
40
+ an event; parent/child relationships preserve as triggered_by;
41
+ service.name becomes the track. Spans whose service or name can't
42
+ be matched to a node are skipped — pass --map to override.
35
43
 
36
44
  loom-spec --help
37
45
  Print this help.
@@ -98,6 +106,30 @@ async function main() {
98
106
  });
99
107
  return;
100
108
  }
109
+ if (subcommand === "import-trace") {
110
+ // Positional arg: the trace file path (first non-flag in rest).
111
+ const trace = rest.find((a) => a && !a.startsWith("--"));
112
+ if (!trace) {
113
+ console.error("import-trace: missing trace file path");
114
+ console.log(HELP);
115
+ process.exit(1);
116
+ }
117
+ const asId = flags.as;
118
+ const diagramId = flags.diagram;
119
+ if (!asId || !diagramId) {
120
+ console.error("import-trace: --as <timeline-id> and --diagram <diagram-id> are required");
121
+ process.exit(1);
122
+ }
123
+ await runImportTrace({
124
+ trace,
125
+ asId,
126
+ diagramId,
127
+ map: typeof flags.map === "string" ? flags.map : undefined,
128
+ append: Boolean(flags.append),
129
+ root: flags.root ?? process.cwd(),
130
+ });
131
+ return;
132
+ }
101
133
  console.error(`unknown subcommand: ${subcommand}\n`);
102
134
  console.log(HELP);
103
135
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}